Convertir un hash anidado en un hash plano

Esta pregunta es la inversa de esta pregunta.

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,
}
``````

preguntado el 10 de marzo de 12 a las 16:03

So you want an array of keys in order that lead to a value? -

8 Respuestas

De otra manera:

``````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}
``````

contestado el 27 de mayo de 14 a las 02:05

I 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? - oxidado

I've heard the term "tail-call optimized" but I have no idea what it means. Perhaps a computer scientist can answer your question. - cary swoveland

You can't optimize the tail call here IMO since you have a cascaded recursion. - Stefan Majewski

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
``````

respondido 10 mar '12, 19:03

This one ran the fastest. Thanks. - sawa

@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 :) - Niklas B.

@sawa: refactored for elegance. - Kyle

@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. - sawa

@Niklas, perhaps, but most Ruby-readers know sawa is a speed-freak. - cary swoveland

Mi intento:

``````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.

respondido 10 mar '12, 17:03

This is not an attempt to give you the el albergue mejor calificado 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.

respondido 10 mar '12, 17:03

A functional approach (see the historia 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
``````

contestado el 23 de mayo de 17 a las 13:05

Inspired by @cary-swoveland way, but in Hash class :

``````class Hash
def deep_flatten(previous_key=[])
flat_hash = {}
self.each do |key, value|
next_key = previous_key+[key]
flat_hash.update(value.is_a?(Hash) ? value.deep_flatten(next_key) : {next_key=>value})
end
return flat_hash
end
end

h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 }

h.deep_flatten #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
``````

Respondido el 23 de Septiembre de 15 a las 09:09

A declarative solution using ProfundoEnumerable:

``````require 'deep_enumerable'

h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 }
``````

``````h.deep_each.map do |k, v|
[DeepEnumerable.deep_key_to_array(k), v]
end.to_h
``````

or, for those who prefer point-free style

``````h.deep_each.to_h.shallow_map_keys(&DeepEnumerable.method(:deep_key_to_array))
``````

Respondido 24 Abr '16, 16:04

Array support / readable names / no update for speed / stringified results keys

``````def flat_hash(input, base = nil, all = {})
if input.is_a?(Array)
input = input.each_with_index.to_a.each(&:reverse!)
end

if input.is_a?(Hash) || input.is_a?(Array)
input.each do |k, v|
flat_hash(v, base ? "#{base}.#{k}" : k, all)
end
else
all[base] = input
end

all
end
``````

respondido 12 mar '17, 02:03

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.