(这可能不是编写一个大小合适的辅助方法,但无论如何,我想弄清楚)
说我有一个清单:
{1,3,6}
我想从该列表中获取一个随机项,但我希望它能直接(概率方面)与项目的值进行加权。因此,如果您运行100,000次,1
将被选中约10,000次,3
将被选中约30,000次,而6
则被选中60,000次。
我可以通过创建这样的范围来编写辅助方法:
{1,3,6}
Generate random number between 1(inclusive) and 11(exclusive) (sum of list)
if (number == 0)
{
//1
}
else if (number > 0 && number < 4)
{
//3
}
else
{
//6
}
虽然这个特定的例子相当简单,但我经常使用大型列表并且它们总是不同,所以它会更复杂一些。虽然我能做到,但我很好奇是否有更简单的方法。
答案 0 :(得分:5)
你已经有了基本的想法 - 总和权重(发生与你的值相同)并在该范围内取一个随机数 - 尽管我使用0作为下限,和作为独占上限。然后你只需要通过列表找出哪个值对应于...从列表的开头开始,并继续检查随机数是否小于当前项的权重:如果是,那就是选中的项目。如果不是,则从随机数中减去权重,然后继续。
无可否认,这是一种O(N)算法。如果你需要多次从同一个列表中取一个随机数,你可以建立一个累积的权重列表,然后进行二元搜索,找出哪个索引对应哪个随机数......但是我首先坚持使用相对简单的方法。
我没有测试过它,但它会是这样的:
// Note: this will iterate over the sequence twice. It's expected not to change
// between iterations!
// The Random parameter is so that you can use a single instance multiple times.
// See http://csharpindepth.com/Articles/Chapter12/Random.aspx
int PickRandomWeightedElement(IEnumerable<int> sequence, Random random)
{
int totalWeight = sequence.Sum();
int weightedPick = random.Next(totalWeight);
foreach (var item in sequence)
{
if (weightedPick < item)
{
return item;
}
weightedPick -= item;
}
throw new InvalidOperationException("List must have changed...");
}
如果你需要将项目与权重分开,你可以选择两个参数(一个用于权重,一个用于项目)或一个类型为IEnumerable<Tuple<T, int>>
的参数,其中每个元组是一个项目/权重对
答案 1 :(得分:2)
我会让统计数据和概率通过多次添加相同的元素来实现。这样你就会扭曲统计数据。随着时间的推移,您将获得您正在寻找的发行版
{1,3,3,3,6,6,6,6,6,6}
答案 2 :(得分:1)
还有一个尝试:)
public static object GetRandom(this IList list, List<int> weights){
var sum = weights.Sum();
var r = new Random().Next(1,sum);
var w = 0;
var i = -1;
while(w <= r){
i++;
w+=weights[i];
}
return list[i];
}
如果您想优化speen和randomness,您可以预先计算权重和重新使用Random
实例并将它们作为参数传递。甚至可以从权重列表中格式化累加和的列表,以摆脱循环中的算术运算。
答案 3 :(得分:0)
这是俄罗斯轮盘赌的通用算法。
private static Random random = new Random();
public static T GetRandomItem<T>(Dictionary<T, int> items)
{
int sum = items.Values.Sum();
int cumulatedProbability = random.Next(sum);
foreach(var item in items)
if((cumulatedProbability -= item.Value) < 0)
return item.Key;
throw new InvalidOperationException();
}
使用它:
Dictionary<string, int> items = new Dictionary<string, int> { { "Item 1", 10000 }, { "Item 2", 30000 }, { "Item 3", 60000 } };
var randomItem = GetRandomItem(items);