Books / Ruby for Beginners / Chapter 13

Random Numbers

There are lots of scientific works about random numbers generation. Think about this: computer is something precise, how it may have truly random numbers? Most personal computers from the last century were generating pseudo-random numbers, and after computer restart these numbers were the same. So the “Sea Battle” game was always predictable, and author of this book learned how to win this game pretty easily, because you already know those “random” location of ships.

Explanation for this behavior is pretty straightforward, computers should rely on random data, and there were no any ways to get this random data from. But in modern operating systems random numbers generator takes into account lots of parameters: delays between pressed keys, mouse movements, network events, and so on. All this information collected from the real world serves as foundation for random number algorithms.

But what if this information is not enough? What if we just turned on our computer, made a couple of mouse movements and want to get the sequence of billions of random numbers? The vector of random numbers is defined by random information from real world, but how many vectors we may have?

It looks like a lot, but in real world things are little bit different. Here is the real life story. One online poker website published code for shuffling cards. They wanted to be transparent with their players about how their software works, and algorithm looked like this:

for i := 1 to 52 do begin
  r := random(52) + 1;
  swap := card[r];
  card[r] := card[i];
  card[i] := swap;
end;

It looks pretty straightforward, but this tiny program has four bugs. First bug is that r variable will never equal to 0, and it’s used as index (we’ll cover indexes later in this book). So one card is always at the same position while shuffling. Second bug is that shuffle isn’t uniform (see Fisher-Yates shuffle for details). Ruby Array object has built-in uniform method for shuffling.

But the main bug is that random() call uses 32-bit seed value. It means that there is capacity for “only” 2 in the power of 32 unique combinations (~4 billion). And since underlying implementation of random() is using the number of milliseconds from the midnight, it gives only 86.4 predictable combinations. This number looks big, but the real number of possible combinations in real life is factorial of 52 (many many times more than that). So after synchronization and first five cards it was possible to predict the rest of the sequence.

Example above just shows vulnerability of algorithm implemented on the real website. So if you’re working on something important and you need to deal with random numbers, you should always look for reliable and proven ways to deal with randomness. The ideal solution for any computer would be special device that generates such numbers, but for our purposes we should be good with built-in Ruby methods. These methods are using operating system under the hood, and “reliable enough”. Here is how you can generate random numbers in Ruby:

$ irb
> rand(1..5)
4
> rand(1..5)
1

We already know how to pass parameters to any function in Ruby, there are two ways:

  • with parentheses, like: puts('hello')
  • or without parentheses, like: puts 'hello'

Code above is passing “tricky” parameter “1..5” called Range (type of object). With this syntax we specify “from” and “to” values. In other words, we’re telling Ruby “generate random number from 1 to 5”. But the trick is that we don’t pass two parameters, but only one. If you pass two, you’ll get the error:

$ irb
> rand(1, 5)
ArgumentError: wrong number of arguments (given 2, expected 0..1)

But what exactly is 1..5? Let’s check it in REPL as we did it before for other objects:

$ irb
> (1..5).class
=> Range

Now we know what it is. It’s just a class in Ruby responsible for the range, and the name if this class is Range. This class is quite useful, and documentation has lots of interesting examples. But let’s make sure it’s not a kind of magic, and we can initialize this class like any other variable:

$ irb
> x = 1..5
=> 1..5
> rand(x)
=> 4

We initialized variable x, assigned value, and we can pass one parameter to rand function. Now we have proof that rand accepts one parameter. Now let’s combine rand and sleep:

$ irb
> sleep rand(1..5)

One-line program above will delay for the random number of seconds, from 1 to 5. As you can see, this tiny program sleep rand(1..5) has three parts:

  • sleep
  • rand
  • 1..5

And can be written the with or without parentheses. All lines below are identical:

$ irb
> sleep rand(1..5)
> sleep rand 1..5
> sleep(rand(1..5))

Last line illustrates what was said before, and what programmer wants from Ruby:

  • Execute 1..5 first, get the value, because we need it for the rand call
  • Execute rand with result of 1..5
  • Execute sleep with result of rand (random number from specified range)

It’s up to a programmer to use parentheses or not. But to avoid mess it’s recommended to use Rubocop, so your style won’t be too much different from other team members.

It’s also worth mentioning that you can calculate random fractional numbers with rand:

$ irb
> rand(0.03..0.09)
=> 0.03920647825951599
> rand(0.03..0.09)
=> 0.06772359081051581

Exercise 1 Look at documentation about Range class.

Exercise 2 Write a program that will print random number from 500 to 510

Exercise 3 Write a program that will print random fractional number from 0 to 1. For example, 0.54321 or 0.123456.

Exercise 4 Write a program that will print random fractional number from 2 to 4.

Guess The Number Game

Guess the Number is a fun game that challenges user to find a number based on greater than or less than feedback. Let’s do it simple first, and then we’ll improve our solution.

number = rand(1..10)
print 'Hi! I picked the number from 1 to 10, try to guess: '

loop do
  input = gets.to_i

  if input == number
    puts 'You guessed it!'
    exit
  end

  if input != number
    print 'Nope, try again: '
  end
end

At this point you should have enough knowledge to understand the program above. Try to read the program and understand how exactly it works. Congratulations if you understand everything, it’s a huge progress! Here is the sample output:

Hi! I picked the number from 1 to 10, try to guess: 6
Nope, try again: 3
Nope, try again: 8
Nope, try again: 9
You guessed it!

First line is where we pick the random number and save the value to number variable. On line 4 we define infinite loop with loop do...end syntax. We define variable input inside of loop block. Input represents the user input.

User input is the type of Integer, so we have the right to compare this input to picked number. There won’t be any error if you compare two different types, but result of such comparison will be always “false”. Despite the fact that we run infinite loop, we can exit from a program with exit keyword. We do it on line 9 which gets executed only when condition input == number is met.

At this point of time we don’t know how to define our own methods (or functions), so we use exit keyword to quit the program. But with more Ruby knowledge you can improve program and add more features, like asking a user if she or he wants to play again.

Second if-block (defined on lines 12-14) has “if user input is not equal to picked number” test. Keep in mind that we use print instead of puts because print doesn’t move cursor on the next line (if you don’t understand it, run this program and compare two versions - with print and puts).

But this simple program can be refactored (improved):

number = rand(1..10)
print 'Hi! I picked the number from 1 to 10, try to guess: '

loop do
  input = gets.to_i

  if input == number
    puts 'You guessed it!'
    exit
  else
    print 'Nope, try again: '
  end
end

We combined two independent if-blocks into one if-else block. Indeed, why do we need two blocks when we combine it into one when we have only two possible flows: number guessed and number not guessed.

Exercise Modify the program above so it picks the number from 1 to 1_000_000 (one million, you can use underscore for numbers in Ruby to improve readability of your program). But guessing this huge number can be a difficult task. So add a hint every time guess is incorrect: if picked number is greater than user input, show “picked number is greater than what you have typed” message; if picked number is less than user input, show “picked number is less than what you have typed”. Math says that you’ll need not more than 20 attempts to guess any number this way. Check it yourself!


Licenses and Attributions


Speak Your Mind

-->