Violet Shreve
Violet Shreve

Reputation: 1138

Ruby random number generator is skewed when called from initializer

I'm trying to model the Monty Hall problem where a user is asked to select one of three doors, then is offered to switch the door. I'm looking for a random variation of choices, which should be in the range of 50% switched vs. 50% stayed.

class Scenario
  def initialize
    # switched?
  end

  def switched?
    @sw ||= [true, false].sample
  end
end

results = { switched: [], stayed: [] }

1000.times do
  s = Scenario.new

  if s.switched?
    results[:switched].push(s)
  else
    results[:stayed].push(s)
  end
end

puts results[:switched].count
puts results[:stayed].count

When I inspect the results hash, in this example, the array counts tend to be about 500/500 as expected.

However, if I uncomment switched? in the initializer method, the results tend to be split about 750/250. The results are still random-looking (e.g. 738 to 262), but they are always skewed to be an incorrect solution to this problem.

I have also tried using other mechanisms like rand(2).zero? to generate the data, but the same problem occurs.

Why or how could calling this memoized function in the initializer cause the random variation to be so far, yet consistently off?

Upvotes: 3

Views: 211

Answers (1)

Tom Lord
Tom Lord

Reputation: 28305

In ruby, all values other than nil and false are considered "truthy".

When you call foo ||= bar, bar will be evaluated if and only if foo is "falsey" - i.e. equal to nil or false. (Or if it's undefined!)

In your code, you have the following:

def switched?
  @sw ||= [true, false].sample
end

So, the @sw variable will memoize the result of the method call only if [true, false].sample returns true!!

What this means, then, is that if you call switched? multiple times, you are giving the @sw variable "multiple attempts" to randomly choose true.

If you call switched? once, there's a 50% chance of it being true. Call it twice, and there's a 75% chance (as you observed). Call it 3 times, and there's a 87.5% chance. And so on.

In order to memoize a potentially false value, you need to be a little more explicit with the syntax - e.g.

def switched?
  return @sw if defined?(@sw)
  @sw = [true, false].sample 
end

You can now call switched? multiple times, safely. It will remember its first result, even if false, and not re-calculate it.

Upvotes: 5

Related Questions