Tech Qu: Weighted picks from a list.
Write a function that given a list of items and weights return a random item in the list taking the weights into account.
The solution really depends if this is a one shot or a repeated call implementation.
Code Snippet
- static Random rnd = new Random();
- public static int WeightedPick(int[] data)
- {
- var total = 0;
- foreach (var datum in data)
- {
- total += datum;
- }
- var pick = rnd.Next(total);
- var accu = 0;
- for (var i = 0; i < data.Length; i++)
- {
- accu += data[i];
- if (accu >= pick)
- {
- //DEBUG: Console.WriteLine(string.Format("rnd:{0} item:{1}",pick,i));
- return i;
- }
- }
- return data.Length;
- }
The code actually had a bug on the first cut. If the rnd is in the function, values will repeat because multiple calls may happen in the same RANDOM interval for the seed. The solution was to use an external Random so each .Next would work onwards from the original seed.
Testing code is simple:
We repeat the procedure with a given distribution and see what the results are. We would expect 9000, 900 and 100.
Code Snippet
- int[] testcase1 = {900, 90, 10};
- var zCount=0;
- var oCount=0;
- var tCount = 0;
- for(int i=0; i < 10000;i++)
- {
- switch(Questions.WeightedPick(testcase1))
- {
- case 0:
- zCount++;
- break;
- case 1:
- oCount++;
- break;
- case 2:
- tCount++;
- break;
- default:
- throw new DataException("Out of bounds");
- }
- }
- Console.WriteLine(string.Format("0:{0} 1:{1} 2:{2}",zCount,oCount,tCount));
The results (from one run) was:
It works!
Comments
Post a Comment