【问题标题】:How to Test if a Deck of Cards Has Been Shuffled Enough in Java如何在 Java 中测试一副纸牌是否洗得足够多
【发布时间】:2011-06-28 23:25:00
【问题描述】:

我必须测试一下,看看这个方法是否洗过一副牌。 这里是实际洗牌部分的代码。

 public void randomShuffle () {
               for (int i = 0; i < DECK_SIZE; i++) {
                   int place = (int)((Math.random()*(51-i))+i);
                   Card temp = this.cardAt(i);
                   this.cardList[i] = this.cardAt(place);
                   this.cardList[place] = temp;
           }
       }

测试它是否被洗牌的问题是我只能交换两张牌,它会被认为是洗牌。 这是我迄今为止对随机洗牌的测试。

static void randomShuffleTest () {
       Deck deck1 = Deck.newDeckOf52();
       Deck deck2 = Deck.newDeckOf52();
       deck2.randomShuffle();           

       assert false == deck1.equals(deck2);
    }

所以,我的问题是,我如何测试某些东西是否被洗牌了?

【问题讨论】:

  • 正如有人所说,你需要一个“足够”的定义。你可以计算出像洗牌的熵之类的东西,但我会说按照 Jigar Joshi 所说的去做并使用 Collections.shuffle() 会更容易。
  • 一个公平的洗牌(例如fisher-yates,你的实现)将以相等的概率选择所有洗牌。通过尝试排除“较少洗牌”的结果,您实际上是在 减少洗牌的随机性。

标签: java algorithm random shuffle playing-cards


【解决方案1】:

你不能。无法确定一副牌是否经过洗牌,因为理论上洗牌可以产生一副完全有序的牌。

【讨论】:

  • 是否可以将一副牌中的第一张牌与另一张牌进行比较并添加到计数器中?然后将牌组的前两张牌与牌组的下两张牌进行比较?等等等等。因此计数器越高,它被洗牌的次数就越多?
  • @frodosamoa:也许这个类比会对你有所帮助。想象一下,您有一个函数声称返回 1 到 10(含)之间的随机整数。您想测试它的随机性。您决定如果结果是 1 或 10,那么它就不够随机。其他任何事情都足够随机。问题是这个测试有 20% 的时间拒绝真正随机的函数,并且它接受根本不是随机的函数(例如“return 4;”)。您所做的与这个简单示例基本相同,只是您选择的是 52 个随机数而不是 1 个。
【解决方案2】:

精确的测试是不可能的。只要相信你的算法。或使用Collections.shuffle()

【讨论】:

  • 是否可以将一副牌中的第一张牌与另一张牌进行比较并添加到计数器中?然后将牌组的前两张牌与牌组的下两张牌进行比较?等等等等。因此计数器越高,它被洗牌的次数就越多?
  • 再次考虑最坏的情况。你已经洗牌并且它像以前一样对齐,你已经洗牌(但实际上不是)。您可以修改您的算法,以跟踪之前的订单并随机播放它在任何情况下都不会与之前的状态对齐
【解决方案3】:

您无法对单个样本执行此操作。但是您可能能够做的是对一些洗牌后的牌组进行统计分析,以寻找非随机性的迹象。

我认为你最好在 math.stackexchange.com 上问这个问题……聪明的家伙在哪里闲逛。如果他们能用简单的术语解释“数学”(对于像你我这样的愚蠢的 IT 人员),你应该能够用 Java 编写测试代码。


当我说“你不能这样做”时……显然你可以测试看看牌组是否根本没有被洗牌,或者洗牌是否破坏了牌组。但这些都不是对您的标准的可靠测试......“洗牌足够了”。

【讨论】:

【解决方案4】:

首先我喜欢这个问题(有趣)。

  • 首先我想知道您对“足够洗牌”的定义是什么。你甚至可以测量它吗?你有这方面的指导方针吗?例如应该至少有五张牌但在不同的位置?您可以轻松编写测试来验证多张卡片或在不同的位置,但这是否足够?
  • 我也认为 Mark 关于制作一个模仿原始牌组的洗牌牌组是正确的(尽管我怀疑概率非常低)。所以测试这将是非常困难的,或者如果它模仿原始的,你可以丢弃洗牌的牌组。这样你的测试就足够了
  • 此外,我认为您可能应该使用 Jigar 提到的 Collections.shuffle(),而不是自己写一些东西?

