1

I am working through the Ruby Koans(A tutorial project of Ruby). In the About_Dice_Project, it's demanded to create a class named DiceSet. I succeed, but there is a interesting question.

Here's the code :

class DiceSet

  # Attribute reader
  attr_reader :values

  # Initializer
  def initialize
    @values = []
  end

  # Roll method
  def roll(dice_amount)
    @values = Array.new(dice_amount) { rand(1..6) }
  end
end

And this test is interesting :

def test_dice_values_should_change_between_rolls
    dice = DiceSet.new

    dice.roll(5)
    first_time = dice.values

    dice.roll(5)
    second_time = dice.values

    assert_not_equal first_time, second_time,
      "Two rolls should not be equal"
  end

THINK ABOUT IT:

If the rolls are random, then it is possible (although not likely) that two consecutive rolls are equal. What would be a better way to test this?

My idea is to test the object_id of first_time and second_time, using assert_not_equal first_time.object_id, second_time.object_id. It works but am i right ? As a beginner in Ruby and programming, what is an object_id indeed ? By the way, is it possible to justify the text in markdown ?

Any help will be appreciated !

Niko Z.
  • 342
  • 1
  • 11

2 Answers2

1

The currently accepted answer (from Eric Duminil) changes the behavior of the roll method and makes it NOT be random anymore, since two true random consecutive rolls SHOULD be able to be the equal if you are lucky enough.

I believe the change should happen in the test method, by doing something like: rolling once and then doing the other roll in a loop until the values are different, as probabilistically speaking the chance of having several equal consecutive rolls decreases with every roll. Adding a timeout would be good enough for catching an implementation with an actual error.

require 'timeout'

# ...

def test_dice_values_should_change_between_rolls
  dice = DiceSet.new

  dice.roll(5)
  first_time = dice.values
  second_time = dice.values
      
  begin
    Timeout.timeout(1) do
      while true do
        dice.roll(5)
        second_time = dice.values

        break if first_time != second_time
      end
    end
  rescue Timeout::Error
    # when it gets here: first_time == second_time, assert will be done in "ensure"
  ensure
    assert_not_equal first_time, second_time, "Two rolls should not be equal"
  end
end
Thomas H.
  • 653
  • 7
  • 18
-1

object_ids and equality

You shouldn't compare object_ids, but values.

a = [1, 2, 3]
b = [1, 2, 3]

puts a == b
#=> true
puts a.object_id == b.object_id
#=> false

By comparing object_ids, you're testing if variables refer to the exact same object. In your case, first_time and second_time are created independently from each others, so they cannot reference the same object. They can have the same values, though.

Think about it

One way to ensure that no two consecutive rolls are equal would be to use a while loop :

class DiceSet
  # Attribute reader
  attr_reader :values

  # Initializer
  def initialize
    @values = []
    @last_values = []
  end

  # Roll method
  def roll(dice_amount)
    while @values == @last_values
      @values = Array.new(dice_amount) { rand(1..6) }
    end
    @last_values = @values
    @values
  end
end

dice = DiceSet.new

dice.roll(5)
first_time = dice.values

dice.roll(5)
second_time = dice.values # <-- cannot be equal to first_time
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
  • Thanks for your help, but please look at the part "Think about it". If the rolls are random, then it is possible (although not likely) that two consecutive rolls are equal. In this case, the test `assert_not_equal first_time, second_time` will fail, right ? – Niko Z. Feb 01 '17 at 09:59
  • 1
    "If two variables have the same `object_id`" – Variables don't have `object_id`s, only objects do (that's why they are called `object_id`s). Variables aren't objects in Ruby (just like most other languages). – Jörg W Mittag Feb 01 '17 at 10:35
  • 2
    Yes, much better! I know that's what you meant, you know that's you meant, but confusing variables with what those variables point to (i.e. confusing things with labels of things, e.g. confusing a pair of socks with a label that says "pair of socks") is one of the biggest hurdles in understanding imperative programming, I believe. And in my personal opinion, a lot of that is due to imprecise wording in tutorials and books. Let's not repeat that mistake here! (You won't believe the arguments I had about "variables aren't objects", even though both the ISO spec and matz's book explicitly say so) – Jörg W Mittag Feb 01 '17 at 10:49
  • I don't think this is a good way of fixing the "Think about it" section for this problem, as you are changing the behavior of the `roll` method and MAKING it not be random anymore, since in reality you SHOULD BE ABLE to roll twice in a roll and get the exact same result. I believe the change should be in the test method: rolling once and then doing the other roll in a loop until the values are different, as probabilistically speaking the change of having several consecutive rolls being equal decreases at every roll. Adding a timeout would be good (for the case there's an actual error). – Thomas H. Sep 29 '22 at 00:07