Hash default value question

Hi,

Can anyone explain why hash_of_indexes1 in the code below works, but
hash_of_index2 doesn’t? (I got the first method as a response to a
previous post.)

What I’d like to get is a hash with a key value for each unique value in
the receiver array, and I’d like the value for each key to be an array
of integers corresponding to the index values of the elements in the
original array. The first method works, but the second one doesn’t. I
figured if I created the hash in the second method with a default value
of an empty array, h[e.to_f] in the each method of the hash would
return an empty array the first time it sees each key. But
what actually happens is that the values for all of the keys are the
same when the hash’s each method finishes -- each value is an array with
all of the index values in the receiver array.

class Array
 def hash_of_indexes1
   h = Hash.new
   each_with_index { |e, i| h[e.to_f] = Array( h[e.to_f] ) << i }
   h
 end

 def hash_of_indexes2
   h = Hash.new([])
   each_with_index { |e, i| h[e.to_f] = h[e.to_f] << i }
   h
 end
end

[1, 2, 2, 3].hash_of_indexes1.inspect ## → returns {1.0=>[0],
3.0=>[3], 2.0=>[1, 2]}

[1, 2, 2, 3].hash_of_indexes2.inspect ## → returns {1.0=>[0, 1, 2, 3],
3.0=>[0, 1, 2, 3], 2.0=>[0, 1, 2, 3]}

Thanks for your help.

----- Original Message ----
From: William J. [email protected]
To: ruby-talk ML [email protected]
Sent: Sunday, September 14, 2008 4:02:54 PM
Subject: Re: Passing a method call into a method

On Sep 14, 8:18 am, Glenn [email protected] wrote:

  if self.empty?
   ‘The array is empty.’
  else
   h = {}
   self.each_with_index { |e, i| h.include?(e.to_f) ? h[e.to_f] << i : h[e.to_f] = [i] }
   h
  end
 end

class Array
 def hash_of_indexes
  h = {}
  each_with_index{ |e,i|
   e = e.to_f
   h[e] = Array( h[e] ) << i }
  h
 end
end

On Sep 19, 2008, at 1:40 PM, Glenn wrote:

second one doesn’t. I figured if I created the hash in the second
each_with_index { |e, i| h[e.to_f] = Array( h[e.to_f] ) << i }
[1, 2, 2, 3].hash_of_indexes1.inspect ## → returns {1.0=>[0],
3.0=>[3], 2.0=>[1, 2]}

[1, 2, 2, 3].hash_of_indexes2.inspect ## → returns {1.0=>[0, 1, 2,
3], 3.0=>[0, 1, 2, 3], 2.0=>[0, 1, 2, 3]}

Thanks for your help.

The Hash.new([]) causes a single array to be used (shared) for the
default value. Using the block form, Hash.new {[]}, would produce the
behavior you want. It’s common to see this used as:
Hash.new {|h,k| h[k] = [] }
if you want the key to exist in the hash when referenced (as you seem
to expect here).

class Array
def hash_of_indexes3
h = Hash.new {|h,k| h[k] = []}
each_with_index {|e,i| h[e] << i }
h
end
end
irb> [1, 2, 2, 3].hash_of_indexes3
=> {1=>[0], 2=>[1, 2], 3=>[3]}

I’m not sure why you wanted to call .to_f on each element, I left that
out.

-Rob

else
h = {}
each_with_index{ |e,i|
  e = e.to_f
  h[e] = Array( h[e] ) << i }
h

end
end

Rob B. http://agileconsultingllc.com
[email protected]
+1 513-295-4739
Skype: rob.biedenharn

On Fri, Sep 19, 2008 at 12:40 PM, Glenn [email protected] wrote:

Hi,

Can anyone explain why hash_of_indexes1 in the code below works, but hash_of_index2 doesn’t? (I got the first method as a response to a previous post.)

snip

h

end
end

[1, 2, 2, 3].hash_of_indexes1.inspect ## → returns {1.0=>[0], 3.0=>[3], 2.0=>[1, 2]}

[1, 2, 2, 3].hash_of_indexes2.inspect ## → returns {1.0=>[0, 1, 2, 3], 3.0=>[0, 1, 2, 3], 2.0=>[0, 1, 2, 3]}

With hash_of_indexes2, the array assigned to the each hash key is the
same Array object. You can tell this by comparing their id tags
(apologies for lazy style)…

a = 1,2,3,4
b = 1,2,3,4

a == b
=> true
a.id == b.id
=> true

a = Array([1,2,3,4])
b = Array([1,2,3,4])

a == b
=> true
a.id == b.id
=> false

Todd