2

I'm working through the Ruby Koans in order to get a better grasp on Ruby and TDD. I got to Line 93 in the code in about_hashes.rb and this confuses me how it would be shoveled into the default constructor instead of the hash value. Just out of curiosity I tried using the same thing with a string as the parameter of the constructor and it produced a similar result.

Now my question is why, whatever key I use, the same object is retrieved and how would I shovel a new object into the array at a specific key in a hash in the method test_default_value_is_the_same_object?

def test_default_value_is_the_same_object
    hash = Hash.new([])

    hash[:one] << "uno"
    hash[:two] << "dos"

    assert_equal ["uno", "dos"], hash[:one] #why not ["uno"]?
    assert_equal ["uno", "dos"], hash[:two] #why not ["dos"]?
    assert_equal ["uno", "dos"], hash[:three] #why not []?

    assert_equal true, hash[:one].object_id == hash[:two].object_id
  end

def test_default_value_with_block
    hash = Hash.new {|hash, key| hash[key] = [] }

    hash[:one] << "uno"
    hash[:two] << "dos"

    assert_equal ["uno"], hash[:one]
    assert_equal ["dos"], hash[:two]
    assert_equal [], hash[:three]
end
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Scott
  • 1,154
  • 1
  • 12
  • 25
  • "how would I shovel a new object into the array hash[:one]" - huh? like you do above. `hash[:one] << "uno"`. After that, `hash[:one]` _will_ have `"uno"`. Or were you asking something else? – Sergio Tulentsev Oct 11 '17 at 20:51
  • https://www.ruby-forum.com/topic/4409379 – Sergio Tulentsev Oct 11 '17 at 20:53
  • @SergioTulentsev I'm asking why the array at hash[:one] is the same array at hash[:two] – Scott Oct 11 '17 at 21:01
  • 1
    @marmeladze: `ruby-koans` is a meta tag, please don't create such tags, we burninated the tag for a reason, see [Burninate Project Euler](//meta.stackoverflow.com/q/275935) – Martijn Pieters Nov 27 '17 at 12:02

2 Answers2

4

A hint as to the reason is in the name of the tests.

test_default_value_is_the_same_object is there to show you that when you ask for hash[:some_value_that_doesnt_exist_yet], by default, you get back the default value you specified -- which is the same object every time. By modifying that object, you modify it for every nonexistent key. Modifying hash[:one] also modifies hash[:two].

test_default_value_with_block shows the construction of a Hash using a block, which will be used to provide a new value for each key. When you do it like that, the values for hash[:one] and hash[:two] are distinct.

cHao
  • 84,970
  • 20
  • 145
  • 172
  • Thanks, I completely spaced about the keys not even being assigned anything. I tried that and printed the value and it worked like I expected. Very clear answer. – Scott Oct 11 '17 at 21:13
2

You have created a new Hash with an Array as an accumulator when the key is not present so

hash = Hash.new([]) 
hash[:one] << "uno" 
hash[:one] == ["uno"] #=> true

but

hash[:two] << "dos" 
(hash[:one] == hash[:two]) && (hash[:two] == ["uno","dos"]) #=> true
hash[:three] == ["uno","dos"] #=> true

because ["uno","dos"] is the original array created with the Hash and hash[:non_existant_key] points to it.

Note: no keys have actually been added to hash in this case. It would be similar to

 a = []
 hash = {}
 hash.fetch(:one,a) << 'uno'
 hash.fetch(:two,a) << 'dos'
 hash.fetch(:three, a) 
 #=> ['uno','dos']
 hash
 #=> {}

To solve this you can use this syntax as mentioned in your second test

hash = Hash.new {|h,k| h[k] = [] } 

This means fro every new key instantiate a new Array as its value rather than reusing the same Array over and over again

This is the problem with the Hash.new(obj) syntax when obj is a mutable object

engineersmnky
  • 25,495
  • 2
  • 36
  • 52