C# 频率为“a”和“b”的1.5倍。但很容易看出,您的算法并不总是在结果中产生“c”

C# 频率为“a”和“b”的1.5倍。但很容易看出,您的算法并不总是在结果中产生“c”,c#,statistics,probability,C#,Statistics,Probability,实现这一点的一种算法是,将项目沿数字线从0到1排列,使其占据与其权重成比例的线段,然后随机选择0到1/x之间的数字“开始”,然后找到所有点“开始+n/x”(对于所有整数n,使点在0到1之间)并生成包含由这些点标记的项的集合 换句话说,类似于: a.) optionally shuffle the list of elements (if you need random combinations of elements in addition to respecting the weights)

实现这一点的一种算法是,将项目沿数字线从0到1排列,使其占据与其权重成比例的线段,然后随机选择0到1/x之间的数字“开始”,然后找到所有点“开始+n/x”(对于所有整数n,使点在0到1之间)并生成包含由这些点标记的项的集合

换句话说,类似于:

a.) optionally shuffle the list of elements (if you need random combinations of elements in addition to respecting the weights)  
b.) create a list of cumulative weights, if you will, called borders, such that borders[0] = items[0].weight and borders[i] = borders[i - 1] + items[i].weight  
c.) calculate the sum of all the weights => total_weight  
d.) step_size = total_weight / x  
e.) next_stop = pick a random number between [0, step_size)  
f.) current_item = 0  
g.) while next_stop < total_weight:
h.)   while borders[current_item] < next_stop:  
i.)     current_item += 1  
j.)   append items[current_item] to the output  
k.)   next_stop += step_size
a.)可选地洗牌元素列表(如果除了考虑权重之外还需要元素的随机组合)
b、 )创建一个名为borders的累积权重列表,使borders[0]=项目[0]。权重和borders[i]=边框[i-1]+项目[i]。权重
c、 )计算所有权重之和=>总权重
d、 )步长=总重量/x
e、 )next_stop=在[0,步长大小]之间选择一个随机数
f、 )当前项目=0
g、 )下一站时<总重量:
h、 )而边框[当前项目]<下一站:
i、 )当前_项+=1
j、 )将项[当前项]附加到输出
k、 )下一站+=步长

注意:这只适用于权重最大的woho,我会使用linq,
order by Guid.NewGuid()
和double/triple/…根据速率计算的实例数量。更容易实现,更容易阅读-但是没有关于性能的字。System.Random不是一个好的随机数生成器(而且guid根本不是随机生成器)。如果你需要一个真正的随机分布,你必须使用其他东西。别无选择。注意:即使是一个“完美”的RNG,你也不会得到两张牌相同的点击次数(即使它们的权重相同)…System.Random是一个非常好的随机数生成器。当然,它只是一个伪随机数生成器,但在这种情况下这不是问题。@Adriano你读过我之前的评论吗?使用另一种算法,我可以在拾取一张卡10000次时获得预期的分布。伪随机数生成器NET的问题不在这里。我没有检查他的代码,但我想最大的问题不是如何“提取”卡,而是生成伪随机数的方式。内置生成器远远不是最佳的。以下是我从您的解决方案中得到的结果:卡1:0.00%(预计为10.00%),卡2:0.00%(预计为30.00%),卡3:0.00%(预期为50.00%),卡4:100.00%(预期为10.00%)。此问题并不像看上去那么简单,请参考链接问题()为了获得更深入的了解。您的代码正在运行,所以我考虑接受这一点作为答案。但是它比我发布的代码慢6倍,而且我非常确定一旦我开始使用真实数据,差异会更大。我不知道性能是一个考虑因素。
Guid.NewGuid()
part可能是罪魁祸首,在这里生成随机小数可能会得到更好的结果。不过,我不是100%确定。是的,它确实减少了40%多一点的计算时间,但仍然比原来的解决方案慢很多。我复制代码的答案被提高了28倍,所以我想它是按照宣传的那样工作的。我不明白我的代码怎么会如此错误。我试着看看你的代码哪里出错了,但按位操作把我的大脑炸得干净利落;)我恐怕我的问题措辞很糟糕:一旦选了一张卡,你就不能再选它了(这就是我所说的不替换样本的意思)。尽管您的原始答案完全尊重了权重,但它也得到了与我的要求不匹配的重复卡片。事实上,我说得太快了,当我只选择一张卡片时,它工作得完美无缺。只要我选择多张卡片(例如,给定的一组卡片中有3张),我就会得到以下结果:卡片1:18.30%(预计为10.00%),卡2:30.20%(预计为30.00%),卡3:32.25%(预计为50.00%),卡4:19.25%(预计为10.00%)@Gabriel我认为你的期望不适合挑选多张牌。在每次试用中,你挑选3张牌而不更换,对吗?因此,不可能让3张牌占挑选的50%!当你挑选多张牌而不更换时,概率会随着你的行动而变化。一旦你取出第一张牌,挑选的概率就会降低ng这张牌再次变为0,并且拾取剩余牌的概率增加。如果您在这4张牌中拾取3张,而不进行替换,我预计您将在96.6%的时间内获取牌3。但是,由于它只是您拾取的三张牌中的一张,它只占您拾取的总张数的32.2%。请注意,这与您观察到的非常接近d!谢谢,这完全有道理。我尝试了几个不同的样品和挑选数量,结果似乎令人满意:)
var cards = new List<Card>
{
    new Card { Id = 1, AttributionRate = 1 }, // 10 %
    new Card { Id = 2, AttributionRate = 3 }, // 30 %
    new Card { Id = 3, AttributionRate = 5 }, // 50 %
    new Card { Id = 4, AttributionRate = 1 }, // 10 %
};
public class CardAttributor : ICardsAttributor
{
    private static Random random = new Random();

