Initializing hashes

45,117

Solution 1

There are two ways to create initial values with for a Hash.

One is to pass a single object in to Hash.new. This works well in many situations, especially if the object is a frozen value, but if the object has internal state, this may have unexpected side-effects. Since the same object is shared between all keys without an assigned value, modifying the internal state for one will show up in all.

a_hash = Hash.new "initial value"
a_hash['a'] #=> "initial value"
# op= methods don't modify internal state (usually), since they assign a new
# value for the key.
a_hash['b'] += ' owned by b' #=> "initial value owned by b"
# other methods, like #<< and #gsub modify the state of the string
a_hash['c'].gsub!(/initial/, "c's")
a_hash['d'] << " modified by d"
a_hash['e'] #=> "c's value modified by d"

Another initialization method is to pass Hash.new a block, which is invoked each time a value is requested for a key that has no value. This allows you to use a distinct value for each key.

another_hash = Hash.new { "new initial value" }
another_hash['a'] #=> "new initial value" 
# op= methods still work as expected
another_hash['b'] += ' owned by b'
# however, if you don't assign the modified value, it's lost,
# since the hash rechecks the block every time an unassigned key's value is asked for
another_hash['c'] << " owned by c" #=> "new initial value owned by c"
another_hash['c'] #=> "new initial value"

The block is passed two arguments: the hash being asked for a value, and the key used. This gives you the option of assigning a value for that key, so that the same object will be presented each time a particular key is given.

yet_another_hash = Hash.new { |hash, key| hash[key] = "#{key}'s initial value" }
yet_another_hash['a'] #=> "a's initial value"
yet_another_hash['b'] #=> "b's initial value"
yet_another_hash['c'].gsub!('initial', 'awesome')
yet_another_hash['c'] #=> "c's awesome value"
yet_another_hash #=> { "a" => "a's initial value", "b" => "b's initial value", "c" => "c's awesome value" }

This last method is the one I most often use. It's also useful for caching the result of an expensive calculation.

Solution 2

You can specify the initial value when you create your hash:

a_hash = { 'x' => 'first text' }
// ...
a_hash['x'] << ' some more text'

Solution 3

Since you are using the hash to collect strings, I assume you simply want to make sure that you don't get an error when appending. Therefore, I'd make the hash default the empty string. Then you can append without error, no matter the hash key.

a_hash = Hash.new {|h,k| h[k]=""}

texts = ['first text', ' some more text']

texts.each do |text|
  a_hash['x'] << text
end

puts a_hash['x'] #=> 'first text some more text'
Share:
45,117
Paul
Author by

Paul

Sort of geeky Anglican priest and Director of Engineering for CareerImp. I would rather not code if it meant giving up Ruby/Rails/jQuery, although I've used just about every language (managed to avoid Cobal &amp; Lisp) in the last 40 years - including Forth and Prolog. I am still stunned my MacBook Pro is more powerful, with more memory (rotating &amp; not) than the two gyms full of IBMs &amp; Amdahls I used to work on - back in the bad old days.

Updated on August 02, 2022

Comments

  • Paul
    Paul almost 2 years

    I frequently write something like this:

    a_hash['x'] ? a_hash['x'] += ' some more text' : a_hash['x'] = 'first text'
    

    There ought to be a better way to do this, but I can't find it.

  • rampion
    rampion about 14 years
    The danger of this is that the same string object is used for all keys. So modicications to that string will be seen across all of them. That is if we use << : a_hash['c'] << " by c" #=> "first text by c", then a_hash['d'] #=> "first text by c"
  • Paul
    Paul about 14 years
    This gets at what I want to do if I initialize a_hash w/ an empty string: a_hash = Hash.new "", then I can add arbitrary text to arbitrary keys using +=
  • Paul
    Paul about 14 years
    initialize with a block is one I will start using. Good one!
  • Mark Thomas
    Mark Thomas about 14 years
    @rampion, you are correct. The block form to Hash.new slipped my mind. Corrected.
  • SystematicFrank
    SystematicFrank over 10 years
    it would be nice mentioning that the later case using a block, can also be used to set a default value when retrieving a value with the Hash#fetch method, since many times you might receive a Hash that was created somewhere else.
  • rampion
    rampion over 10 years
    Didn't know about Hash#fetch, thanks! Worth noting that the block passed to #fetch is a bit different, it doesn't get the hash table as a parameter during a miss.