【问题标题】:Is this a sufficient way to shuffle a deck of cards?这是洗牌的充分方法吗?
【发布时间】:2014-04-30 16:38:46
【问题描述】:

我正在尝试在我的应用程序中洗牌,并使用以下代码。这会让套牌充分随机化吗?我几乎可以肯定只是想要另一种意见。谢谢!

for (int i = 0; i < 40000; i++) {
    int randomInt1 = arc4random() % [deck.cards count];
    int randomInt2 = arc4random() % [deck.cards count];
    [deck.cards exchangeObjectAtIndex:randomInt1 withObjectAtIndex:randomInt2];

编辑:如果有人想知道或将来应该遇到这个问题。这就是我用来洗牌的方法,它是 Fisher-Yates 算法的一种实现。我从下面建议的@MartinR 帖子中得到它,可以在这里找到:What's the Best Way to Shuffle an NSMutableArray?

NSUInteger count = [deck.cards count];
    for (uint i = 0; i < count; ++i)
    {
        // Select a random element between i and end of array to swap with.
        int nElements = count - i;
        int n = arc4random_uniform(nElements) + i;
        [deck.cards exchangeObjectAtIndex:i withObjectAtIndex:n];
    }

【问题讨论】:

  • 两项改进 - 1) 将[deck.cards count] 存储在循环之前的变量中,这样您就不需要调用该方法 80,000 次。 2) 使用arc4random_uniform(count) 而不是arc4random 与模数。
  • “这会让牌组充分随机化吗?” 对于概率论专家来说,这更像是一个数学问题。 - 例如,可以在此处找到用于 NSArray 的 Fisher-Yates 算法的实现:What's the Best Way to Shuffle an NSMutableArray?
  • @Rob 我的意思是[deck.cards count] 应该在循环之前存储在一个变量中,然后在循环中,应该使用该变量。这样可以节省 80,000 次方法调用。
  • @Rob,是的,将循环从 40,000 减少到 52 是一个更好的改进。 :)

标签: ios objective-c algorithm shuffle


【解决方案1】:

如果 [deck.cards count]

for (int i = [deck.cards count] - 1; i > 0 ; i--) {
    int randomInt1 = arc4random_uniform(i + 1);
    [deck.cards exchangeObjectAtIndex:randomInt1 withObjectAtIndex:i];
}

来自文档:

arc4random_uniform() 将返回一个均匀分布的随机数 小于上限。 建议使用 arc4random_uniform(),而不是像 ``arc4random() % upper_bound'' 这样的结构,因为它避免了 上限不是 2 的幂时的“模偏差”。

【讨论】:

  • 你为什么把它从向前计数改为向后计数?
  • @GuyKogus 使用 'arc4random_uniform' 无需额外计算。
  • 这不是一个正确的实现。请看我的回答。
  • 这还是不对。随着i 接近于 0,与您交换对象的项目范围变得更小。随机化意味着遍历数组的整个大小。将i + 1 更改为[deck.cards count],您就会成功。
  • @GuyKogus 在这里没关系。例如,第一个元素有可能更改其位置“计数”次数并占据任何位置。因此,当我接近 0 时,第一个位置的元素很可能已经与其他一些元素交换了。
【解决方案2】:

这是正确实现的 Fisher-Yates 算法。是的,它会充分随机化你的数组,我已经用过很多次了,真是太棒了!

NSUInteger count = [deck.cards count];
if (count > 0) {
    for (NSUInteger i = count - 1; i > 0 ; --i) {
        [deck.cards exchangeObjectAtIndex:i
                        withObjectAtIndex:arc4random_uniform(i + 1)];
    }
}

【讨论】:

  • 以这种方式洗牌实际上有一个微妙的问题,所以我不会说“它仍然很好用”。请参阅:The Danger of Naïveté。正确的 FY 洗牌可以避免这种情况。
  • 您建议始终与整个范围内的索引进行交换实际上引入了可观察到的偏差。请参阅 Fisher-Yates 页面上的 implementation errors 部分,其中“在每次迭代中始终从有效数组索引的整个范围中选择 j 也会产生有偏差的结果,尽管不太明显。”我对 Avt 和你的答案都做了 monte-carlo,而你所引入的偏见并非微不足道(尽管我不确定你是否会注意到如果你坐在那里,看着应用程序交易卡)。
  • @user3361608:我觉得这很有趣。
  • @user3361608:我知道,但是这段代码被巧妙地破坏了,将其修复为 正确 Fisher-Yates shuffle 非常容易。这段代码看起来很简单,而且“感觉不错”,但这就是它如此有害的原因。我相信你会做正确的事。
  • @user3361608:您可以根据 wiki 页面自己实现它,或者上面有 related question/answers right here on SO 或 Avt 的答案可能会有所帮助。
【解决方案3】:

根据您实现套牌的方式,您可以简单地使用Collections.sort(),或者您可以使用 ArrayList,假设您的实现类似于以下内容

    ArrayList<Integer> originalDeck = new ArrayList<Integer>();
    ArrayList<Integer> randomDeck = new ArrayList<Integer>();

    //Initalize array
    for (int i = 0; i < 52; i++) {
        originalDeck.add(i, i);
    }

    //Smart Implementation
    Collections.shuffle(originalDeck);

    Collections.sort(originalDeck);

    //Long Implementation
    int i=0, max = 0;
    while (originalDeck.size() != 0) {
        max = originalDeck.size();
        i = (int) (Math.random() * max); // goes from 0 to size-1 so always in bounds
        randomDeck.add(originalDeck.remove(i));
    }

【讨论】:

    猜你喜欢
    • 2014-12-22
    • 1970-01-01
    • 2010-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-08
    • 1970-01-01
    • 2016-01-02
    相关资源
    最近更新 更多