Multilevel Hash Default Parameters When Accessing Non-Existant Keys as in h[1][1] += 1 [duplicate]

StackOverflow https://stackoverflow.com/questions/3576008

  •  01-10-2019
  •  | 
  •  

Pergunta

I'm trying to create a Hash of Hash-es. The value of the 2nd level hash is an integer defaulted to 0. What I'm trying to do is start with an empty hash of hashes, and then when I add a value if the key doesn't exist, they key should be added with a default value.

When I try this with a simple Hash of integers, it works fine:

irb(main):003:0> h = Hash.new(0)
=> {}
irb(main):004:0> h[1] += 1
=> 1
irb(main):005:0> p h
{1=>1}
=> nil
irb(main):006:0> h.keys.size
=> 1
irb(main):007:0>

h now has one key and a value of 1. Perfect.

But when my hash's values are hashes, it doesn't appear to work:

irb(main):007:0> h = Hash.new(Hash.new(0))
=> {}
irb(main):008:0> h[1][1] += 1
=> 1
irb(main):009:0> p h
{}
=> nil
irb(main):010:0> h.keys.size
=> 0
irb(main):011:0>

Am I doing something wrong, or can I not set the default value for a hash to Hash.new(0)?

EDIT:

Based on the answers below, I was able to figure out what I was doing wrong. Actually, I was able to figure out where my thinking was faulty. Long story short, h[1][1] doesn't nest Hash.new calls, unless you give Hash.new a block of code to call. The value of the expression h[1][1] += 1 is 1 as expected, but only the innermost hash was being initialized properly.

I'm posting this because while my example above uses a 2-dimensional hash, my actual problem uses a 3-dimensional hash:

syms[level][exchange][symbol] = count

The solution might be helpful to others having this problem, so here's the code that gets this working the way I want:

irb(main):024:0> syms = Hash.new{|h1,k1| h1[k1] = Hash.new{|h2,k2| h2[k2] = Hash.new(0)}}
=> {}
irb(main):026:0> syms["level1"]["NYSE"]["IBM"] += 1
=> 1
irb(main):027:0> p syms
{"level1"=>{"NYSE"=>{"IBM"=>1}}}
=> nil
irb(main):028:0>
Foi útil?

Solução

The most general way to set a default value when creating a Hash is to use the Hash#new function with a block argument:

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

The problem you have is that you are setting the default to a object pointer rather than a piece of code to be executed. That is, every time a missing value in the "outer" hash is accessed, it returns a pointer to the exact same object. To prove this to yourself, try this:

>> h = Hash.new(Hash.new(0))
=> {}
>> h[1] === h[2]
=> true

So you can in fact set the default value for a hash, but doing so will not accomplish what you want. Incrementing h[1][1] will in fact change the default hash itself, since h[1] hasn't been initialized.

Hope this helps.

Outras dicas

h = Hash.new(Hash.new(0))

The Hash.new(0)is the default object; it's the same object for every nonexisting key. It's just what the hash returns when the key isn't there.

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

the {|h,k| h[k]=Hash.new(0)} is the default Proc. It runs when the key is absent and it creates a new hash (defaulting to 0.) for every non-existant key.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top