关于c#:为什么洗牌两个不同的套牌产生相同的结果?

Why does shuffling two different decks yield the same result?

本问题已经有最佳答案,请猛点这里访问。

首先,我在这里上"甲板"课。为了测试的目的,我刚刚介绍了一些基本的方法。这是我创建"卡片"程序的第一步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
    public class Deck
{
    private int deckCounter = 0;
    private List<Card> deckSize = new List<Card>();
    private List<Card> shuffledDeck = new List<Card>();
    private Random random = new Random();

    public Deck()
    {
    }

    public void Build()
    {
        for (int i = 1; i < 5; i++)
        {
            for (int k = 1; k < 14; k++)
            {
                deckSize.Add(new Card(k.ToString(), i));
            }
        }
    }

    public void Add(Card card)
    {
        deckSize.Add(card);
        deckCounter++;
    }

    public Card RemoveCard()
    {
            Card cardToRemove = deckSize.First();
            deckSize.RemoveAt(0);
            return cardToRemove;
    }

    public void ShowContainedCards()
    {
        int cardCount = 0;
        foreach (Card c in deckSize)
        {
            Console.WriteLine(c.ReturnCardInfo());
            cardCount++;
        }
        Console.WriteLine(cardCount);
    }

    public void Shuffle()
    {
        while (deckSize.Count != 0)
        {
            int i = random.Next(deckSize.Count);
            shuffledDeck.Add(deckSize[i]);

            deckSize.RemoveAt(i);
        }
        deckSize = shuffledDeck;
    }

    public bool IsEmpty()
    {
        if (deckSize.Any())
        {
            return false;
        }
        else return true;
    }

    public List<Card> GetCardList()
    {
        return deckSize;
    }
}

基本上,我要做的是:

1
2
3
4
5
6
7
    Deck deck1 = new Deck();
    Deck deck2 = new Deck();

    deck1.Build();
    deck1.Shuffle();
    deck2.Build();
    deck2.Shuffle();

在那之后,我得到了和第一层和第二层完全一样的洗牌。为什么会这样?另外,如果你不知道的话,我是这方面的新手。


来自文档:

The default seed value is derived from the system clock and has finite resolution. As a result, different Random objects that are created in close succession by a call to the default constructor will have identical default seed values and, therefore, will produce identical sets of random numbers.

同样的文档也提出了解决方案:

This problem can be avoided by using a single Random object to generate all random numbers.

因此,一种可能的解决方案是使随机生成器保持静态,因此所有Deck实例共享同一Random实例:

1
private static Random random = new Random();

这样,您甚至可以避免更改Deck类的公共接口的任何部分。


在两个deck实例中使用相同的Random类实例:

1
2
3
Random random = new Random();
Deck deck1 = new Deck(random);
Deck deck2 = new Deck(random);

因此,在构造函数中:

1
2
3
4
5
6
7
8
9
10
11
public class Deck
{
    private int deckCounter = 0;
    private List<Card> deckSize = new List<Card>();
    private List<Card> shuffledDeck = new List<Card>();
    private Random random;

    public Deck(Random random)
    {
        this.random = random;
    }

当前代码的问题是,创建的两个Random实例的种子是相同的。这使得它们产生相同的结果。使用相同的Random实例意味着第二次洗牌将建立在第一次洗牌的种子结果之上。


计算机本质上不是随机的,因此任何随机数生成器实际上都将使用一种算法来生成看起来随机的输出。问题是总是有一个起点,如果你知道它从哪里开始,你就可以预测结果。因此,随机数生成器有一个"种子",告诉它从哪里开始。相同的种子总是会给出相同的"随机"数字序列。

两种情况下,您都使用new Random(),它使用默认种子。在某些语言中,建议您将当前时间作为种子传递,但C会为您这样做。但是,如果您将两个Random对象紧密地创建在一起,它们很可能得到相同的时间,这就是这里发生的事情。

如果你把你的Random静态化,那么你所有的随机数都来自同一个来源,所以两个甲板会得到连续的随机数,而不是平行的相同的随机数。