使用Ruby Array #shuffle / sample的自定义随机数生成器

时间:2013-04-20 06:45:08

标签: ruby

使用Array #shuffle时,Ruby允许使用自定义随机函数,甚至提供类Random来使用它。以下示例使用种子值为48的类。

array = [1,2,3,4,5,6,7,8,9,10]
array.shuffle(random: Random.new(48))  # => [8,6,3,7,10,9,5,2,4,1] 

我写了一个小的单比特测试,看看一个值在洗牌数组中出现的次数。

deck = (1..10).to_a
counts = Hash.new(0)

rng = Random.new

50000.times do
  counts[deck.shuffle(random: rng).first] += 1
end

1.upto(10) do |card|
  puts "#{card}:\t#{counts[card]}"
end

输出类似于以下内容:

1:  4942
2:  5100
3:  4938
4:  4960
5:  5024
6:  4992
7:  5184
8:  4930
9:  4916
10: 5014

假设我想用新类替换伪随机数生成器。由于在上面的例子中,Array #shuffle似乎使用Random#rand,因此实现一个新的类来充当用于混洗的RNG似乎很简单。在这里,我实现了一个新的伪随机数生成器,它实际上只是rand的一个非常简单的包装器:

deck = (1..10).to_a
counts = Hash.new(0)

class FooRandom
  def rand(max=nil)
    max.nil? ? Kernel::rand : Kernel::rand(max)
  end
end

rng = FooRandom.new

50000.times do
  counts[deck.shuffle(random: rng).first] += 1
end

1.upto(10) do |card|
  puts "#{card}:\t#{counts[card]}"
end

然而,这并不像预期的那样运作。调用FooRandom#rand,但改组产生以下分布:

1:  0
2:  5423
3:  5562
4:  5544
5:  5512
6:  5569
7:  5535
8:  5595
9:  5524
10: 5736

正如您所看到的,在数组被洗牌后,数组值1永远不会出现在数组的第一个位置。任何人都知道为什么?

2 个答案:

答案 0 :(得分:4)

有一个bug in Ruby 2.0.0p0,其中限制是一个。

这已在Ruby 2.0.0p195中修复,因此您应升级安装。

答案 1 :(得分:2)

在ruby 1.9.3中,max是零。在Ruby 2.0.0中,max传递给rand。我可以通过将rand方法更改为以下内容来使其工作。

class FooRandom
  def rand(max=nil)
    max.nil? ? Kernel::rand : Kernel::rand(max+1)
  end
end

之前它被一个人关闭了。

以下是C源代码的摘录 http://ruby-doc.org/core-2.0/Array.html#method-i-shuffle

i = RARRAY_LEN(ary);
ptr = RARRAY_PTR(ary);
while (i) {
    long j = RAND_UPTO(i);
    VALUE tmp;
    tmp = ptr[--i];
    ptr[i] = ptr[j];
    ptr[j] = tmp;
}
return ary;

}