Seleccione x elementos aleatorios de una lista ponderada en C # (sin reemplazo)

Actualizar: mi problema se ha resuelto, actualicé el código fuente en mi pregunta para que coincida con la respuesta de Jason. Tenga en cuenta que la respuesta de rikitikitik es resolver el problema de escoger tarjetas de una muestra con reemplazo.

Quiero seleccionar x elementos aleatorios de una lista ponderada. El muestreo es sin reemplazo. Encontré esta respuesta:https://stackoverflow.com/a/2149533/57369 Con una implementación en Python. Lo implementé en C # y lo probé. Pero los resultados (como se describen a continuación) no coincidían con lo que esperaba. No tengo conocimiento de Python, así que estoy bastante seguro de que cometí un error al portar el código a C #, pero no puedo ver dónde está realmente bien documentado el código en Pythong.

Escogí una tarjeta 10000 veces y este es el resultado que obtuve (el resultado es consistente en todas las ejecuciones):

Card 1: 18.25 % (10.00 % expected)
Card 2: 26.85 % (30.00 % expected)
Card 3: 46.22 % (50.00 % expected)
Card 4: 8.68 % (10.00 % expected)

Como puede ver, la Tarjeta 1 y la Tarjeta 4 tienen un peso de 1, pero la Tarjeta 1 se juega con más frecuencia que la tarjeta 4 (incluso si elijo 2 o 3 tarjetas).

Datos de prueba:

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 %
};

Aquí está mi implementación en C #

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; }
}

Respuestas a la pregunta(4)

Su respuesta a la pregunta