21

Is there any way simpler than

if hash.key?('a')
  hash['a']['b'] = 'c' 
else  
  hash['a'] = {}
  hash['a']['b'] = 'c' 
end
ndnenkov
  • 35,425
  • 9
  • 72
  • 104
Radek
  • 13,813
  • 52
  • 161
  • 255
  • 1
    Keywords: "ruby hash auto-vivification auto-vivifying" (let's see how SO links them in -- see "Related") –  May 04 '11 at 04:31
  • @pst: I think there was an edit conflict - someone fixed some typos and got rid of your tag. – Andrew Grimm May 05 '11 at 00:15

5 Answers5

39

The easiest way is to construct your Hash with a block argument:

hash = Hash.new { |h, k| h[k] = { } }
hash['a']['b'] = 1
hash['a']['c'] = 1
hash['b']['c'] = 1
puts hash.inspect
# "{"a"=>{"b"=>1, "c"=>1}, "b"=>{"c"=>1}}"

This form for new creates a new empty Hash as the default value. You don't want this:

hash = Hash.new({ })

as that will use the exact same hash for all default entries.

Also, as Phrogz notes, you can make the auto-vivified hashes auto-vivify using default_proc:

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

UPDATE: I think I should clarify my warning against Hash.new({ }). When you say this:

h = Hash.new({ })

That's pretty much like saying this:

h = Hash.new
h.default = { }

And then, when you access h to assign something as h[:k][:m] = y, it behaves as though you did this:

if(h.has_key?(:k))
    h[:k][:m] = y
else
    h.default[:m] = y
end

And then, if you h[:k2][:n] = z, you'll end up assigning h.default[:n] = z. Note that h still says that h.has_key?(:k) is false.

However, when you say this:

h = Hash.new(0)

Everything will work out okay because you will never modified h[k] in place here, you'll only read a value from h (which will use the default if necessary) or assign a new value to h.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • 11
    As you know, but might want to point out, that only auto-vivifies one level deep, whereas doing this: `Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) }` is turtles all the way down. – Phrogz May 04 '11 at 04:48
  • @mu: good explanation but I don't understand `that will use the exact same hash for all default entries.`. Don't we want default entries be the same? – Radek May 04 '11 at 05:22
  • 1
    @Radek: No, you don't want the default entries to be the same **object**. If they're using the same object then you'll end up with `hash['a']` and `hash['b']` being the same object and that won't even be in `hash` they way you want it to be. Try my example in `irb` but use the `Hash.new({})` constructor and you'll see the problem. The `Hash.new(x)` form only works sensibly when `x` is something like a Fixnum. – mu is too short May 04 '11 at 05:34
  • @Radek: And check `hash.inspect`, `hash.default.inspect`, `hash['a'].inspect`, and `hash['b'].inspect` after you run through the example with `Hash.new({})`. – mu is too short May 04 '11 at 05:36
  • 1
    @Radek: I added an update with a better explanation of why `Hash.new({})` is a bad idea. – mu is too short May 04 '11 at 05:47
  • Wikipedia shows a way to do this in a reusable fashion: `autovivify_hash_proc = proc { Hash.new { |hash, key| hash[key] = Array.new } }`. Then you just do `my_hash = autovivify_hash_proc.call`. Note that my example assumes that you want all vals to be `Array`s *source: http://en.wikipedia.org/wiki/Autovivification#Ruby* – duma Mar 20 '13 at 15:17
10

a simple one, but hash should be a valid hash object

(hash["a"] ||= {})['b'] = "c"
Naren Sisodiya
  • 7,158
  • 2
  • 24
  • 35
  • When compared to the use of a default proc this approach has a lot of flexibility. To construct a default hash that sets `h[k]` to `{}` only when `k = 'a'`, one would write `Hash.new { |_,k| k=='a' ? {} : nil }`, which is pretty ugly. On the other hand, any or all of the strings in your expression could be variables, so it is in no way restrictive. Also, I've heard reports that this method tends to be faster than the use of a default proc. It's curious that `'b'` is in single quotes whereas,.. :-) – Cary Swoveland Feb 21 '19 at 20:42
2

If you create hash as the following, with default value of a new (identically default-valued) hash: (thanks to Phrogz for the correction; I had the syntax wrong)

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

Then you can do

hash["a"]["b"] = "c"

without any additional code.

Sophie Alpert
  • 139,698
  • 36
  • 220
  • 238
  • 3
    Well, you can _do_ it, but your `hash` doesn't change. I think you meant: `hash = Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) }` – Phrogz May 04 '11 at 04:47
0

The question here: Is auto-initialization of multi-dimensional hash array possible in Ruby, as it is in PHP? provides a very useful AutoHash implementation that does this.

Community
  • 1
  • 1
Ross Attrill
  • 2,594
  • 1
  • 22
  • 31
0
class NilClass
  def [](other)
    nil
  end
end

Once you defined that, everything will work automatically. But be aware that from now on nil would behave as an empty hash when used as a hash.

Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177