Reputation: 1138
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
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