2

From this SO answer, I can create hash values on the fly that are nested:

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

So for example:

hash = Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) }
hash['a']['b']['c'] = { 'key' => 'value' }
#=> {'a' => { 'b' => { 'c' => { 'key' => 'value' }}}}

So far so good.

I need this:

hash = Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) }
hash['a', 'b', 'c'] = { 'key' => 'value' }
#=> {'a' => { 'b' => { 'c' => { 'key' => 'value' }}}}

I'd prefer that it preserves any other hash values present in the hierarchy, and create new hashes as needed.

I'm fairly new to metaprogramming in ruby, andy help is appreciated.

3
  • What would "I'd prefer that it preserves any other hash values present in the hierarchy" mean? Commented Mar 22, 2013 at 1:58
  • 1
    The Hash#[] (element reference) method has an arity of 1. That is, it only accepts a single argument, and can't accept multiple arguments as you wrote it: hash['a', 'b', 'c'] = .... If you pass in an array, like so hash[['a', 'b', 'c']] = ... then the default value block won't be called since you're not referencing a non-existent key, you're directly assigning a value with a given key ['a', 'b', 'c'] which just happens to be an array. Commented Mar 22, 2013 at 2:00
  • AlistairIsrael - How would I use ['a', 'b', 'c'] as hash['a']['b']['c']? That would solve my issue. I need a way to do this programmatically. Commented Mar 22, 2013 at 2:04

2 Answers 2

4

One way to accomplish what I think you want is given the same auto-vivifying hash technique, and without having to redefine Hash#[] is to simply traverse the hash (going deeper) given each element of the desired array. Most idiomatic way I can think of is to use Array#inject:

# auto-vivifying hash
hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc)  }

# array of keys
keys = ['a', 'b', 'c']

# "Injection", a.k.a, "go deeper" ;)
keys.inject(hash) {|h, k|
  h[k]
}['key'] = 'value'   # assign 'value' to 'key' on last/deepest hash

p hash               # {"a"=>{"b"=>{"c"=>{"key"=>"value"}}}}

Not quite the same as hash['a', 'b', 'c'] (since that's a syntax error) but I gather it's what you need.

Sign up to request clarification or add additional context in comments.

1 Comment

Exactly what I needed, despite my poorly asked question you discerned what I needed. Thanks!
1

This is the closest I've got, without knowing exactly what would be meant by "I'd prefer that it preserves any other hash values present in the hierarchy". For some reason the return value on the method isn't perfect, but I'm a bit too tired to work out why (any comments on why are welcome).

class MyH < Hash
  def []= (*keys, value)
    self.merge! keys.reverse.inject(value){|mem,obj| {obj => mem } }
    self
  end
end

hash = MyH.new
# => {}
hash['a', 'b', 'c'] = { 'key' => 'value' }
# => {"key"=>"value"}
hash
# => {"a"=>{"b"=>{"c"=>{"key"=>"value"}}}}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.