4

When using Array#shuffle, Ruby allows the use of a custom randomizer and even provides class Random to utilize it. The following example uses the class with a seed value of 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] 

I wrote a small monobit test to see how many times a value appeared first in the shuffled array.

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

The output looks similar to the following:

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

Suppose I want to replace the pseudo-random number generator with a new class. Since Array#shuffle appears to use Random#rand in the above example, it would seem straightforward to implement a new class to act as the RNG for shuffling. Here, I implement a new pseudo-random number generator that actually is just a very simple wrapper around 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

This, however, does not operate as expected. FooRandom#rand is called, but the shuffling produces the following distribution:

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

As you can see, the array value 1 never appears in the first position of the array after the array is shuffled. Anyone have an idea why?

2
  • In ruby-1.9.3-p392 I got the expected distribution with both examples. But in Ruby-2.0.0-p0 I see the same results as you. Commented Apr 20, 2013 at 7:07
  • Thank you, Shawn. Yeah, I am using ruby-2.0.0-p0. Commented Apr 20, 2013 at 8:32

2 Answers 2

4

There is a bug in Ruby 2.0.0p0 where the limit is off by one.

This has been fixed in Ruby 2.0.0p195, so you should upgrade your installation.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for posting that (and reporting the bug.) I suspected it might be a bug and was going to look into that today.
2

In ruby 1.9.3 max is nil. In Ruby 2.0.0 max is passed to rand. I was able to get it to work by changing the rand method to the following.

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

It was off by one before.

Here's an excerpt of the C source from 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;

}

2 Comments

Shawn, that works great! I wonder if the change made to rand from 1.9.3. to 2.0.0 was intentional or perhaps a bug?
@ThaiDoan: It's a bug, see my answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.