C# and "Weighted probability" [duplicate] - c#

This question already has answers here:
Random weighted choice
(12 answers)
Closed 6 years ago.
I am looking to pick 1 person from a list of people where each item in the list has a certain "weighting" to it. Let's assume the Person class has the necessary constructor.
public class Person {
public string Name { get; set; }
public float Weighting { get; set; }
}
public List<Person> People = new List<Person>();
People.Add(new Person("Tim", 1.0));
People.Add(new Person("John", 2.0));
People.Add(new Person("Michael", 4.0));
Now, I want to choose a person randomly from this list. But on average I want to pick Michael 4 times more often than Tim. And I want to pick John half (2/4) as often as Michael. And of course, I want to pick Michael double as often as John.
Does that make any sense?
I already have code in place to select people based on percentages. Wouldn't it work if I just multiplied the %chance of them by the weighting provided in this example?
Also, my current system only works with chances of up to 100%, nothing above. Any advice on how to overcome this limitation? I would probably have to scale every chance according to the largest factor in the list?
public static bool Hit(double pct) {
if (rand == null)
rand = new Random();
return rand.NextDouble() * 100 <= pct;
}
Or am I missing something?

You are not keeping percentages to start with.
I would create a random number in the range of 0 to the sum of all Weighting values. Then just walk over the list and check if the value is lower than the current plus own weight.
So:
float r = YourRandomNumber(People.Sum(p => p.Weighting));
float sum = 0;
foreach (Person p in People)
{
if (r < (sum + p.Weighting))
{
// hit
// save the person somewhere
break;
}
else
{
sum += p.Weighting;
}
}

Related

Is there a standard algorithm to balance overlapping objects into buckets?

