0

I'm trying to solve a problem where I want to use hash to record the indices of each character appeared in the array.

def dupe_indices(arr)
  hash = Hash.new {|h,k| []}
  arr.each.with_index do |char,idx|
    hash[char] << idx
  end
  return hash
end

Weird thing is that, despite hash[char] will initialize as empty array, hash[char] << idx won't work. The hash will end up empty: {}.

I can fix the issue by either initializing the hash using Hash.new {|h,k| h[k] = []} or changing hash[char] << idx to hash[char] = hash[char].push[idx] for assignment.

smilence
  • 349
  • 4
  • 9

2 Answers2

3

I can fix the issue by either initializing the hash using Hash.new {|h,k| h[k] = []} or [...]

That's actually the correct way to use the block variant. From the docs for Hash.new:

"It is the block’s responsibility to store the value in the hash if required."

The block is called when accessing missing values via Hash#[]. If you don't store the value, the hash remains empty. So the next time you attempt to access this value, it will still be missing and the block will be called again:

hash = Hash.new { rand }

hash[:foo] #=> 0.9548960551853385
hash[:foo] #=> 0.7535154376706064
hash[:foo] #=> 0.007113200178872958
hash[:foo] #=> 0.07621008793193496

hash #=> {}

The same happens for your Hash.new { [] } attempt – you'll get a fresh array every time you call hash[char] because the array is never stored in the hash. Your code is equivalent to:

def dupe_indices(arr)
  hash = {}
  arr.each.with_index do |char, idx|
    [] << idx  # <- obviously doesn't do anything useful
  end
  return hash
end
Stefan
  • 109,145
  • 14
  • 143
  • 218
  • thx @Stefan, this explains. It's so different from how `Array.new` or `Proc.new` works as these 2 you dont have to store the value in block. And also I tried the following `hash = Hash.new([])` it gives me an empty hash as well – smilence Feb 12 '20 at 17:52
  • @JamesYu well, arrays don't have a default value – `Array.new` creates a fixed-size array and fills it using the block's result. And procs aren't collections so there's nothing to store. – Stefan Feb 12 '20 at 17:58
  • yeah im just thinking why Hash isn't implemented such that it take a block like `hash = Hash.new { |key| foo(key) }` and `hash#[]` can just take the output and assign to `hash[key]`. – smilence Feb 12 '20 at 18:11
  • @JamesYu maybe because it would be a little less flexible. There could be use cases where you don't want to store the value. – Stefan Feb 12 '20 at 21:10
1

It does work when hash[char] << idx is replaced by hash[char] <<= idx.

steenslag
  • 79,051
  • 16
  • 138
  • 171
  • yeah I found this and it's very detailed: https://stackoverflow.com/questions/2698460/strange-unexpected-behavior-disappearing-changing-values-when-using-hash-defa – smilence Feb 12 '20 at 21:41