0

Why is this hash:

test_hash = Hash.new{|hash, key|
              Hash.new{|second_level_hash, second_level_key| 0 }
            }

not updated by the following operation?

test_hash[1][1] += 1
test_hash[1][1] # => 0
sawa
  • 165,429
  • 45
  • 277
  • 381
Favourite Onwuemene
  • 4,247
  • 8
  • 28
  • 46

2 Answers2

4

You're not actually assigning the value to the Hash, you're just returning a Hash and an independent 0 value. These get modified, then thrown away.

Fix this by performing an assignment:

test_hash = Hash.new { |h,k|
  h[k] = Hash.new(0)
}

You can tell something was wrong because after accessing test_hash[1][1] then calling test_hash.inspect it's still empty.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • 1
    The `Hash.new` block is called each time a new key is populated and while you're not obligated to do anything to the hash, usually it's a good idea to alter it. Unless you call `h[k] = ...` or something like that your values are independent of the hash and aren't saved. – tadman May 04 '16 at 22:43
  • Thanks! Why does this work? test_hash = Hash.new{|h, k| [] } test_hash += ['something'] – Favourite Onwuemene May 04 '16 at 22:45
  • I get "undefined method `+' for {}:Hash (NoMethodError)", which is not surprising. That shouldn't work. – tadman May 04 '16 at 22:50
  • 1
    If you mean `test_hash[1] += [ 'test' ]` then I presume it's because this also calls `test_hash[1] = [ 'test' ]` which forces a direct assignment. Your two level version can't benefit from this quirk. – tadman May 04 '16 at 22:51
  • Typo, I meant: test_hash = Hash.new{|h, k| [] } test_hash[1] += ['something'] – Favourite Onwuemene May 04 '16 at 22:52
  • 1
    Note that `test_hash[1] << 'test'` doesn't work because the assignment operator is not involved. Your implementation only works by coincidence. The correct way to do it is to force an assignment in the block, that way values are always nailed down. – tadman May 04 '16 at 22:52
  • Hope that explains it! Sometimes stuff in Ruby sort of kind of not quite works and it can be really confusing if you don't know the reason why. – tadman May 04 '16 at 22:54
  • 1
    The inner hash construction can simply be `Hash.new(0)`. – Andrew Marshall Sep 03 '16 at 14:46
  • @AndrewMarshall Not sure why I didn't do that in the first place. Adjusted. – tadman Sep 03 '16 at 23:33
2

tadman's answer is (halfway) correct, but may be a bit misleading.

You are assigning a value to the embedded hash, but not assigning the embedded hash to the main hash. Each time a key is called in the main hash, a new embedded hash is created. After you have assigned a value to the embedded hash, the embedded hash that has become {1 => 1} is not assigned to the main hash, and is thrown away.

sawa
  • 165,429
  • 45
  • 277
  • 381