Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/linq/3.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#_Linq_Weighted_Weighted Average - Fatal编程技术网

在c#中,按权重选取随机元素的最简洁方法是什么?

在c#中,按权重选取随机元素的最简洁方法是什么?,c#,linq,weighted,weighted-average,C#,Linq,Weighted,Weighted Average,让我们假设: 列表哪个元素是: public class Element(){ int Weight {get;set;} } 我想要实现的是,根据权重随机选择一个元素。 例如: Element_1.Weight = 100; Element_2.Weight = 50; Element_3.Weight = 200; 所以 被选中的机会元素_1为100/(100+50+200)=28.57% 被选中的机会元素_2为50/(100+50+200)=14.29% 被选中的机会元素_3为

让我们假设:

列表
哪个元素是:

public class Element(){
   int Weight {get;set;}
}
我想要实现的是,根据权重随机选择一个元素。 例如:

Element_1.Weight = 100;
Element_2.Weight = 50;
Element_3.Weight = 200;
所以

  • 被选中的机会
    元素_1
    为100/(100+50+200)=28.57%
  • 被选中的机会
    元素_2
    为50/(100+50+200)=14.29%
  • 被选中的机会
    元素_3
    为200/(100+50+200)=57.14%
我知道我可以创建一个循环,计算总数,等等

我想学习的是,Linq用一行(或尽可能短)来完成这项工作的最佳方式是什么,谢谢

更新

我在下面找到了答案。我学到的第一件事是:Linq不是魔法,它比精心设计的循环慢


所以我的问题变成了按权重查找一个随机元素(没有尽可能短的东西:)

这是一个预计算的快速解决方案。预计算采用
O(n)
,搜索采用
O(log(n))

预计算:

int[] lookup=new int[elements.Length];
lookup[0]=elements[0].Weight-1;
for(int i=1;i<lookup.Length;i++)
{
  lookup[i]=lookup[i-1]+elements[i].Weight;
}

如果你想要一个<强>泛型版本>(对使用(单体)随机化助手有用的话,考虑是否需要一个常数种子)

用法:

randomizer.GetRandomItem(items, x => x.Weight)
代码:

公共T GetRandomItem(IEnumerable itemsEnumerable,Func weightKey) { var items=itemsEnumerable.ToList(); var totalWeight=items.Sum(x=>weightKey(x)); var randomWeightedIndex=_random.Next(总重量); var itemWeightedIndex=0; foreach(项目中的var项目) { itemWeightedIndex+=权重键(项); if(随机加权指数<项目加权指数) 退货项目; } 抛出新ArgumentException(“集合计数和权重必须大于0”); } 这可能会起作用:

int weightsSum = list.Sum(element => element.Weight);
int start = 1;
var partitions = list.Select(element => 
                 { 
                   var oldStart = start;
                   start += element.Weight;
                   return new { Element = element, End = oldStart + element.Weight - 1};
                 });

var randomWeight = random.Next(weightsSum);
var randomElement = partitions.First(partition => (partition.End > randomWeight)).
                               Select(partition => partition.Element);
基本上,为每个元素创建一个带有结束权重的分区。 在您的示例中,Element1与(1-->100)关联,Element2与(101-->151)关联,依此类推

然后计算一个随机权重和,我们寻找与之相关的范围

您也可以在方法组中计算总和,但这将引入另一个副作用


请注意,我不是说这是优雅或快速。但是它确实使用linq(不在一行中…

所以你想要短代码,但你不在乎它慢吗?linq会比基于循环的代码慢。如果你想要快速的代码,你需要在
O(n)
中预计算,这样你就可以进行
O(1)
查找。但是这方面的代码将相对复杂。您认为Linq(到对象)是如何工作的?魔术它只是封装循环,通常比手写循环慢2-3倍。linq的主要优点是代码更短更清晰。linq确实比自己编写高效的迭代算法慢。linq的原因是它更易于阅读/使用,并且不太可能导致错误。@CodeInChaos-2到3倍的速度是一种严重的夸张。Factor out
list.Max(…)
。您当前的代码是
O(n^2)
。如果你把它算出来,只有
O(n*log(n))
如果你需要First(),而且你已经使用了Random,为什么要按Random排序呢?另外,我会缓存list.Max,没有理由在每次迭代中计算Max。无论如何,对于@allguys,我想我只是使用loop:(@EricYin:他在Max-weight上使用Random,因为你说200应该比50更有可能被选中。但是,我不确定它是否匹配总和(weight)-权衡你提出的问题。排序似乎确实是多余的(见我之前的评论)。
minWeight
是为了确保随着物品重量的增加,选择物品的频率会增加。而随机顺序(@Abel)用于从结果集中选择一个随机项,因为仅从示例中选择第一个不会产生
Weight=50
项,即使
minWeight
<50
问题的LINQ在哪里?;)
int total=elements.Sum(e=>e.Weight)
:P这是我发现linq在这个问题上唯一有用的地方。我找不到一个快速、干净的方法来使用linq执行搜索。我喜欢这个解决方案,看起来非常不错。此外,我将编辑并允许
totalWeight=0
,然后随机选择一个(由于它们的权重相同,所以机会均等),而不抛出异常。我想我在这里发现了一个问题(一个小问题),您应该使用
randomWeightedIndex=\u random。下一步(totalWeight)+1
,然后比较if(randomWeightedIndex)我很确定它不会有什么不同。它会产生影响。让我们假设
a=0,B=2
,a被选中的几率为0%,B为100%。
R=rand.Next(2)当R=1时,A将被选择,A将变成A和B的50%。如果使用+1,R=1到2,在基于零的索引中也不会有区别,但是我认为权重值0是“0%机会”。,所以…这一切都取决于定义。不过,很高兴看到你发现它很有用。谢谢。我想除了求和部分,我将离开linq,因为它在途中创建了太多新对象
int total=elements.Sum(e=>e.Weight);
int chosen=random.GetNext(total);
int runningSum=0;
foreach(var element in elements)
{
  runningSum+=element.Weight;
  if(chosen<runningSum)
    return element;
}
randomizer.GetRandomItem(items, x => x.Weight)
public T GetRandomItem<T>(IEnumerable<T> itemsEnumerable, Func<T, int> weightKey)
{
    var items = itemsEnumerable.ToList();

    var totalWeight = items.Sum(x => weightKey(x));
    var randomWeightedIndex = _random.Next(totalWeight);
    var itemWeightedIndex = 0;
    foreach(var item in items)
    {
        itemWeightedIndex += weightKey(item);
        if(randomWeightedIndex < itemWeightedIndex)
            return item;
    }
    throw new ArgumentException("Collection count and weights must be greater than 0");
}
int weightsSum = list.Sum(element => element.Weight);
int start = 1;
var partitions = list.Select(element => 
                 { 
                   var oldStart = start;
                   start += element.Weight;
                   return new { Element = element, End = oldStart + element.Weight - 1};
                 });

var randomWeight = random.Next(weightsSum);
var randomElement = partitions.First(partition => (partition.End > randomWeight)).
                               Select(partition => partition.Element);