6

更新:私の問題は解決されました。ジェイソンの回答と一致するように、質問のコード ソースを更新しました。rikitikitik の回答は、サンプルからカードを選択して交換する問題を解決していることに注意してください。

加重リストから x 個のランダムな要素を選択したい。サンプリングは交換なしです。私はこの答えを見つけました:https ://stackoverflow.com/a/2149533/57369 Pythonで実装されています。C# で実装してテストしました。しかし、結果 (後述) は、私が期待したものと一致しませんでした。私は Python の知識がないので、コードを C# に移植する際に間違いを犯したと確信していますが、Pythong のコードが本当によく文書化されているため、どこにあるのかわかりません。

私は 1 枚のカードを 10000 回ピックしましたが、これが得られた結果です (結果は実行全体で一貫しています)。

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)

ご覧のとおり、カード 1 とカード 4 の重みはどちらも 1 ですが、カード 1 はカード 4 よりもずっと頻繁にピックされます (たとえ 2 枚または 3 枚のカードをピックしても)。

テストデータ:

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

これが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; }
}
4

4 に答える 4

2

一部の人々がコメントで言及しているように、必要な正確な比率でカードのリストを作成します。

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();

そして、x枚のカードを選びます:

var hand = deck.Take(x)

もちろん、これAttributionRateは が の場合にのみ機能しintます。そうでなければ、デッキの生成を少しいじる必要があります。

一度に 5 回実行して 10,000 回実行すると、次の結果が得られます。

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% 

編集:

私はビット単位の操作に勇敢に立ち向かい、あなたのコードを調べました。揚げた脳みそにバーベキューソースをたっぷりかけたところ、いくつかのことに気づきました。

まず、Random.Next(min,max)ランダム プールに min を含めますが、max は含めません。これが、カード 1 の予想より高い確率の理由です。

その変更を行った後、私はあなたのコードを実装しました。カードを 1 枚引いたときに機能しているようです。

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;

その行とその後の再計算ループは、基本的に、描画されたカードのすべてのインスタンスをヒープから削除します。たまたま 4 枚のカードを引いた場合、基本的に 4 枚すべてのカードを引くため、すべてのカードの割合は 25% になります。アルゴリズムはそのままでは、あなたのケースに完全には適用できません。

カードを引くたびにヒープを再作成する必要があると思いますが、それでも同じように機能するとは思えません。ただし、これに取り組むとしたら、1 から 4 までの 4 つの異なる乱数を生成しheap[1].TotalWeight、そこから対応する 4 枚のカードを取得するだけですが、この場合の乱数生成は予測不能 (リロール) になり、非効率になる可能性があります。

于 2012-08-06T03:58:26.530 に答える
2

プログラムには 2 つの小さなバグがあります。まず、乱数の範囲は、すべてのアイテムの合計重量と正確に等しくなければなりません。

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

gas >第二に、言うと言う場所を両方とも変更しますgas >=

(元の Python コードは、 が浮動小数点数であるため問題ありません。したがって、とgasの違いは無視できます。そのコードは、整数または浮動小数点の重みを受け入れるように記述されています。)>>=

更新: OK、コードに推奨される変更を加えました。そのコードは正しいと思います。

于 2012-08-06T23:16:02.543 に答える
0

あなたはこれを行うことができます:

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

理論的には、これは機能するはずです。

于 2012-08-02T11:13:56.887 に答える