2008-02-29
A Better to_hash
For about a year ago Chris learned us how to build a hash from an array:
class Array def to_hash Hash[*self.flatten] end end >> array = [["name", "chris"], ["age", 47]] => [["name", "chris"], ["age", 47]] >> array.to_hash => {"name"=>"chris", "age"=>47} >> blog = { :name => 'err', :style => 'classic' } => {:name=>"err", :style=>"classic"} >> blog.to_a => [[:name, "err"], [:style, "classic"]] >> blog.to_a.to_hash => {:name=>"err", :style=>"classic"} >> blog.to_a.to_hash == blog => true
Using Hash[*array.flatten] is a small, clever hack, but what he didn’t tell us is that it’s broken with hashes which contains arrays:
>> me = {:name => ["Magnus", "Holm"], :other => [1, 2]} => {:name=>["Magnus", "Holm"], :other=>[1, 2]} >> me.to_a => [[:name, ["Magnus", "Holm"]], [:other, [1, 2]]] >> me.to_a.to_hash => {1=>2, :name=>"Magnus", "Holm"=>:other} >> me.to_a.to_hash == me => false
Solution 1: Inject
Who doesn’t love inject? Chris has actually written a great article about inject too. Just skim through it and look at this gorgeous code:
class Array def to_hash self.inject({}) do |memo, element| memo[element[0]] = element[1] memo end end end >> me = {:name => ["Magnus", "Holm"], :other => [1, 2]} => {:name=>["Magnus", "Holm"], :other=>[1, 2]} >> me.to_a => [[:name, ["Magnus", "Holm"]], [:other, [1, 2]]] >> me.to_a.to_hash => {:name=>["Magnus", "Holm"], :other=>[1, 2]} >> me.to_a.to_hash == me => true
Solution 2: Flatten
What about making flatten take an optional argument which specify how many levels that should be flattened? Like this:
class Array def flatten(levels = -1) if levels > 0 self.inject([]) do |m,e| if e.is_a?(Array) e.flatten(levels-1).map{|x|m<<x} else m<<e end m end elsif levels == 0 self else e=self e=e.flatten(1) until !e.any?{|x|x.is_a?(Array)} e end end def to_hash Hash[*self.flatten(1)] end end >> me = {:name => ["Magnus", "Holm"], :other => [1, 2]} => {:name=>["Magnus", "Holm"], :other=>[1, 2]} >> me.to_a.flatten => [:name, "Magnus", "Holm", :other, 1, 2] >> me.to_a.flatten(1) => [:name, ["Magnus", "Holm"], :other, [1, 2]] >> me.to_a.to_hash => {:name=>["Magnus", "Holm"], :other=>[1, 2]} >> me.to_a.to_hash == me => true
It’s a much longer solution1, but you will get a very nice flatten which you (probably/hopefully) can use other places too.
- 1 Compressed version is located here.