Hashes of Hashes Idiom in Ruby?
Asked Answered
T

2

34

Creating hashes of hashes in Ruby allows for convenient two (or more) dimensional lookups. However, when inserting one must always check if the first index already exists in the hash. For example:

h = Hash.new
h['x'] = Hash.new if not h.key?('x')
h['x']['y'] = value_to_insert

It would be preferable to do the following where the new Hash is created automatically:

h = Hash.new
h['x']['y'] = value_to_insert

Similarly, when looking up a value where the first index doesn't already exist, it would be preferable if nil is returned rather than receiving an undefined method for '[]' error.

looked_up_value = h['w']['z']

One could create a Hash wrapper class that has this behavior, but is there an existing a Ruby idiom for accomplishing this task?

Tubuliflorous answered 4/10, 2008 at 12:10 Comment(1)
Is there a hash of hash idioms that'd return 0 after a certain depth? (I'm counting things and I'm using h[:foo][:bar][:baz] += 1)Balliett
A
54

You can pass the Hash.new function a block that is executed to yield a default value in case the queried value doesn't exist yet:

h = Hash.new { |h, k| h[k] = Hash.new }

Of course, this can be done recursively. There's an article explaining the details.

For the sake of completeness, here's the solution from the article for arbitrary depth hashes:

hash = Hash.new(&(p = lambda{|h, k| h[k] = Hash.new(&p)}))

The person to originally come up with this solution is Kent Sibilev.

Affra answered 4/10, 2008 at 12:16 Comment(5)
Dead link. Impressive solution though.Incunabula
The dead link is switched here inquirylabs.com/blog2009/2006/09/20/…Joon
That link is also now dead :-(Syd
Here's a link to it in the Internet Archive Wayback Machine. At least that should stick around for a while! web.archive.org/web/20090331170758/http://blog.inquirylabs.com/…Margit
@Margit Indeed, thanks, I’ve edited it. For small corrections like this feel free to perform the edit yourself.Affra
G
4

Autovivification, as it's called, is both a blessing and a curse. The trouble can be that if you "look" at a value before it's defined, you're stuck with this empty hash in the slot and you would need to prune it off later.

If you don't mind a bit of anarchy, you can always just jam in or-equals style declarations which will allow you to construct the expected structure as you query it:

((h ||= { })['w'] ||= { })['z']
Gallicism answered 11/4, 2009 at 4:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.