How to use Hash.new with a block

by Jared Norman
published October 03, 2019

In Ruby you can create a hash with a static default value. It’s often useful if you’re counting something, like the number of each kind of dog you’ve seen while out for a walk:

dog_counts = Hash.new(0)
# => {}

[
  "doberman",
  "dachshund",
  "doberman",
  "doberman",
  "whippet",
  "labrador"
].each do |dog_seen|
  dog_counts[dog_seen] += 1
end

dog_counts
# => {"doberman"=>3, "dachshund"=>1, "whippet"=>1, "labrador"=>1}

In that example, using a default value of 0 allowed us to skip the checks to see if we’d already seen the dog or not.

Sometimes this isn’t enough and you need to dynamically set the default values. This can still be done elegantly by passing a block to Hash.new. The block receives the hash itself and the key that’s being requested. This allows you to do something like this:

h = Hash.new do |hash, key|
  if FORBIDDEN_KEYS.include?(key)
    nil
  else
    10
  end
end

h["any value"]
# => 10
h["a forbidden key"]
# => nil

There’s one gotcha, though. If you want to store the value, that’s on you.

h = Hash.new { |hash, key| 3 }
# => {}

h[:foo]
# => 3

h
# => {}

As you can see, the hash doesn’t end up storing the :foo key. In some cases this won’t matter, perhaps because you’re setting the keys yourself, as we did in the first example with dog_counts[dog_seen] += 1, which is equivalent to dog_counts[dog_seen] = dog_counts[dog_seen] + 1.

If you need to persist the value, you’d do something like this:

h = Hash.new { |hash, key| hash[key] = 3 }
# => {}

h[:foo]
# => 3

h
# => {:foo=>3}

I find that using hashes like this can sometimes allow for really clean refactorings of tricky to follow algorithms. I hope this helps you!