Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/337.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/webpack/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 从列表中选择随机项目的快速方法-根据项目值的不同概率?_C#_List_Random_Probability - Fatal编程技术网

C# 从列表中选择随机项目的快速方法-根据项目值的不同概率?

C# 从列表中选择随机项目的快速方法-根据项目值的不同概率?,c#,list,random,probability,C#,List,Random,Probability,(除非编写一个大小合适的助手方法,否则这可能是不可能的,但无论如何,我想弄清楚它) 假设我有一张清单: {1,3,6} 我希望从该列表中获得一个随机项,但是,我希望它直接(概率方面)与该项的值进行加权。因此,如果您运行它100000次,1将被选择大约10000次,3将被选择大约30000次,6,60000次 我可以通过如下方式创建范围来编写帮助器方法: {1,3,6} Generate random number between 1(inclusive) and 11(exclusive)

(除非编写一个大小合适的助手方法,否则这可能是不可能的,但无论如何,我想弄清楚它)

假设我有一张清单:

{1,3,6}
我希望从该列表中获得一个随机项,但是,我希望它直接(概率方面)与该项的值进行加权。因此,如果您运行它100000次,
1
将被选择大约10000次,
3
将被选择大约30000次,
6
,60000次

我可以通过如下方式创建范围来编写帮助器方法:

{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
}
{1,3,6}
生成介于1(含)和11(不含)之间的随机数(列表之和)
如果(数字==0)
{
//1
}
否则如果(编号>0&&编号<4)
{
//3
}
其他的
{
//6
}

虽然这个例子相当简单,但我经常处理大型列表,它们总是不同的,所以它会更复杂一些。虽然我可以这样做,但我很好奇是否有更简单的方法。

您已经有了基本的想法-对权重求和(这里的值恰好与您的值相同)并在该范围内取一个随机数-尽管我会使用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...");
}
//注意:这将在序列上迭代两次。预计不会改变
//迭代之间!
//随机参数的作用是,您可以多次使用单个实例。
//看http://csharpindepth.com/Articles/Chapter12/Random.aspx
int PickRandomWeightedElement(IEnumerable序列,随机)
{
int totalWeight=sequence.Sum();
int-weightedPick=random.Next(总重量);
foreach(序列中的var项目)
{
if(加权拾取<项目)
{
退货项目;
}
加权拣选-=项目;
}
抛出新的InvalidOperationException(“列表必须已更改…”);
}

如果需要将项目与权重分开,可以选择两个参数(一个用于权重,一个用于项目)或一个
IEnumerable
类型的参数,其中每个元组都是一个项目/权重对。

这是俄罗斯轮盘赌的通用算法

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();
}
private static Random=new Random();
公共静态T GetRandomItem(字典项)
{
int sum=items.Values.sum();
int累积概率=随机。下一步(总和);
foreach(项目中的var项目)
如果((累积概率-=项目值)<0)
返回项。键;
抛出新的InvalidOperationException();
}
要使用它:

Dictionary<string, int> items = new Dictionary<string, int> { { "Item 1", 10000 }, { "Item 2", 30000 }, { "Item 3", 60000 } };

var randomItem = GetRandomItem(items);
Dictionary items=newdictionary{{“Item 1”,10000},{“Item 2”,30000},{“Item 3”,60000};
var randomItem=GetRandomItem(项目);

我会让统计和概率通过多次添加相同的元素来运行。这样你就会歪曲统计数据。随着时间的推移,您将拥有所需的发行版

{1,3,3,3,6,6,6,6,6,6}
再试穿一次:)

publicstaticobjectgetrandom(此IList列表,列表权重){
var sum=weights.sum();
var r=new Random().Next(1,总和);
var w=0;
var i=-1;

虽然(我不会在这里使用
字典
,我会使用
IEnumerable
或类似的东西。
字典
给人的印象是你想按键查找值,它会阻止你多次使用同一个键。@JonSkeet为什么?在编写代码时,我在
IEnumerable
d之间犹豫ictionary
但我得出的结论是,同一个密钥不应该出现多次(否则,复制的密钥出现的概率等于概率之和)。在我看来,复制的密钥应该出现的概率等于概率之和。而且它不是真正的“密钥”-这只是一个项目。我认为重要的是使用能正确暗示数据含义的类型-无论是
字典
还是
IEnumerable
,我都不这么做。对于小数字来说,这绝对是一个不错的选择。但是,如果值恰好是数百万,那么效率会很低。回答很好,谢谢。我没有想一想用weightedPick来递增-这真的简化了它。当你说它迭代两次时,是不是一次用于
Sum()
还有一次实际的
foreach
循环?奇怪的是,序列不会太大,因此不会成为性能问题。@Wilson:没错,
Sum
中的迭代是隐式的。如果序列很短,这应该不是问题。我认为在使用数字
0
时,这不会起作用
应该有
0
被选择的机会,但由于
Random.Next()的范围,因此可以选择它
0
以上。@John:
加权拾取
的次数如何小于
,这是返回该项的唯一方式?@JonSkeet如果数字的顺序与
1,0
相反,且加权拾取的次数是
0
。返回的数字是
0
,而
0
本不该返回的数字有可能。您不想在每次调用时创建新的随机实例。请参阅。请检查别名表,如[此处][1]。[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];
}