I have a bunch of users, with a given start and end time, e.g.:
{ Name = "Peter", StartTime = "10:30", EndTime = "11:00" },
{ Name = "Dana", StartTime = "11:00", EndTime = "12:30" },
{ Name = "Raymond", StartTime = "10:30", EndTime = "14:00" },
{ Name = "Egon", StartTime = "12:00", EndTime = "13:00" },
{ Name = "Winston", StartTime = "10:00", EndTime = "12:00" }
I want to put them into buckets, based on times that they overlap (based on a configurable threshold, e.g., they need to overlap at least half an hour). I want buckets to be ideally 4 items big, but any range from 2-5 is acceptable.
In the example above, no 4 people match, so I'd have a bucket of 3 (Peter, Raymond, Winston) and one of 2 (Dana, Egon).
I've prototyped an algorithm that seems to rely on chance rather than science:
Order the List by StartTime
Create an empty bucket
Pick the first user from the List
Check that user against all users in the bucket
If that user overlaps with everyone in the bucket, put that person in it and remove it from the list
If the bucket has the ideal size (4) or if I'm looping and checking the same user more than three times, close the bucket and create a new, empty one
This works well for the first few buckets, but leads to buckets with only 2 people that could be combined better.
I could change the algorithm to remove all ideal buckets from the list and reshuffle and try some more, but I feel that this should be a common problem - it's like shift assignments for workers, or maybe the knapsack problem.
Does anyone know a standard algorithm for this type of problem?
(Tagged combinatorics because I think this is the area of math it applies, correct me if wrong)
tl;dr: dynamic programming for the win (O(sort(n)) time).
First, note that bucketing contiguously in start-time order is fine.
Proposition (defragging): Let a, b, c, d be distinct users such that StartTime(a) ≤ StartTime(b) ≤ StartTime(c) ≤ StartTime(d). If X and Y are valid buckets such that a, c ∈ X and b, d ∈ Y, then X - {c} ∪ {b} and Y - {a} ∪ {d} are valid buckets as well.
I only know how to prove this by tedious case analysis (omitted).
The upshot is, you can pretend as though you're breaking a paragraph into lines, where the “paragraph“ is the list of users in start-time order, and each “line” is a bucket. There is an algorithm due to Knuth and Plass for line breaking optimally, where the penalty for a given line is a more or less arbitrary function. You could make buckets of 4 cost 0, buckets of 3 cost 1, buckets of 2 cost 2, and buckets of 1 cost 100, for example.
Based on your problem I would probably do something like first making a class called "Person" pr something like that. Give this class attributes of "Name", "Start Time", and "End Time".
class Person
{
public string name;
public double start_time;
public double end_time;
}
Then put them in some ordered list of type Person. (Also I'm currently storing the times as doubles. You can convert them back to times simply by multiplying any decimal part of the time that I have by 60/100.
Afterwords, you make a list of Buckets to which you can add new Buckets if need be. You then sort the list based on a threshold which you define and if two objects being compared overlap based on that threshold, then those both go into that Bucket. If they don't overlap, then move to the next Bucket, if there is an overlap there then add it to that Bucket, etc until you reach the last Bucket. If you have gone through all the Buckets and there is still no overlap, then create a new Bucket for that object.
class MainFunc
{
static void Main(string[] args)
{
//makes a function to check if 2 values overlap over a threshold
//st stands for start time and et stands for end time
public bool IsWithinThreshold(double st1, double st2, double et1, double et2)
{
double threshold = .5;
if(st1 >= et2 || st2 >= et1)
{
return false
}
else
{
if(st1+threshold <= et2 && st1+threshold <= et1 || st2+threshold <= et1 && st2+threshold <=et2)
{
return true;
}
else
{
return false;
}
}
}
// makes objects of type Person with attributes of name, start time, and end time
Person Peter = new Person();
Peter.name = "Peter"
Peter.start_time = 10.5
Peter.end_time = 11.0
Person Dana = new Person();
Dana.name = "Dana"
Peter.start_time = 11.0
Peter.end_time = 12.5
Person Raymond = new Person();
Raymond.name = "Raymond"
Raymond.start_time = 10.5
Raymond.end_time = 14.0
Person Egon = new Person();
Egon.name = "Egon"
Egon.start_time = 12.0
Egon.end_time = 13.0
Person Winston = new Person();
Winston.name = "Winston"
Winston.start_time = 10.0
Winston.end_time = 12.0
//puts objects of type Person into an unordered list
List<Person> people = new List<Person>();
people.Add(Peter);
people.Add(Dana);
people.Add(Raymond);
people.Add(Egon);
people.Add(Winston);
//sets up a list of lists of People (Buckets in our case)
List<List<Person>> Buckets = new List<List<Person>>;
//sets up an intial Bucket and adds the first person on the list to it
List<Person> Bucketinitial = new List<Person>;
Bucketinitial.add(people[0]);
for(var i = 1; i < people.Count; i++)
{
for(var j = 0; j< Buckets.count; j++)
{
//sets a checker to make sure that all objects in a given Bucket overlap with the person we are checking
bool overlap = true;
for(var k = 0; k< Buckets[k].count; k++)
{
overlap = overlap & IsWithinThreshold(people[i].start_time,Buckets[j][k].start_time,people[i].end_time,Buckets[j][k].end_time)
}
if (overlap == true)
{
Buckets[j].add(people[i])
}
//if all the objects in a bucket don't overlap with the person...
//... make a new Bucket with that person
else
{
List<Person> NewBucket = new List<Person>;
NewBucket.add(people[i]);
Buckets.add(NewBucket);
}
}
}
}
}
Then just add a print command to print out the name attributes of each object within each Bucket of the buckets list. Please leave a comment if you have any questions/concerns, cheers.
You can modify your algorithm to incorporate an interval tree to speed up the search
Order the List by StartTime
Add items to an interval tree
Create an empty bucket
Pick the first item from the List
Find earliest items within threshold time of first item that fill up a bucket using an interval search of the interval tree
Remove bucketed items from list
If list is empty stop otherwise go to step 4
Basically you're moving from left to right in interval steps (given by your configurable threshold) using the interval tree to quickly query for closest items as you move.

Dealing with ranges of numbers

I need to return a value that corresponds to some weighting for a calculation based on age.
Here's the age ranges and weights:-
21-30: 1.2
31-40: 1.8
41-50: 1.9
and so on (there's no real pattern)
The program needs to take an age as input and then return the weighting (e.g. if age = 35, the return value would be 1.8.
How would this be best achieved?
I could use switch but I'm not sure if it's the best way around this. Is there some other construct or technique I could apply in C# to achieve this that would be more effective and portable/scalable should the weightings change?
One other thing - I can't use a database to store any weightings data - just adding this for info.
Create a Dictionary to define your age ranges and weights. The key should be a Tuple with the min-age and max-age for this range and the value should be your weight:
Dictionary<Tuple<int,int>, double> // minAge, MaxAge -> weight
Then you may loop through all keys to find your weight.
You may create this dictionary from the contents of a table, a XML file, a database, whatever.
We have done something similar in a system here, and we use the concept of storing in a database table the weighting and the lower threshold. Thus all we need to do is to find the record with the highest lower threshold less than the value entered and read the weight.
This simplifies the process and allows for editing and adding and removing the values.
No, as far as I know, there is nothing like a range-structure.
You could use a switch, either in this way, if the ranges are always from x1 to y0
switch((value-1) / 10)
{
case 1: ... break;
case 2: ... break;
}
or, if needed:
switch(value)
{
case 11:
case 12:
case 20: ... break;
case 21: ...
}
Depending on the number of groups you need, you could do checks like
if(value > 10 && <= 20) ...
I don't know any more elegant approach.
If the ranges do not overlap then the best thing to use would be a SortedList where the key is the upper value of the range, and the value is the weight. Additionally you can make the weigth nullable to distinguish the case of not finding an entry. I've added the entry of {20, null} so that if the age is <= 20 you'll get null instead of 1.2.
var rangedWeights = new SortedList<int, float?>
{
{ 20, null }
{ 30, 1.2f },
{ 40, 1.8f },
{ 50, 1.9f }
};
int age = 44;
float? weight = null;
foreach (var kvp in rangedWeights)
{
if (age <= kvp.Key)
{
weight = kvp.Value;
break;
}
}
You can dynamically add new entries and still be sure they are sorted.
rangedWeights.Add(60, 2.1f);
You can use dictionary with composite key, which you will use to check the user input and get the respected value for the matching key.
Here is example;
Dictionary<Tuple<int, int>, double> t = new Dictionary<Tuple<int, int>, double>();
t.Add(new Tuple<int,int>(11,20),1f);
t.Add(new Tuple<int, int>(21, 30), 2f);
t.Add(new Tuple<int, int>(31, 40), 3f);
int weight = 34;
double rr = (from d in t where d.Key.Item1 <= weight && d.Key.Item2 >= weight select d.Value).FirstOrDefault();
Console.WriteLine(rr); // rr will print 3.0
Hope it helps..!!
If the ranges are consecutive as they appear to be in your example, you only need the upper value and the ranges sorted in order to be able to query it, so you can do something like this:
public class RangeEntry
{
public RangeEntry(int upperValue, float weight)
{
UpperValue = uperValue;
Weight = weight;
}
public int UpperValue { get; set; }
public float Weight { get; set; }
}
public class RangeWeights
{
private List<RangeEntry> _ranges = new List<RangeEntry>
{
new RangeEntry(30, 1.2f),
new RangeEntry(40, 1.8f),
new RangeEntry(50, 1.9f),
}
public float GetWeight(int value)
{
// If you pre-sort the ranges then you won't need the below OrderBy
foreach (var r in _ranges.OrderBy(o => o.UpperValue))
{
if (value <= r.UpperValue)
return r.Weight;
}
// Range not found, do whatever you want here
throw new InvalidOperationException("value not within in any valid range");
}
}
The value of this approach is that adding a new range means adding just 1 line of code in the instantiation of the ranges.
If you are looking for a shorter way of writing it, I suggest you use ?: operator.
double getWeight(int age){
return (age <= 30) ? 1.2 :
(age <= 40) ? 1.8 : 1.9;
}
It will be the same as using switch. Only shorter way of putting it. You can replace the digits and weights with variables if you don't want them to be hard coded.
It's not very clear what is the structure of the age ranges and weights data,
but I would probably do something like this:
Class AgeRangeAndWeight {
int FromAge {get;set;}
int ToAge {get;set;}
double Weight {get;set;}
}
Class AgeRangeAndWeight Collection() : List<AgeRangeAndWeight> {
AgeRangeAndWeight FindByAge(int age) {
foreach(AgeRangeAndWeight araw in this) {
if(age >= araw.FromAge && age <= araw.ToAge) {
return araw;
}
}
return null;
}
}
then all you have to do is call the FindByAge method. remember to check that it doesn't return null.
Update
Five years after I've posted this answer it was upvoted.
Today I wouldn't recommend inheriting a List<T> - but simply use it's Find method like this:
var list = new List<AgeRangeAndWeight>() {/* populate here */}
var age = 35;
var ageRangeAndWeight = list.Find(a => d.FromAge >= age && d.ToAge <= age);

Game Design/theory, Loot Drop Chance/Spawn Rate

I have a very specific and long-winded question for you all. This question is both about programming and game-theory. I recently added spawnable ore to my Turn Based Strategy Game: http://imgur.com/gallery/0F5D5Ij (For those of you that look please forgive the development textures).
Now, onto the enigma that I have been contemplating. In my game, ore is generated each time a new map is created. 0-8 ore nodes are generated per level-creation. I already have this working; except it only generates "Emeraldite" at this point, which brings me to my question.
How would I, the programmer, make it so nodes have specific rarity? Consider this short mockup which is not actually game data:
(Pseudo Chances that a node will be one of the following)
Bloodstone 1 in 100
Default(Empty Node) 1 in 10
Copper 1 in 15
Emeraldite 1 in 35
Gold 1 in 50
Heronite 1 in 60
Platinum 1 in 60
Shadownite 1 in 75
Silver 1 in 35
Soranite 1 in 1000
Umbrarite 1 in 1000
Cobalt 1 in 75
Iron 1 in 15
I want to make it so that a generated node could be, theoretically, any of the above, however, with the odds also considered. I hope that question is clear enough. I have been trying to wrap my head around this, and even tried to write out a few if statements with randoms, however, I keep coming up empty handed.
Basically, I just want you guys to see my issue, and hopefully provide me with some insight on how I could approach this in a dynamic kind of way.
If any clarification is needed, please ask; sorry again if this was convoluted.
(I am adding C# as a tag only because that is the language I am using for this project)
I'd first represent the probability of each loot type as a simple number.
A probability in pure mathematics is conventionally expressed as a floating point number in the range 0 to 1, but for efficiency, you can use integers in any (large enough) range (each value is the 0-1 value multiplied by the maximum (which I'm calling MaxProbability here)).
e.g. Bloodstone (1 in 100) is 1/100 = 0.01, or MaxProbability * (1/100).
Copper (1 in 15) is 1/15 = 0.06667, or MaxProbability * (1/15).
I'm assuming that 'Default (Empty Node)' means the probability of none of the others.
In this case, the simplest way is not to define it - you get it if none of the others are chosen.
If 'Default' was included, the sum of all these probabilities would be 1 (i.e. 100%) (or MaxProbability, if using integers).
The 1/10 probability of 'Default' in your example is actually a contradiction because the total of all those probabilities is not 1 (it's 0.38247619 - the sum of the probability as calculated in my examples above).
Then you would choose a random number in the range 0 to 1 (or MaxProbability if using integers), and the chosen loot type is the first one in the list such that the sum of the probabilities of it and all previous ones ("cumulative probability") is greater than the random number.
e.g.
MaxProbability = 1000 (I'm using this to make it easy to read).
(For accurate probabilities, you could use 0x7FFFFFFF).
Type Probability Cumulative
---- ----------- ----------
Bloodstone 10 10 (0..9 yield Bloodstone)
Copper 67 77 (10+67) (10..76 yield Copper)
Emeraldite 29 105 (77+29)
Gold 20 125 etc.
Heronite 17 142
Platinum 17 159
Shadownite 13 172
Silver 29 200
Soranite 1 201
Umbrarite 1 202
Cobalt 13 216
Iron 67 282
Default (Empty Node) 7175 1000 (anything else)
e.g. If your random number in the range 0 to 999 (inclusive) was 184 (or anything in the range 172 to 199), you would choose "Silver" (the first one with cumulative probability greater than this).
You could hold the cumulative probabilities in an array and loop through it until you find one higher than the random number, or reach the end.
The order of the list does not matter.
You chose a random number only once per instance.
Including 'Default (Empty Node)' in the list means that the last cumulative probability will always be MaxProbability and the loop that searches it would never go past the end. (Alternatively, 'Default' can be omitted, and you choose it if the loop reaches the end of the list.)
Note that choosing a random number for each one in turn, e.g. a 1/10 chance of 'Bloodstone', then a 1/15 chance of Copper if not Bloodstone, skews the probabilities towards the earlier items:
The actual probability of Copper would be (1/15) * (1 - (1/10)) - 10% less than 1/15.
Here's code to do it (the actual choosing is 5 statements - in the method Choose ).
using System;
namespace ConsoleApplication1
{
class LootChooser
{
/// <summary>
/// Choose a random loot type.
/// </summary>
public LootType Choose()
{
LootType lootType = 0; // start at first one
int randomValue = _rnd.Next(MaxProbability);
while (_lootProbabilites[(int)lootType] <= randomValue)
{
lootType++; // next loot type
}
return lootType;
}
/// <summary>
/// The loot types.
/// </summary>
public enum LootType
{
Bloodstone, Copper, Emeraldite, Gold, Heronite, Platinum,
Shadownite, Silver, Soranite, Umbrarite, Cobalt, Iron, Default
};
/// <summary>
/// Cumulative probabilities - each entry corresponds to the member of LootType in the corresponding position.
/// </summary>
protected int[] _lootProbabilites = new int[]
{
10, 77, 105, 125, 142, 159, 172, 200, 201, 202, 216, 282, // (from the table in the answer - I used a spreadsheet to generate these)
MaxProbability
};
/// <summary>
/// The range of the probability values (dividing a value in _lootProbabilites by this would give a probability in the range 0..1).
/// </summary>
protected const int MaxProbability = 1000;
protected Random _rnd = new Random((int)(DateTime.Now.Ticks & 0x7FFFFFFF));
/// <summary>
/// Simple 'main' to demonstrate.
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
var chooser = new LootChooser();
for(int n=0; n < 100; n++)
Console.Out.WriteLine(chooser.Choose());
}
}
}
You could rewrite all chances so that they use the same divisor (e.g. 1000), your chances then become
Bloodstone 10 in 1000
Default(Empty Node) 100 in 1000
Gold 20 in 1000
Next, create an array of a 1000 elements, and fill it with
10 Bloodstone elements,
100 Empty elements,
20 Gold elements,
etc.
Finally, generate a random number between 0 and 1000, and use that as the index into the element array, this will give
you your random element.
You might have to play with the chances a bit, since you'll probably want all 1000 array elements to be filled, but this is the general idea.
edit its not the most efficient implementation (at least in terms of memory usage, its running time should be good), but I chose this since it allows for a concise explanation that doesn't require a whole lot of math.
First of all, specifying the the default-empty node's probability is unnecessary. The other probabilities should be defined in such a way, that the empty node is created if no other type is created.
How to do this and ensure the generation probabiltiies are equal to those you specified? In short:
convert the probabilities to a floating point (it's a value with a common divisor of 1)
sum all probabilities and check if they are < 1
write a class which will store the all the probabilities
write a function which will get a random node based on those probabilities
For your example:
Bloodstone 1 in 100 = 0.01
Copper 1 in 15 ~= 0.07
Emeraldite 1 in 35 ~= 0.03
Gold 1 in 50 = 0.02
Default = 0.87
Now the class can be implemented in at least two ways. My option consumes much memory, does the computations once, but it also rounds the probability values which may introduce some error. Note, that the error depends on the arrSize variable - the larger it is, the smaller the error.
The other option is as in Bogusz's answer. It is more precise, but required more operations per each generated element.
Option suggested by Thomas requires a lot of repeatable code for each option hence is not versatile. Shellshock's answer will have invalid effective probabilities.
Astrotrain's idea to force yourself to use the same divisor is virtually the same as my own, though the implementation would be slightly different.
Here's a sample implementation of my idea (in java, but should be ported very easily):
public class NodeEntry {
String name;
double probability;
public NodeEntry(String name, double probability) {
super();
this.name = name;
this.probability = probability;
}
public NodeEntry(String name, int howMany, int inHowMany) {
this.name = name;
this.probability = 1.0 * howMany / inHowMany;
}
public final String getName() {
return name;
}
public final void setName(String name) {
this.name = name;
}
public final double getProbability() {
return probability;
}
public final void setProbability(double probability) {
this.probability = probability;
}
#Override
public String toString() {
return name+"("+probability+")";
}
static final NodeEntry defaultNode = new NodeEntry("default", 0);
public static final NodeEntry getDefaultNode() {
return defaultNode;
}
}
public class NodeGen {
List<NodeEntry> nodeDefinitions = new LinkedList<NodeEntry>();
public NodeGen() {
}
public boolean addNode(NodeEntry e) {
return nodeDefinitions.add(e);
}
public boolean addAllNodes(Collection<? extends NodeEntry> c) {
return nodeDefinitions.addAll(c);
}
static final int arrSize = 10000;
NodeEntry randSource[] = new NodeEntry[arrSize];
public void compile() {
checkProbSum();
int offset = 0;
for (NodeEntry ne: nodeDefinitions) {
int amount = (int) (ne.getProbability() * arrSize);
for (int a=0; a<amount;a++) {
randSource[a+offset] = ne;
}
offset+=amount;
}
while (offset<arrSize) {
randSource[offset] = NodeEntry.getDefaultNode();
offset++;
}
}
Random gen = new Random();
public NodeEntry getRandomNode() {
return randSource[gen.nextInt(arrSize)];
}
private void checkProbSum() {
double sum = 0;
for (NodeEntry ne: nodeDefinitions) {
sum+=ne.getProbability();
}
if (sum >1) {
throw new RuntimeException("nodes probability > 1");
}
}
public static void main(String[] args) {
NodeGen ng = new NodeGen();
ng.addNode(new NodeEntry("Test 1", 0.1));
ng.addNode(new NodeEntry("Test 2", 0.2));
ng.addNode(new NodeEntry("Test 3", 0.2));
ng.compile();
Map<NodeEntry, Integer> resCount = new HashMap<NodeEntry, Integer>();
int generations = 10000;
for (int a=0; a<generations; a++) {
NodeEntry node = ng.getRandomNode();
Integer val = resCount.get(node);
if (val == null) {
resCount.put(node, new Integer(1));
} else {
resCount.put(node, new Integer(val+1));
}
}
for (Map.Entry<NodeEntry, Integer> entry: resCount.entrySet()) {
System.out.println(entry.getKey()+": "+entry.getValue()+" ("+(100.0*entry.getValue()/generations)+"%)");
}
}
}
This makes sure the probabilities are actually uniform. If you checked for the first node spawn, then the other, then the other - you would get wrong results: nodes checked first would have increased probability.
Sample run:
Test 2(0.2): 1975 (19.75%)
Test 1(0.1): 1042 (10.42%)
Test 3(0.2): 1981 (19.81%)
default(0.0): 5002 (50.02%)
I think that it is easy to understand how it works.
(Cobalt, 20: means 1 of 20 -> 5%)
Dictionary<string, double> ore = new Dictionary<string, double>();
Random random = new Random();
private void AddOre(string Name, double Value)
{
ore.Add(Name, 1.0 / Value);
}
private string GetOreType()
{
double probSum = 0;
double rand = random.NextDouble();
foreach (var pair in ore)
{
probSum += pair.Value;
if (probSum >= rand)
return pair.Key;
}
return "Normal Ore"; //Reaches this point only if an error occurs.
}
private void Action()
{
AddOre("Cobalt", 20);
AddOre("Stone", 10);
AddOre("Iron", 100);
AddOre("GreenOre", 300);
//Add Common ore and sort Dictionary
AddOre("Common ore", 1 / (1 - ore.Values.Sum()));
ore = ore.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
Console.WriteLine(GetOreType());
}
Edit:
I add section "Add Common ore and sort Dictionary".
I recently had to do something similar, and I ended up with this generic "spawn generator".
public interface ISpawnable : ICloneable
{
int OneInThousandProbability { get; }
}
public class SpawnGenerator<T> where T : ISpawnable
{
private class SpawnableWrapper
{
readonly T spawnable;
readonly int minThreshold;
readonly int maxThreshold;
public SpawnableWrapper(T spawnable, int minThreshold)
{
this.spawnable = spawnable;
this.minThreshold = minThreshold;
this.maxThreshold = this.minThreshold + spawnable.OneInThousandProbability;
}
public T Spawnable { get { return this.spawnable; } }
public int MinThreshold { get { return this.minThreshold; } }
public int MaxThreshold { get { return this.maxThreshold; } }
}
private ICollection<SpawnableWrapper> spawnableEntities;
private Random r;
public SpawnGenerator(IEnumerable<T> objects, int seed)
{
Debug.Assert(objects != null);
r = new Random(seed);
var cumulativeProbability = 0;
spawnableEntities = new List<SpawnableWrapper>();
foreach (var o in objects)
{
var spawnable = new SpawnableWrapper(o, cumulativeProbability);
cumulativeProbability = spawnable.MaxThreshold;
spawnableEntities.Add(spawnable);
}
Debug.Assert(cumulativeProbability <= 1000);
}
//Note that it can spawn null (no spawn) if probabilities dont add up to 1000
public T Spawn()
{
var i = r.Next(0, 1000);
var retVal = (from s in this.spawnableEntities
where (s.MaxThreshold > i && s.MinThreshold <= i)
select s.Spawnable).FirstOrDefault();
return retVal != null ? (T)retVal.Clone() : retVal;
}
}
And you'd use it like:
public class Gem : ISpawnable
{
readonly string color;
readonly int oneInThousandProbability;
public Gem(string color, int oneInThousandProbability)
{
this.color = color;
this.oneInThousandProbability = oneInThousandProbability;
}
public string Color { get { return this.color; } }
public int OneInThousandProbability
{
get
{
return this.oneInThousandProbability;
}
}
public object Clone()
{
return new Gem(this.color, this.oneInThousandProbability);
}
}
var RedGem = new Gem("Red", 250);
var GreenGem = new Gem("Green", 400);
var BlueGem = new Gem("Blue", 100);
var PurpleGem = new Gem("Purple", 190);
var OrangeGem = new Gem("Orange", 50);
var YellowGem = new Gem("Yellow", 10);
var spawnGenerator = new SpawnGenerator<Gem>(new[] { RedGem, GreenGem, BlueGem, PurpleGem, OrangeGem, YellowGem }, DateTime.Now.Millisecond);
var randomGem = spawnGenerator.Spawn();
Evidently the spawn algorithm was not considered critical code so the overhead of this implementation was of no concern when compared to the ease of use. Spawns were run on world creation and it was easily more than fast enough.
Astrotrain already gave my answer but since I coded it up already I'll post it. Sorry for the syntax, I work mostly in Powershell and that is the context currently in my mind. Consider this psuedo code:
// Define the odds for each loot type
// Description,Freq,Range
LootOddsArray = "Bloodstone",1,100,
"Copper",1,15,
"Emeraldite,"1,35,
"Gold",1,50,
"Heronite",1,60,
"Platinum",1,60,
"Shadownite",1,75,
"Silver",1,35,
"Soranite",1,1000,
"Umbrarite",1,1000,
"Cobalt",1,75,
"Iron",1,15
// Define your lookup table. It should be as big as your largest odds range.
LootLookupArray(1000)
// Fill all the 'default' values with "Nothing"
for (i=0;i<LootLookupArray.length;i++) {
LootOddsArray(i) = "Nothing"
}
// Walk through your various treasures
for (i=0;i<LootOddsArray.length;i++)
// Calculate how often the item will appear in the table based on the odds
// and place that many of the item in random places in the table, not overwriting
// any other loot already in the table
NumOccsPer1000 = Round(LootOddsArray(i).Freq * 1000/LootOddsArray(i).Range)
for (l=0;l<NumOccsPer1000;l++) {
// Find an empty slot for the loot
do
LootIndex = Random(1000)
while (LootLookupArray(LootIndex) != "Nothing")
// Array(Index) is empty, put loot there
LootLookupArray(LootIndex) = LootOddsArray(i).Description
}
}
// Roll for Loot
Loot = LootLookupArray(Random(1000))
Use Random.Next http://msdn.microsoft.com/en-us/library/2dx6wyd4(v=vs.110).aspx:
Random rnd = new Random();
if (rnd.Next(1, 101) == 1)
// spawn Bloodstone
if (rnd.Next(1, 16) == 1)
// spawn Copper
if (rnd.Next(1, 36) == 1)
// spawn Emeraldite
The minimum value should always be 1, the maximum value is the odds of spawning the item + 1 (minValue is inclusive, maxValue is exclusive). Always test the return value for 1, e.g., for Bloodstone there is a 1 in 100 chance that the randomly generated number is 1. Of course, this uses a pseudo random number generator, which should be good enough for a game.
A slightly different approach to Astrotrains idea would be that instead of an array to use if statements . The upside is then that you need less memory, the downside that it will need more cpu time to calc the value of the node.
Thus:
Random rnd = new Random();
var number = rnd.next(1,1000);
if (number >= 1 && number <10)
{
// empty
}
else
{
if (number >= 10 && number <100)
{
// bloodstone
}
else
{
//......
}
}
Also a downside to this variant comapred to the array variant is that this one takes more place codewise at the location where you use it and is more prone to errors / corrections (try to add something inside it you need to update all variants).
Thus this here is metnioned for completeness sake but the array vairant (memory usage aside) is less prone to the problems that the if variant has.

I need an efficient algorithm for best matching

I have, for instance, 10 teams and I need to match them.
The criteria for matching are:
Two teams are matched only one time.
Teams with closest score must be matched (Score is property of Team class, type is double).
It could be iterated 9 (n-1) times max and this is what I try to do, go further as I can.
At the end of each iteration, points of teams increase randomly.
I populated my list with random data.
List<Team> _teams = new List<Team>();
for (int i = 1; i <= 10; i++)
{
_teams.Add(new Team("Team "+i,MyExtensions.RandomNumber(31)));
}
private static readonly Random Rand = new Random();
public static int RandomNumber(int max)
{
return Rand.Next(max);
}
My team class:
public class Team
{
private string _name;
private double _score;
private List<Team> _matchedTeams;
private Team _currentMatch;
public Team CurrentMatch
{
get { return _currentMatch; }
set { _currentMatch = value; }
}
public List<Team> MatchedList
{
get { return _matchedTeams; }
set { _matchedTeams = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public double Score
{
get { return _score; }
set { _score = value; }
}
public Team(string name, double score)
{
_matchedTeams = new List<Team>();
_name = name;
_score = score;
}
public override string ToString()
{
return _name + " - (" + _score + ")";
}
}
And here's my extension method to get closest match;
public static Team FindClosest(this List<Team> list, Team team)
{
var orderedItems =
list.Where(p => p != team && !p.MatchedList.Contains(team)).OrderBy(x => Math.Abs(x.Score - team.Score));
return orderedItems.FirstOrDefault();
}
var nearestMatch = _teams.FindClosest(_teams.ElementAt(0));
Actually, I am trying to make a bridge game fixture calculator. For each round different teams must be matched and matched teams should be equal (or close) strength. But for next rounds, because of team match uniqueness is first criterion and we must arrange a match for people, we must bend the rules of score (point) closeness.
So it will result something like that;
Algorithm runs...
First Round;
Team1-Team2 ; Team3-Team4 ; Team5-Team6 .....
scores are updated by user at the end of the round1
Algorithm runs...
Second Round;
Team1-Team7 ; Team3-Team8 ; Team4-Team9 .....
.....
scores are updated by user at the end of the round8
Algorithm runs...
Ninth Round;
Team1-Team9; Team2-Team7; Team3-Team5 .....
scores are updated by user at the end of the round9
Algorithm runs...
No more match.
I prefer an algorithm like backtracking instead of brute force or random trial-and-error.
Can you suggest me an algorithm, a method to solve my problem?
Any help would be much appreciated.
Sort the teams by number of points, and then take pairs of teams starting from the beginning of the sorted list. Clearly the second-place team is the only possible team that is closest in points to the first-place team. If you don't pair the first-place team with the second-place team, but instead pair the second-place team with the third-place team, then the best match for the first-place team will be the fourth-place team. Pairing teams out of order in the sorted list will only increase the points disparity.
To illustrate, assume that there are 4 teams named A,B,C,D. The difference in points between the teams is d1,d2,d3
A B C D
d1 d2 d3
If the pairings are AB and CD, then the set of differences is { d1, d3 }.
If the pairings are BC and AD, then the set of differences is { d2, (d1+d2+d3) }.
I don't know of any metric where the second set is preferable to the first.
You need to explicitly state an objective function you are trying to optimize. If it's to find a minimum of sum of absolute values of differences between points for pairs, then I agree with another answer, you simply sort the teams by points and then pair them off two teams at a time from left to right and you can prove this gives the optimal pairing. If your objective function is different, then it may be harder to optimize, maybe even NP-hard depending on what your objective function is. But the point is we can't help if you if you just say "teams closest in points must be paired together" because it's possible to have a team with two neighbors on opposite sides that are equally close in points, such that the other neighbors of the two neighbors are far away, and then you can only pair up the team with one of its close neighbors.
You will need this course.
The dynamic programming may be too slow. Maybe the Local search, or other method would be more efficient. You could read the videos about Local Search first.

Help with maths/coding on possible combinations of a set to make up a total - C#

I have a coding/maths problem that I need help translating into C#. It's a poker chip calculator that takes in the BuyIn, the number of players and the total amount of chips for each colour (there are x amount of colours) and their value.
It then shows you every possible combination of chips per person to equal the Buy In. The user can then pick the chipset distribution they would like to use. It's best illustrated with a simple example.
BuyIn: $10
Number of Players: 1
10 Red Chips, $1 value
10 Blue Chips, $2 value
10 Green Chips, $5 value
So, the possible combinations are:
R/B/G
10/0/0
8/1/0
6/2/0
5/0/1
4/3/0
2/4/0
1/2/1
etc.
I have spent a lot of time trying to come up with an algorithm in C#/.NET to work this out. I am stumbling on the variable factor - there's usually only 3 or 4 different chips colours in a set, but there could be any amount. If you have more than one player than you have to count up until TotalChips / NumberOfPlayers.
I started off with a loop through all the chips and then looping from 0 up to NumberOfChips for that colour. And this is pretty much where I have spent the last 4 hours... how do I write the code to loop through x amount of chips and check the value of the sum of the chips and add it to a collection if it equals the BuyIn? I need to change my approach radically methinks...
Can anyone put me on the right track on how to solve this please? Pseudo code would work - thank you for any advice!
The below is my attempt so far - it's hopeless (and wont compile, just an example to show you my thought process so far) - Might be better not to look at it as it might biased you on a solution...
private void SplitChips(List<ChipSuggestion> suggestions)
{
decimal valueRequired = (decimal)txtBuyIn.Value;
decimal checkTotal = 0;
ChipSuggestion suggestion;
//loop through each colour
foreach (Chip chip in (PagedCollectionView)gridChips.ItemsSource)
{
//for each value, loop through them all again
foreach (Chip currentChip in (PagedCollectionView)gridChips.ItemsSource)
{
//start at 0 and go all the way up
for (int i = 0; i < chip.TotalChipsInChipset; i++)
{
checkTotal = currentChip.ChipValue * i;
//if it is greater than than ignore and stop
if (checkTotal > valueRequired)
{
break;
}
else
{
//if it is equal to then this is a match
if (checkTotal == valueRequired)
{
suggestion = new ChipSuggestion();
suggestion.SuggestionName = "Suggestion";
chipRed.NumberPerPlayer = i;
suggestion.Chips.Add(chipRed);
chipBlue.NumberPerPlayer = y;
suggestion.Chips.Add(chipBlue);
chipGreen.NumberPerPlayer = 0;
suggestion.Chips.Add(chipGreen);
//add this to the Suggestion
suggestions.Add(suggestion);
break;
}
}
}
}
}
}
Here's an implementation that reads the number of chips, the chips (their worth and amount) and the buyin and displays the results in your example format. I have explained it through comments, let me know if you have any questions.
class Test
{
static int buyIn;
static int numChips;
static List<int> chips = new List<int>(); // chips[i] = value of chips of color i
static List<int> amountOfChips = new List<int>(); // amountOfChips[i] = number of chips of color i
static void generateSolutions(int sum, int[] solutions, int last)
{
if (sum > buyIn) // our sum is too big, return
return;
if (sum == buyIn) // our sum is just right, print the solution
{
for (int i = 0; i < chips.Count; ++i)
Console.Write("{0}/", solutions[i]);
Console.WriteLine();
return; // and return
}
for (int i = last; i < chips.Count; ++i) // try adding another chip with the same value as the one added at the last step.
// this ensures that no duplicate solutions will be generated, since we impose an order of generation
if (amountOfChips[i] != 0)
{
--amountOfChips[i]; // decrease the amount of chips
++solutions[i]; // increase the number of times chip i has been used
generateSolutions(sum + chips[i], solutions, i); // recursive call
++amountOfChips[i]; // (one of) chip i is no longer used
--solutions[i]; // so it's no longer part of the solution either
}
}
static void Main()
{
Console.WriteLine("Enter the buyin:");
buyIn = int.Parse(Console.ReadLine());
Console.WriteLine("Enter the number of chips types:");
numChips = int.Parse(Console.ReadLine());
Console.WriteLine("Enter {0} chips values:", numChips);
for (int i = 0; i < numChips; ++i)
chips.Add(int.Parse(Console.ReadLine()));
Console.WriteLine("Enter {0} chips amounts:", numChips);
for (int i = 0; i < numChips; ++i)
amountOfChips.Add(int.Parse(Console.ReadLine()));
int[] solutions = new int[numChips];
generateSolutions(0, solutions, 0);
}
}
Enter the buyin:
10
Enter the number of chips types:
3
Enter 3 chips values:
1
2
5
Enter 3 chips amounts:
10
10
10
10/0/0/
8/1/0/
6/2/0/
5/0/1/
4/3/0/
3/1/1/
2/4/0/
1/2/1/
0/5/0/
0/0/2/
Break the problem down recursively by the number of kinds of chips.
For the base case, how many ways are there to make an $X buy-in with zero chips? If X is zero, there is one way: no chips. If X is more than zero, there are no ways to do it.
Now we need to solve the problem for N kinds of chips, given the solution for N - 1. We can take one kind of chip, and consider every possible number of that chip up to the buy-in. For example, if the chip is $2, and the buy-in is $5, try using 0, 1, or 2 of them. For each of these tries, we have to use only the remaining N - 1 chips to make up the remaining value. We can solve that by doing a recursive call, and then adding our current chip to each solution it returns.
private static IEnumerable<IEnumerable<Tuple<Chip, int>>> GetAllChipSuggestions(List<Chip> chips, int players, int totalValue)
{
return GetAllChipSuggestions(chips, players, totalValue, 0);
}
private static IEnumerable<IEnumerable<Tuple<Chip, int>>> GetAllChipSuggestions(List<Chip> chips, int players, int totalValue, int firstChipIndex)
{
if (firstChipIndex == chips.Count)
{
// Base case: we have no chip types remaining
if (totalValue == 0)
{
// One way to make 0 with no chip types
return new[] { Enumerable.Empty<Tuple<Chip, int>>() };
}
else
{
// No ways to make more than 0 with no chip types
return Enumerable.Empty<IEnumerable<Tuple<Chip, int>>>();
}
}
else
{
// Recursive case: try each possible number of this chip type
var allSuggestions = new List<IEnumerable<Tuple<Chip, int>>>();
var currentChip = chips[firstChipIndex];
var maxChips = Math.Min(currentChip.TotalChipsInChipset / players, totalValue / currentChip.ChipValue);
for (var chipCount = 0; chipCount <= maxChips; chipCount++)
{
var currentChipSuggestion = new[] { Tuple.Create(currentChip, chipCount) };
var remainingValue = totalValue - currentChip.ChipValue * chipCount;
// Get all combinations of chips after this one that make up the rest of the value
foreach (var suggestion in GetAllChipSuggestions(chips, players, remainingValue, firstChipIndex + 1))
{
allSuggestions.Add(suggestion.Concat(currentChipSuggestion));
}
}
return allSuggestions;
}
}
For some large combinations this is propably not solvable in finite time.
(It is a NP problem)
http://en.wikipedia.org/wiki/Knapsack_problem
There are also links with Code? that could help you.
Hope this helps a bit.

Categories