As some people have mentioned in the comments, create a list of the cards in the exact proportion that you want:
var deck = new List<Card>();
cards.ForEach(c =>
{
for(int i = 0; i < c.AttributionRate; i++)
{
deck.Add(c);
}
}
Shuffle:
deck = deck.OrderBy(c => Guid.NewGuid()).ToList();
And pick x cards:
var hand = deck.Take(x)
Of course this only works if AttributionRate
is an int
. Otherwise, you would have to tinker with the deck generation a bit.
I get the following results for 10,000 runs taking 5 at a time:
Card 1: 9.932%
Card 2: 30.15%
Card 3: 49.854%
Card 4: 10.064%
Another result:
Card 1: 10.024%
Card 2: 30.034%
Card 3: 50.034%
Card 4: 9.908%
EDIT:
I've braved the bitwise operations and I have taken a look at your code. After adding a generous amount of barbecue sauce on my fried brain, I noticed a few things:
First, Random.Next(min,max)
will include min in the random pool, but not max. This is the reason for the higher than expected probability for Card 1.
After doing that change, I implemented your code and it appears to be working when you draw 1 card.
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%
HOWEVER, your code will not work when you draw more than 1 card because of this statement:
heap[i].Weight = 0;
That line, and the recalculation loop after that, essentially removes all instances of the drawn card from the heap. If you happen to draw four cards, then the percentage becomes 25% for all cards since you're basically drawing all 4 cards. The algorithm, as it is, is not completely applicable to your case.
I suspect you would have to recreate the heap every time you draw a card, but I doubt it would still perform as well. If I were to work on this though, I would just generate 4 distinct random numbers from 1 to heap[1].TotalWeight
and get the 4 corresponding cards from there, although the random number generation in this case might become unpredictable (rerolling) and thus inefficient.