In Progress
Unit 1, Lesson 1
In Progress

# Random Seed

Video transcript & code

In earlier episodes, we've talked about how to get random numbers from Ruby, and how to ensure that they are truly, cryptographically random. But as contradictory as it may sound, sometimes we need random numbers to behave more predictably.

For instance, let's say we're playing around with some code that generates random mazes.

Every time we run it we get a different maze.

``````require "./maze"

puts Maze.new(width: 10, height: 10).to_s
# => nil

# >> ---------------------
# >> |           |     | |
# >> - --------- --- - - -
# >> |   |       |   |   |
# >> - - - ------- ----- -
# >> | | | |     | |   | |
# >> - --- - --- - - - - -
# >> |   |   | | | | | | |
# >> - - ----- - - - --- -
# >> | |       | | | |   |
# >> - ----- - - - - - ---
# >> | | |   | | | | | | |
# >> - - - ----- - - - - -
# >> |   |   |   | |   | |
# >> - ----- - --- - --- -
# >> | |     | |   | |   |
# >> - - ----- --- - --- -
# >> | |     | |   |   | |
# >> - ----- - - ----- - -
# >> |     |     |       |
# >> ---------------------
``````

But what if we see a particularly interesting maze and want to be able to generate it over again? Maybe we want to step through the code in order to understand how that particular design came about. How can we reproduce a random creation?

The answer is that we need to make use of random seeds. As we talked about previously, ruby's standard pseudorandom number generator is seeded with a strongly random number from the system's entropy source. But after that, it proceeds forward mechanically. The algorithm produces a nice even distribution without identifiable patterns. But given the same seed value, it will always produce the same sequence.

The system's default random number generator is contained in a constant called Random::DEFAULT.

``````Random::DEFAULT                 # => #<Random:0x007f9c6c8fb7f8>
``````

We can ask this object what it's initial seed was. As we can see, it is different every time we run Ruby.

``````Random::DEFAULT.seed            # => 130422514844656851045348920844218973837
``````
``````Random::DEFAULT.seed            # => 132475933376449586508660641321277652157
``````

But not only can we ask, we can also provide a random seed, using the `Kernel#srand` method. When we seed the system default random number generator with the same number twice, and then ask for some random numbers, we get the same sequence of picks each time.

``````srand(12345)
rand(10)                        # => 2
rand(10)                        # => 5
rand(10)                        # => 1
rand(10)                        # => 4

srand(12345)
rand(10)                        # => 2
rand(10)                        # => 5
rand(10)                        # => 1
rand(10)                        # => 4
``````

So far we've been using the system default random number generator. But we don't have to use the singleton system generator if we don't want to. And that's a good thing, because when we re-seed the default random number generator, we might be interfering with other, unrelated code which also relies on random numbers.

Instead, we can create our own random number generators, and provide a seed as we create them. As you can see here, given the same seed we used above, it produces the same sequence of numbers.

``````rng = Random.new(12345)
rng.rand(10)                    # => 2
rng.rand(10)                    # => 5
rng.rand(10)                    # => 1
rng.rand(10)                    # => 4
``````

Back in episode #306, we met #sample method on arrays. Now that you know a little bit more about how random number generation works, you might be wondering where it gets its random-ness from.

Well, by default it uses the system default number generator. But we can also provide our own generator as an optional keyword argument.

``````[1, 2, 3, 4, 5, 6, 7, 8, 9].sample # => 3

rng = Random.new(12345)

[1, 2, 3, 4, 5, 6, 7, 8, 9].sample(random: rng) # => 3
``````

Let's go to our maze generator now. Let's say we want to be able to reproduce mazes by optionally passing in our own seed value. We can enable this with some small changes.

First, we add a new, optional initialization parameter called `seed`. We make it default to `Random.new_seed`, which is the usual default for a random number generator when we don't provide a seed.

We add a new instance variable, and store a new random number generator in it, using the seed to initialize it.

The actual maze generation method uses `Array#sample` to pick where to move next. We update it to pass the `@rng` instance variable into the `#sample` method. Now instead of using the system default random number series, it's using one that's specific to this maze generator.

