Ruby 这个洗牌算法对吗?

Ruby 这个洗牌算法对吗?,ruby,algorithm,Ruby,Algorithm,以下是我在ruby中实现的洗牌算法: def shuffle03!(arr) len = arr.length for i in 0..len-1 index1 = Random.rand(0..len-1) index2 = Random.rand(0..len-1) arr[index1], arr[index2] = arr[index2], arr[index1] end end 我通过计算来测试这个算法: cla

以下是我在ruby中实现的洗牌算法:

def shuffle03!(arr)
    len = arr.length
    for i in 0..len-1
        index1 = Random.rand(0..len-1)
        index2 = Random.rand(0..len-1)
        arr[index1], arr[index2] = arr[index2], arr[index1]
    end
end
我通过计算来测试这个算法:

class ShuffleTest
    def initialize(seed)
        len = seed.length
        @count = {}
        for i in 0..len-1
            @count[seed[i]] = Array.new(len, 0)
        end
    end
    def test(arr)
        for i in 0...arr.length
            @count[arr[i]][i] += 1
        end
    end
    def show_count
        return @count
    end
end


def shuffle03!(arr)
    len = arr.length
    for i in 0..len-1
        index1 = Random.rand(0..len-1)
        index2 = Random.rand(0..len-1)
        arr[index1], arr[index2] = arr[index2], arr[index1]
    end
end


arr = ['a', 'b', 'c', 'd']

st = ShuffleTest.new(arr)

for x in 0..100_0000
    shuffle03!(arr)
    st.test(arr)
end

st.show_count.each do |k, v|
    puts k
    p v
end
结果是:

a
[250418, 249105, 249553, 250925]
b
[249372, 250373, 250785, 249471]
c
[250519, 250097, 249369, 250016]
d
[249692, 250426, 250294, 249589]
这似乎是正确的。然而,我不知道如何用数理统计证明它。所以我不确定它是否正确。

不,它不正确

假设您有一个四元素列表,[a,B,C,D]。注意:

  • 有4个!=24种可能的排列。要使其成为正确的洗牌算法,这些排列中的每一个都需要具有相同的可能性
  • 您将生成4×2=8个随机整数,每个整数的范围为0–3,总共48=65536个可能的随机数序列。这些序列中的每一个都具有相同的可能性
  • 65536不能被24整除,因此您的算法无法将65536可能的随机数序列映射到置换,从而为每个置换分配相等数量的随机数序列(因此概率相等)

要在测试中看到这一点,您可以创建
shuffle03的变体它不使用随机生成器,而是获取包含八个索引的列表,并使用这些索引。(
shuffle03!
然后可以通过生成八个随机索引并调用此变量作为辅助函数来实现。)然后,您的测试将迭代所有4096个可能的序列,并为每个序列创建一个四元素列表[a、B、C、D],然后调用variant方法以查看结果排列。该测试可以记录每个排列出现的频率,并使用它来发现哪些排列出现的次数比其他排列多。您将发现:

 Permutation    # of Occurrences
-------------  ------------------
 A B C D                    4480
 A B D C                    3072
 A C B D                    3072
 A C D B                    2880
 A D B C                    2880
 A D C B                    3072
 B A C D                    3072
 B A D C                    2432
 B C A D                    2880
 B C D A                    2048
 B D A C                    2048
 B D C A                    2880
 C A B D                    2880
 C A D B                    2048
 C B A D                    3072
 C B D A                    2880
 C D A B                    2432
 C D B A                    2048
 D A B C                    2048
 D A C B                    2880
 D B A C                    2880
 D B C A                    3072
 D C A B                    2048
 D C B A                    2432
正如您所看到的,元素往往以它们开始的相同顺序结束;例如,
abcd
是最常见的排列。我们可以通过观察每一对元素,它们以相同的顺序结束与以相反的顺序结束的频率,得出这一点的一个方面。我们发现:

 Elements    Same Order    Opposite Order
----------  ------------  ----------------
 A and B          33792             31744
 A and C          34816             30720
 A and D          35840             29696
 B and C          33792             31744
 B and D          34816             30720
 C and D          33792             31744
因此,一些配对比其他配对更有可能以相反的顺序结束,但每对配对都更有可能以相同的顺序结束,而不是以相反的顺序结束

您可以通过执行更多的传递来减少不平衡,但由于8的幂不能被24整除,因此不可能使所有排列的可能性相等


顺便说一句,如果你在这里的实际目标是一个好的洗牌算法(而不仅仅是为自己找出一个的学习经验),那么你应该使用一个


当然,因为您使用的是Ruby,所以只需使用
Array.shuffle就可以绕过整个问题,为您执行Fisher–Yates洗牌。

我想建议一种实现您目标的Ruby方法

显然,你不能使用,但(谢天谢地!)可以使用。(我假设两者都不能使用,因为:
arr.sample(arr.size)
具有与
arr.shuffle
相同的效果)

有许多方法可以实现统计上有效的洗牌(假设
rand(n)
产生介于
0
n-1
之间的真正随机数,这当然是不可能的,但这是一个合理的假设)。这里有一个方法:

class Array
  def shuffle
    arr = self.dup
    map { arr.delete_at(rand(arr.size)) }
  end
end
让我们尝试一下:

arr = [4,:a,5,6,'b',7,8]

arr.shuffle #=> [6,   8, "b", 5,   4, :a,   7]
arr.shuffle #=> [5,  :a,   8, 4, "b",  7,   6]
arr.shuffle #=> [6,   8,   5, 7, "b", :a,   4]
arr.shuffle #=> [6,   4,   7, 8,   5, :a, "b"]
arr.shuffle #=> [:a,  4, "b", 5,   7,  8,   6]
arr.shuffle #=> ["b", 4,   7, 8,  :a,  6,   5]

这个问题似乎离题了,因为它属于另一个网站:codereview.stackexchange.com为什么随机生成2个索引?(因此,可能不正确)请参阅wiki文章:@MitchWheat:我相信codereview.stackexchange.com希望问题包含正确的工作代码。(然后答案建议可读性/设计/性能/等方面的改进。)此代码不正确,因此我认为它不在主题中。我根据答案进行了修改,以证明您的算法不会对给定序列进行随机重新排序。您可以定义
数组#sample
,但在您的示例中,您调用了
Array#shuffle
。否则,我喜欢您使用的map和delete_的方法。聪明+1谢谢,@Daniël。我修正了它,还添加了一个证明,证明OP的算法不会产生数组的随机排列。这有点有趣。我的意思是将该方法设置为
shuffle
,但编写了
sample
,然后使用(Ruby的)
shuffle
进行测试。当然成功了!幸运的是,除了名字之外,我似乎写的方法是正确的。谢谢,@ruakh。当然可以。我将删除它,但我仍然希望看到任意大小数组的可靠数学证明。@ruakh,对不起,我的意思是我仍然希望看到一个严格的证明,即对于任意大小的数组,OP的算法不会产生随机序列。@CarySwoveland:你能形式化你希望看到证明的语句吗?通常情况下,如果发现一个反例,一个声明就被认为是不成立的,因此我认为我们已经严格地驳斥了“给定任意长度的数组,OP算法会产生随机洗牌,所有排列的可能性相同”的声明。我们无法反驳较弱的说法,即“给定一个任意长度的数组,OP的算法会产生它的随机洗牌,所有排列都是可能的”,因为较弱的说法事实上是正确的。