【讨论】:

    【解决方案5】:

    一种措施可能是对洗牌后的牌组进行排序并检查它进行了多少“操作”。显然,这个度量将取决于排序算法。

    或者,您可能会在此处的某处找到一些统计度量:http://en.wikipedia.org/wiki/Randomness_tests

    【讨论】:

      【解决方案6】:

      正如 Mark Byers 所说,洗牌算法可能会产生一副完全有序的牌组。但如果它在每次连续运行中都这样做,那么绝对是一个糟糕的算法!因此,一个合适的洗牌算法应该创建卡片序列,使得卡片在第 i 个位置的分布是均匀的。让S(i)={c(1)(i),c(2)(i),...,c(54)(i)} 成为第 i 个序列(算法的第 i 个结果)。那么关于 (i) 的 c(j)(i) 应该遵循(近似)均匀分布。要检查它是否成立,请运行您的算法数千次,并在每个位置 j=1,2,...,54 计算每张不同卡片出现的频率。数字应该或多或少相等。理想情况下,如果您运行算法 54000 次,您应该在每个位置看到每张卡片 1000 次。我强烈怀疑使用Math.random() 是否会出现这种情况。使用java.util.Random 以获得更好的结果。这就是你使用 Random 的方式:

      final java.util.Random random = new java.util.Random(seed);
      

      每次你需要一个随机的双精度:

      random.nextDouble();
      

      Collections.shuffle(); 方法正是这样做的。如果您需要比 Java 的 Random 实现更好的 RNG,您很可能应该继续自己的实现。

      【讨论】:

      • “我强烈怀疑使用 Math.random() 是否会出现这种情况。使用 java.util.Random 以获得更好的结果” - 不,是一回事。 Math.random() javadoc 说:“当这个方法第一次被调用时,它会创建一个新的伪随机数生成器,就像通过表达式 new java.util.Random”“如果你需要比 Java 的 Random 实现更好的 RNG,你很可能应该继续您自己的实施。” - 不,如果你需要比这更好的 RNG,你应该使用 java.util.SecureRandom,这几乎肯定比非专家可以实现的更好。
      • 也就是说,否则是一个很好的答案。测试分布是唯一真正的测试方法。
      • @Cowan:我同意 SecureRandom 是一个非常好的 RNG,但(也许)更好的是 linux 的 /dev/random,它使用从设备驱动程序收集的环境噪声。
      【解决方案7】:

      我发现测试这一点的最佳方法是测试 1000 次左右不同的时间,并记录每次测试成功的时间。最终,您真的想知道测试在 98-99% 的时间内都有效,只要结果是 ~99% +/- 1%,您就应该很好,并且测试应该始终通过。

      • 洗过的手不应该等于原来的。
      • 两者的长度应该相同。
      • 所有卡片的总和应该相同(或相似)

      swift中的示例代码:

      func testShuffled() {
          var hand1 = [3,4,5,6,7,1,2,8,9,10,11,12,2,3,4,5,6,7,8,9,10,11,12]
          var count =  0
      
          for _ in 0..<1000 {
              let hand2 = hand1.shuffled()
      
              if (hand1 != hand2 && hand1.count == hand2.count &&
                  hand1.reduce(0, combine: +) == hand2.reduce(0, combine: +)) {
                  count += 1
              }
              hand1 = hand2
          }
          let result = Double(count) / 1000.00
          XCTAssertEqualWithAccuracy(result, 1, accuracy: 0.02)
      }
      

      【讨论】:

      • 这并不能测试“足够洗牌”......除非您对“足够”的唯一标准是“完全”。此外,这不是 Java 代码。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-24
      • 2012-06-07
      • 2014-02-27
      相关资源
      最近更新 更多