Finally, we add some extra code to the `#to_s` method to print the current random seed with every maze generated.

Now when we generate a new maze, we also see the random seed that created it. If we see a maze we like, we can take the random seed from one output, and pass it in again and get the exact same maze.

``````require "./maze"

class Maze
# ...
def initialize(width:, height:, seed: Random.new_seed)
@width  = width
@height = height
@cells  = {}
@walls  = Hash.new do |h, (cell1, cell2)|
if cell1.nil? || cell2.nil?
SideWall.new
elsif h.key?([cell2, cell1])
h[[cell2, cell1]]
else
h[[cell1, cell2]] = Wall.new
end
end
@rng = Random.new(seed)
populate
generate
end

# ...

def generate
stack = []
stack.push cell_at(0,0)
i = 0
until cells.values.all?(&:visited?)
if stack.last.unvisited_neighbors.any?
next_cell = stack.last.unvisited_neighbors.sample(random: @rng)
stack.push next_cell
next_cell.visited = true
wall_between(stack[-2], stack[-1]).open = true
else
stack.pop until stack.last.unvisited_neighbors.any? || stack.empty?
end
i += 1
end
end

# ...

def to_s
result = ""
result << north_wall_string << "\n"
each_row do |row|
result << row_string(row) << "\n"
result << row_south_wall_string(row) << "\n"
end
result << "Seed: #{@rng.seed}\n"
result
end
end

puts Maze.new(width: 10, height: 10, seed: 48857389216179225828269858709490177991).to_s

# >> ---------------------
# >> |             |     |
# >> - - ------- - - - ---
# >> | | |   |   | | |   |
# >> - - - - - --- ----- -
# >> | | | |   |         |
# >> - - - ------------- -
# >> | | | |       |     |
# >> - --- - ----- - -----
# >> |   | |     | |   | |
# >> --- - ----- - --- - -
# >> | | |       | | | | |
# >> - - - ------- - - - -
# >> |   | |       |   | |
# >> - --- - ------- --- -
# >> | |   |   |   | |   |
# >> - ------- - - - - - -
# >> |   |   |   | | | | |
# >> --- - - ----- - --- -
# >> |     |       |     |
# >> ---------------------
# >> Seed: 48857389216179225828269858709490177991
``````

One place we can see this technique in use is in the RSpec testing framework. Here are three RSpec examples, which simply print their own name.

``````require "rspec"

RSpec.describe "stuff" do
it "example 1" do
puts "example 1"
expect(1 + 1).to eq(2)
end

it "example 2" do
puts "example 2"
expect(2 + 2).to eq(4)
end

it "example 1" do
puts "example 3"
expect(3 + 3).to eq(6)
end
end
``````

RSpec makes it possible to run tests in random order, using a command-line option. This is useful for discovering cases where tests are unintentionally dependent on each other.

``` \$ rspec examples.rb --order rand

Randomized with seed 46127
example 3
.example 1
.example 2
.

Finished in 0.0011 seconds (files took 0.10511 seconds to load)
3 examples, 0 failures

Randomized with seed 46127

\$ rspec examples.rb --order rand

Randomized with seed 8853
example 1
.example 2
.example 3
.

Finished in 0.00103 seconds (files took 0.10607 seconds to load)
3 examples, 0 failures

Randomized with seed 8853

\$ rspec examples.rb --order rand

Randomized with seed 13947
example 2
.example 3
.example 1
.

Finished in 0.00109 seconds (files took 0.10685 seconds to load)
3 examples, 0 failures

Randomized with seed 13947
```

Of course, if we find such a case, we'd like to be able to repeat it so we can debug the problem. So RSpec prints out the random seed it used after each randomized run.

If we see a run that we want to repeat, we can just supply the same seed back to RSpec, using the `--seed` option.

```\$ rspec examples.rb --seed 13947

Randomized with seed 13947
example 2
.example 3
.example 1
.

Finished in 0.00104 seconds (files took 0.10577 seconds to load)
3 examples, 0 failures

Randomized with seed 13947
```

So there you go: now you know how to give Ruby objects their own private random number generators, and how to reproduce specific random sequences, using a seed. Happy hacking!