Converting a nested hash into a flat hash
Solution 1
Another way:
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
h = { :a => { :b => { :c => 1,
:d => 2 },
:e => 3 },
:f => 4 }
flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
Solution 2
Very similar to Adiel Mittmann's solution
def flat_hash(h, k = [])
new_hash = {}
h.each_pair do |key, val|
if val.is_a?(Hash)
new_hash.merge!(flat_hash(val, k + [key]))
else
new_hash[k + [key]] = val
end
end
new_hash
end
Edit: Refactored for elegance. Should be almost as fast.
def flat_hash(hash, k = [])
return {k => hash} unless hash.is_a?(Hash)
hash.inject({}){ |h, v| h.merge! flat_hash(v[-1], k + [v[0]]) }
end
Solution 3
My attempt:
def flatten_hash(h)
return { [] => h } unless h.is_a?(Hash)
Hash[h.map { |a,v1| flatten_hash(v1).map { |b,v2| [[a] + b, v2] } }.flatten(1)]
end
Sorry for the bad variables names, had to fit it in one line.
Solution 4
This is not an attempt to give you the best way to do it, but it is a way :P
def flatten(hash)
return {[] => hash} if !hash.is_a?(Hash)
map = {}
hash.each_pair do |key1, value1|
flatten(value1).each_pair do |key2, value2|
map[[key1] + key2] = value2
end
end
return map
end
It works for your example, producing this result:
{[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
It may not produce the result you expect if there are empty hashes.
Solution 5
A functional approach (see the history for an alternative implementations):
def recursive_flatten(hash)
hash.flat_map do |key, value|
if value.is_a?(Hash)
recursive_flatten(value).map { |ks, v| [[key] + ks, v] }
else
[[[key], value]]
end
end.to_h
end
Related videos on Youtube
![sawa](https://i.stack.imgur.com/zL1re.jpg?s=256&g=1)
sawa
I have published the following two Ruby gems: dom: You can describe HTML/XML DOM structures in Ruby language seamlessly with other parts of Ruby code. Node embedding is described as method chaining, which avoids unnecessary nesting, and confirms to the Rubyistic coding style. manager: Manager generates a user's manual and a developer's chart simultaneously from a single spec file that contains both kinds of information. More precisely, it is a document generator, source code annotation extracter, source code analyzer, class diagram generator, unit test framework, benchmark measurer for alternative implementations of a feature, all in one. Comments and contributions are welcome. I am preparing a web service that is coming out soon.
Updated on July 09, 2022Comments
-
sawa almost 2 years
This question is the inverse of this question.
Given a nested hash like
{ :a => { :b => {:c => 1, :d => 2}, :e => 3, }, :f => 4, }
what is the best way to convert it into a flat hash like
{ [:a, :b, :c] => 1, [:a, :b, :d] => 2, [:a, :e] => 3, [:f] => 4, }
-
Linuxios over 12 yearsSo you want an array of keys in order that lead to a value?
-
-
sawa over 12 yearsThis one ran the fastest. Thanks.
-
Niklas B. over 12 years@sawa: Just a tip for the future: If you want a fast solution, mention this in the question next time. Usually the main criterion in dynamic languages like Python or Ruby is elegancy and conciseness. If you specifically ask for performance as well, you could get much better suited answers :)
-
Kyle over 12 years@sawa: refactored for elegance.
-
sawa over 12 years@Kyle One thing that was good about your earlier code was that it can be redefined as a method on
Hash
. The rewritten one cannot. -
rusty over 9 yearsI know Ruby doesn't necessarily support TCO out of the box, but if you didn't return
g
here, would this be tail-call optimized? -
Cary Swoveland over 9 yearsI've heard the term "tail-call optimized" but I have no idea what it means. Perhaps a computer scientist can answer your question.
-
Stefan Majewsky over 9 yearsYou can't optimize the tail call here IMO since you have a cascaded recursion.
-
Cary Swoveland about 3 years@Niklas, perhaps, but most Ruby-readers know sawa is a speed-freak.