Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby #detect behaviour with random numbers

Tags:

random

ruby

In the following Ruby code I have two methods d100_in_detect and d100_out_detect which return an Array of unique elements (numbers for simplicity) contained in ary according to the result of d100.

def d100
  1 + ( rand 100 )
end

def d100_in_detect( ary )
  choice = [ ]
  100.times do
    choice.push ary.detect { |el| d100 <= el }
  end
  choice.uniq.sort
end

def d100_out_detect( ary )
  choice  = [ ]
  numbers = [ ]

  100.times do
    numbers.push d100
  end

  numbers.each do |i|
    choice.push ary.detect { |el| i <= el }
  end

  choice.uniq.sort
end

As you can see the difference between the two methods is that in the first d100 is called inside detect's block, while in the second 100 random numbers are stored in numbers Array and then are used as it happens in d100_in_detect.

Let's suppose I call the two methods as follows

ary = [ ]
50.times do |i|
  ary.push i * 5 
end

puts '# IN DETECT #'
print d100_in_detect ary
puts

puts '# OUT DETECT #'
puts d100_out_detect ary
puts

A typical output is the following.

# IN DETECT #
[ 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 ]
# OUT DETECT #
[ 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100 ]

I can't figure out why the two methods return such different results. Is there any implication in calling d100 method in detect's block?

like image 596
dave Avatar asked Nov 03 '22 05:11

dave


1 Answers

Right.

First thing I did was to modify your sample script:

def d100_in_detect( ary )
  choice = [ ]
  numbers = []
  100.times do
    var = d100
    numbers << var
    choice.push ary.detect { |el| var <= el }
  end
  puts numbers.inspect
  choice.uniq.sort
end

def d100_out_detect( ary )
  choice  = [ ]
  numbers = [ ]

  100.times do
    numbers.push d100
  end
  puts numbers.inspect

  numbers.each do |i|
    choice.push ary.detect { |el| i <= el }
  end

  choice.uniq.sort
end

As you can see, all I did was assign the result of the d100 to a temporary variable to see what was happening....and the 'bug' went away! My return values were suddenly identical. Hmm.

Then, it occurred to me exactly what was going on. When you 'cache' the variable (as you do in the second example), you guarantee you have a spread of 100 numbers.

When you iterate through the block, for each number in the block, you execute the d100 again. Hence, the first one has vastly more calls than the second one...but you also require the randomly generated number at the time that number was called to be greater than the number (whereas, if you randomly generate 100 with 2, you can guarantee it will hit the 100 at some point). This drastically bias's your script towards the lower numbers!

For an example, run:

@called_count = 0
def d100
  @called_count += 1
  1 + ( rand 100 )
end

def d100_in_detect( ary )
  choice = [ ]
  numbers = []
  100.times do
    choice.push ary.detect { |el| d100 <= el }
  end
  puts @called_count.inspect
  @called_count = 0
  choice.uniq.sort
end

def d100_out_detect( ary )
  choice  = [ ]
  numbers = [ ]

  100.times do
    numbers.push d100
  end
        puts @called_count.inspect
  @called_count = 0

  numbers.each do |i|
    choice.push ary.detect { |el| i <= el }
  end

  choice.uniq.sort
end

I get

# IN DETECT #
691
# OUT DETECT #
100
like image 142
Joe Pym Avatar answered Nov 08 '22 21:11

Joe Pym