Wed, 12 Jul 06

Ruby hashes with default values

I found that on several occasions I’ve wanted to create a hash that increments the number of instances of a given key. We could do something manual like.

hash = Hash.new
def increment_count(hash, key)
  hash[key] ||= 0 # Sets any new keys to 0.
  hash[key] += 1
end

This is fine; but can be written more concisely. If we supply an object to Hash::new then that object becomes the default value for all missing keys in the hash. The above example is shortened by one line.

hash = Hash.new(0)
def increment_count(hash, key)
  hash[key] += 1
end

A problem arises when we want to use an object that isn’t immutable (in this case, basically anything other than a Fixnum) as the default value. The example that I came across a couple of times today was using an empty array as the default value. If we try to use the same method as above, we experience problems.

hash = Hash.new(Array.new)
hash[:key_one]
=> []
hash[:key_two] << 99
=> [99]
hash[:key_one]
=> [99] # is actually the same array amended in the step above

I’ve known that there was a way around this for a long time but have never bothered to look it up. I finally got round to looking at it today, and it is, unsurprisingly, very simple. We supply the hash constructor with a block. This block is called for any missing key and supplied with the hash and said key. The example above becomes.

hash = Hash.new { |hash, key| hash[key] = Array.new }
hash[:key_one]
=> []
hash[:key_two] << 99
=> [99]
hash[:key_one]
=> []

I say I looked it up today but to be fair there’s every chance that I was well aware of this at some point in the past…