Ruby’s Hash can accept a block when you initialize it. The block is called any time you attempt to access a key that is not present. Hash’s initialization block expects the following format:
Hash.new{|hash, key| ... }
The hash
references the hash itself, and the key
parameter is the missing key. With this, you can initialize default values in the hash before they get accessed. Here are a few interesting things you can do with a hash’s initialization block.
By setting the value to an array, you can easily group items in a list:
groups = Hash.new{|h,k| h[k] = [] }
list = ["cake", "bake", "cookie", "car", "apple"]
# Group by string length:
list.each{|v| groups[v.length] << v}
groups #=> {4=>["cake", "bake"], 6=>["cookie"], 3=>["car"], 5=>["apple"]}
Setting the value to 0 is a good way to count the occurrences of various items in a list:
counts = Hash.new{|h,k| h[k] = 0 }
list = ["cake", "cake", "cookie", "car", "cookie"]
# Group by string length:
list.each{|v| counts[v] += 1 }
counts #=> {"cake"=>2, "cookie"=>2, "car"=>1}
Or if you return hashes that return hashes, you can build a tree structure:
tree_block = lambda{|h,k| h[k] = Hash.new(&tree_block) }
opts = Hash.new(&tree_block)
opts['dev']['db']['host'] = "localhost:2828"
opts['dev']['db']['user'] = "me"
opts['dev']['db']['password'] = "secret"
opts['test']['db']['host'] = "localhost:2828"
opts['test']['db']['user'] = "test_user"
opts['test']['db']['password'] = "test_secret"
opts #=> {"dev"=>
{"db"=>{"host"=>"localhost:2828", "user"=>"me", "password"=>"secret"}},
"test"=>
{"db"=>{"host"=>"localhost:2828", "user"=>"test_user", "password"=>"test_secret"}}
}
A block can also be used to create a caching layer:
require 'net/http'
http = Hash.new{|h,k| h[k] = Net::HTTP.get_response(URI(k)).body }
http['http://www.google.com'] # makes a request
http['http://www.google.com'] # returns cached value
In ruby 1.9 hashes are ordered so you can make the cache a fixed length, and evict old values:
http = Hash.new{|h,k|
h[k] = Net::HTTP.get_response(URI(k)).body
if h.length > 3
h.delete(h.keys.first)
end
}
http['http://www.google.com']
http['http://www.yahoo.com']
http['http://www.bing.com']
http['http://www.reddit.com'] # this evicts http://www.google.com
http.keys #=> ["http://www.yahoo.com", "http://www.bing.com", "http://www.reddit.com"]
You can also use it to compute recursive functions:
factorial = Hash.new do |h,k|
if k > 1
h[k] = h[k-1] * k
else
h[k] = 1
end
end
This will cache each result, so if you have computed part of a number’s factorial, it won’t need to compute it again. For instance, factorial[4]
will compute the values for 1,2, and 3, and then if you call factorial[3]
it will already have the result. This is a somewhat contrived use, but it’s interesting none the less.
As you can see the default block for a Hash has a lot of interesting uses, are there any that you find particularly useful?