Suppose `
h = Hash.new(:cat)
h[:a] = 1
h[:b] = 2
h #=> {:a=>1, :b=>2}
Now
h[:a] #=> 1
h[:b] #=> 2
h[:c] #=> :cat
h[:d] #=> :cat
h #=> {:a=>1, :b=>2}
h = Hash.new(:cat)
defines an empty hash h
with a default value of :cat
. This means that if h
does not have a key k
, h[k]
will return :cat
, nothing more, nothing less. As you can see above, executing h[k]
does not change the hash when k
is :c
or :d
.
On the other hand,
h[:c] = h[:c]
#=> :c
h #=> {:a=>1, :b=>2, :c=>:cat}
Confused? Let me write this without the syntactic sugar:
h.[]=(:d, h.[](:d))
#=> :cat
h #=> {:a=>1, :b=>2, :d=>:cat}
The default value is returned by h.[](:d)
(i.e., h[:d]
) whereas Hash#[]=
is an assignment method (that takes two arguments, a key and a value) to which the default does not apply.
A common use of this default is to create a counting hash:
a = [1,3,1,4,2,5,4,4]
h = Hash.new(0)
a.each { |x| h[x] = h[x] + 1 }
h #=> {1=>2, 3=>1, 4=>3, 2=>1, 5=>1}
Initially, when h
is empty and x #=> 1
, h[1] = h[1] + 1
will evaluate to h[1] = 0 + 1
, because (since h
has no key 1
) h[1]
on the right side of the equality is set equal to the default value of zero. The next time 1
is passed to the block (x #=> 1
), x[1] = x[1] + 1
, which equals x[1] = 1 + 1
. This time the default value is not used because h
now has a key 1
.
This would normally be written (incidentally):
a.each_with_object(Hash.new(0)) { |x,h| h[x] += 1 }
#=> {1=>2, 3=>1, 4=>3, 2=>1, 5=>1}
One generally does not want the default value to be a collection, such as an array or hash. Consider the following:
h = Hash.new([])
[1,2,3].map { |n| h[n] = h[n] }
h #=> {1=>[], 2=>[], 3=>[]}
Now observe:
h[1] << 2
h #=> {1=>[2], 2=>[2], 3=>[2]}
This is normally not the desired behaviour. It has happened because
h.map { |k,v| v.object_id }
#=> [25886508, 25886508, 25886508]
That is, all the values are the same object, so if the value of one key is changed the values of all other keys are changed as well.
The way around this is to use a block when defining the hash:
h = Hash.new { |h,k| h[k]=[] }
[1,2,3].each { |n| h[n] = h[n] }
h #=> {1=>[], 2=>[], 3=>[]}
h[1] << 2
h #=> {1=>[2], 2=>[], 3=>[]}
h.map { |k,v| v.object_id }
#=> [24172884, 24172872, 24172848]
When the hash h
does not have a key k
the block { |h,k| h[k]=[] }
is executed and returns an empty array specific to that key.