/me@(voip.)?|www./ SamLown.com

Recursive Lambdas and How to completely flatten a Hash in Ruby

Today I’ve been toiling with a complex problem in Ruby; how to completely flatten a hash of hashes and arrays into a single flat array. My aim was to be able to create a unique and platform consistent MD5 string of a complex hash. You cannot guarantee a Hash will always be ordered the same way across platforms, but a flat array can be sorted, converted to a string, and checksummed easily.

A common approach would be to monkey patch the Array and Hash methods with recursive methods that iterate through their entries, flattening along the way. Unless absolutely necessary this doesn’t sound like a good idea, even less so for a one-off problem that’s unlikely to be used elsewhere.

Having searched around for a while and only finding samples for factorial solutions and other examples of recursive lambdas I finally worked out the following solution:

flatten =
  lambda {|r|
    (recurse = lambda {|v|
      if v.is_a?(Hash)
        v.to_a.map{|v| recurse.call(v)}.flatten
      elsif v.is_a?(Array)
        v.flatten.map{|v| recurse.call(v)}
      else
        v.to_s
      end
    }).call(r)
  }
# Flatten a Hash:
flatten.call({:a => {:b => :c => 12345}}})
#      produces: ['a', 'b', 'c', '12345']

This will work in both Ruby 1.8.7 and 1.9.2. Combining two lambdas is required in Ruby 1.8 as it does not support using the variable the lambda is assigned to, unless contained inside another lambda. (Took a while to grasp that.) Ruby 1.9 does not suffer this setback so if backwards compatibility is not a problem, the following is little bit clearer:

flatten =
  lambda {|r|
    if v.is_a?(Hash)
      v.to_a.map{|v| flatten.call(v)}.flatten
    elsif v.is_a?(Array)
      v.flatten.map{|v| flatten.call(v)}
    else
      v.to_s
    end
  }

In case you’re interested, this code is used in couchrest_model to be able to include checksums in CouchDB design documents in an attempt to make checking for updates and changes in views a little bit easier.

written by Sam,


No comments yet


Write your comment



HTML will be removed, but Textile formating is permitted. Log in for your comment to be posted without review.