Flatten out Hash

Hi,

I would like to take a nested hash that looks like this:

{“a”=>{“b”=>{“c”=>1}, “b2”=>{“c2”=>2}}}

and turn it into an array of 2 element arrays like this:

[["a: b2: c2: ", 2], ["a: b: c: ", 1]]

Is there a simple way to do this? I wrote a method that iterates
through the nested hashes recursively, but it’s a bit cumbersome.

On Tue, Mar 9, 2010 at 8:31 PM, Glenn R. [email protected] wrote:

Hi,

I would like to take a nested hash that looks like this:

{“a”=>{“b”=>{“c”=>1}, “b2”=>{“c2”=>2}}}

and turn it into an array of 2 element arrays like this:

[["a: b2: c2: ", 2], ["a: b: c: ", 1]]

My method:

hash = {“a”=>{“b”=>{“c”=>1}, “b2”=>{“c2”=>2}}}
array = []

hash.keys.each do |k1|
hash[k1].keys.each do |k2|
hash[k1][k2].keys.each do |k3|
array << [“#{k1}: #{k2}: #{k3}:”,hash[k1][k2][k3]]
end
end
end

Jean G. wrote:

On Tue, Mar 9, 2010 at 8:31 PM, Glenn R. [email protected] wrote:

Hi,

I would like to take a nested hash that looks like this:

{“a”=>{“b”=>{“c”=>1}, “b2”=>{“c2”=>2}}}

and turn it into an array of 2 element arrays like this:

[["a: b2: c2: ", 2], ["a: b: c: ", 1]]

My method:

hash = {“a”=>{“b”=>{“c”=>1}, “b2”=>{“c2”=>2}}}
array = []

hash.keys.each do |k1|
hash[k1].keys.each do |k2|
hash[k1][k2].keys.each do |k3|
array << [“#{k1}: #{k2}: #{k3}:”,hash[k1][k2][k3]]
end
end
end

Thanks, Jean.

It’s a good solution. But it’s dependent on there being 3 levels of
hashes. What if you didn’t know how many levels of nesting there was
before executing the code? Suppose the hash looked like this instead:

{“a”=>{“b”=>{“c”=>1}, “b2”=>{“c2”=> {“d2” => 2}}}}

Glenn R. wrote:

Hi,

I would like to take a nested hash that looks like this:

{“a”=>{“b”=>{“c”=>1}, “b2”=>{“c2”=>2}}}

and turn it into an array of 2 element arrays like this:

[["a: b2: c2: ", 2], ["a: b: c: ", 1]]

Is there a simple way to do this? I wrote a method that iterates
through the nested hashes recursively, but it’s a bit cumbersome.

I think that’s the right way, if the nesting depth is variable. One
example:

class Hash
def flat_each(prefix="", &blk)
each do |k,v|
if v.is_a?(Hash)
v.flat_each(prefix+k+": ", &blk)
else
yield prefix+k, v
end
end
end
end

require ‘enumerator’
h = {“a”=>{“b”=>{“c”=>1}, “b2”=>{“c2”=>2}}}
p h.to_enum(:flat_each).collect { |k,v| [k,v] }

Perhaps a better solution is to yield an array of keys (being the ‘path’
to reach the end node), because then the caller can choose how to
combine them.

class Hash
def flat_each(prefix=[], &blk)
each do |k,v|
if v.is_a?(Hash)
v.flat_each(prefix+[k], &blk)
else
yield prefix+[k], v
end
end
end
end

require ‘enumerator’
h = {“a”=>{“b”=>{“c”=>1}, “b2”=>{“c2”=>2}}}
h = {“a”=>{“b”=>{“c”=>1}, “b2”=>{“c2”=> {“d2” => 2}}}}
p h.to_enum(:flat_each).collect { |k,v| [k.join(": “),v] }
h.flat_each do |k,v|
puts “#{k.join(”/”)} => #{v}"
end

I coded up a way to do this in a method without needing to monkey-patch
Hash: http://forr.st/~zaG

Takes in a hash in input and merges it with an output hash to produce

a hash that is only one dimensional

i.e. there are no nested elements in the hash

def make_hash_one_dimensional(input = {}, output = {}, options = {})
input.each do |key, value|
key = options[:prefix].nil? ? “#{key}” :
“#{options[:prefix]}#{options[:delimiter]||”“}#{key}”
if value.is_a? Hash
make_hash_one_dimensional(value, output, :prefix => key,
:delimiter => "
")
else
output[key] = value
end
end
output
end

hash = {:a => {:b => {:c => {:d => {:e => “hi”}}}}, :f => “there”}
make_hash_one_dimensional(hash)
#=> {“f”=>“there”, “a_b_c_d_e”=>“hi”}

From there it would be easy to turn this into an array. Enjoy


@schneems

2010/3/9 Glenn R. [email protected]:

[["a: b2: c2: ", 2], [“a: b: c: “, 1]]
array << [”#{k1}: #{k2}: #{k3}:”,hash[k1][k2][k3]]
{“a”=>{“b”=>{“c”=>1}, “b2”=>{“c2”=> {“d2” => 2}}}}
Basically you want to do a DFS and store the path to the root for
every leaf. Something like DFS in nested Hashes · GitHub

Kind regards

robert