    private List<Node> GenerateHeap(List<Card> cards)
    {
        List<Node> nodes = new List<Node>();
        nodes.Add(null);

        foreach (Card card in cards)
        {
            nodes.Add(new Node(card.AttributionRate, card, card.AttributionRate));
        }

        for (int i = nodes.Count - 1; i > 1; i--)
        {
            nodes[i>>1].TotalWeight += nodes[i].TotalWeight;
        }

        return nodes;
    }

    private Card PopFromHeap(List<Node> heap)
    {
        Card card = null;

        int gas = random.Next(heap[1].TotalWeight);
        int i = 1;

        while (gas >= heap[i].Weight)
        {
            gas -= heap[i].Weight;
            i <<= 1;

            if (gas >= heap[i].TotalWeight)
            {
                gas -= heap[i].TotalWeight;
                i += 1;
            }
        }

        int weight = heap[i].Weight;
        card = heap[i].Value;

        heap[i].Weight = 0;

        while (i > 0)
        {
            heap[i].TotalWeight -= weight;
            i >>= 1;
        }

        return card;
    }

    public List<Card> PickMultipleCards(List<Card> cards, int cardsToPickCount)
    {
        List<Card> pickedCards = new List<Card>();

        List<Node> heap = GenerateHeap(cards);

        for (int i = 0; i < cardsToPickCount; i++)
        {
            pickedCards.Add(PopFromHeap(heap));
        }

        return pickedCards;
    }
}

class Node
{
    public int Weight { get; set; }
    public Card Value { get; set; }
    public int TotalWeight { get; set; }

    public Node(int weight, Card value, int totalWeight)
    {
        Weight = weight;
        Value = value;
        TotalWeight = totalWeight;
    }
}

public class Card
{
    public int Id { get; set; }
    public int AttributionRate { get; set; }
}
Card GetCard(List<Card> cards)
{
  int total = 0;
  foreach (Card c in cards)
  {
    total += AttributionRate;
  }

  int index = Random.Next(0, total - 1);
  foreach(Card c in cards)
  {
    index -= c.AttributionRate;
    if (index < 0)
    {
      return c;
    }
  }
}

Card PopCard(List<Card> cards)
{
  Card c = GetCard(cards);
  cards.Remove(c);
}
var deck = new List<Card>();

cards.ForEach(c => 
{
    for(int i = 0; i < c.AttributionRate; i++)
    {
         deck.Add(c);
    }
}
deck = deck.OrderBy(c => Guid.NewGuid()).ToList();
var hand = deck.Take(x)
Card 1: 9.932% 
Card 2: 30.15% 
Card 3: 49.854% 
Card 4: 10.064% 
Card 1: 10.024%
Card 2: 30.034%
Card 3: 50.034% 
Card 4: 9.908% 
Card 1: 10.4%  
Card 2: 32.2% 
Card 3: 48.4% 
Card 4: 9.0% 

Card 1: 7.5%
Card 2: 28.1%
Card 3: 50.0% 
Card 4: 14.4% 
heap[i].Weight = 0;
int gas = random.Next(heap[1].TotalWeight);
a.) optionally shuffle the list of elements (if you need random combinations of elements in addition to respecting the weights)  
b.) create a list of cumulative weights, if you will, called borders, such that borders[0] = items[0].weight and borders[i] = borders[i - 1] + items[i].weight  
c.) calculate the sum of all the weights => total_weight  
d.) step_size = total_weight / x  
e.) next_stop = pick a random number between [0, step_size)  
f.) current_item = 0  
g.) while next_stop < total_weight:
h.)   while borders[current_item] < next_stop:  
i.)     current_item += 1  
j.)   append items[current_item] to the output  
k.)   next_stop += step_size