francis

Deep merge a Ruby hash, the joys of recursion

posted Friday, 15 February 2008

I was doing some hacking with lists of valid phone number codes (3,4,5 digits depending upon various factors). I used hashes that could be walked down because of the key lengths. Now we've decided to do it  differently because the data file wasn't up to date ...

This gets the phone number as an array of integers:

      code_pieces = code.to_s.split(//).collect(&:to_i) rescue []

(note the Rails &: thing)

This takes a list of keys and a value, pops the value off the beginning of the array and when the array is done then puts the value in with a hash key of -1:

    def hash_for_digits(n_array,value)
      n = n_array.delete_at(0)
      if n
        the_hash = {}
        the_hash.merge({ n => hash_for_digits(n_array,value) })
      else
        { -1 => value}
      end
    end

This is a merge of a hash of hashes with another - warning - it blows up  without the -1 markers from the previous function.

    def deep_merge_hash(hash1,hash2)
       hash2.each_key do |k1|
        if hash1.key?(k1)
          deep_merge_hash(hash1[k1],hash2[k1])
        else
          hash1[k1] = hash2[k1]
        end
      end
    end
 

Hope others find this some use - I might return to this another day and make it work with arbitrary hashes but now too busy and didn't want to throw the code away ... 

The return from the function looked like this:

      top_number = get_code_ref[code_pieces[0]][code_pieces[1]][code_pieces[2]] rescue nil
      if top_number
        ok = top_number[code_pieces[3]][code_pieces[4]][-1] rescue nil
        ok = top_number[code_pieces[3]][-1] unless ok rescue nil
        ok = top_number[-1] unless ok rescue nil
      end
      ok

I realised I could probably have speeded things up by having the first 3 as a hash key in their own right but too late now - reimplementation with a different data set on the way ... 

links: digg this    del.icio.us    technorati