C# 频率为“a”和“b”的1.5倍。但很容易看出,您的算法并不总是在结果中产生“c”
实现这一点的一种算法是,将项目沿数字线从0到1排列,使其占据与其权重成比例的线段,然后随机选择0到1/x之间的数字“开始”,然后找到所有点“开始+n/x”(对于所有整数n,使点在0到1之间)并生成包含由这些点标记的项的集合 换句话说,类似于: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)
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