ruby on rails - create - Iterato sobre un nivel de hashes profundamente anidado en Ruby
ruby create hash (7)
Así que tengo un hash, y para cada nivel del hash, quiero almacenar su clave y valor. El problema es que un valor puede ser otra matriz de hash. Además, ese hash puede contener pares de valores clave donde el valor es otra vez matriz de hash, etc., etc. Además, no sabré cuán profundamente anidado estará cada hash. Para dar un ejemplo:
{
:key1 => ''value1'',
:key2 => ''value2'',
:key3 => {
:key4 => ''value4'',
:key5 => ''value5''
},
:key6 => {
:key7 => ''value7'',
:key8 => {
:key9 => ''value9''
}
}
}
..Y así. Lo que quiero hacer es guardar cada clave, par de valores y la identificación de su padre. Me imagino que esto probablemente se hará recursivamente, no estoy seguro de cómo porque no estoy familiarizado con las funciones recursivas. Sé cómo iterar a través de los datos normalmente:
myHash.each {|key, value|
...Do something with the key and value ...
}
Y entonces supongo que la llamada recursiva será algo como esto:
def save_pair (myHash)
myHash.each {|key, value|
if(value.class != Hash) ? Pair.create(key, value) : save_pair(value)
}
end
Esto no se ha probado, y todavía no estoy seguro de cómo incorporar el guardado de los ID principales independientemente.
¿Has probado algo así?
trios = []
def save_trio(hash, parent = nil)
hash.each do |key, value|
value.kind_of?(Hash) ? save_trio(value, key) : trios << {:key => key, :value => value, :parent => parent}
end
end
save_trio(myHash)
Aquí está la versión recursiva (lectura mejorada ) de Hash::each
( Hash::each_pair
) con bloque y soporte de enumerador :
module HashRecursive
refine Hash do
def each(recursive=false, &block)
if recursive
Enumerator.new do |yielder|
self.map do |key, value|
value.each(recursive=true).map{ |key_next, value_next| yielder << [[key, key_next].flatten, value_next] } if value.is_a?(Hash)
yielder << [[key], value]
end
end.entries.each(&block)
else
super(&block)
end
end
alias_method(:each_pair, :each)
end
end
using HashRecursive
Aquí hay ejemplos de uso de Hash::each
con y sin indicador recursive
:
hash = {
:a => {
:b => {
:c => 1,
:d => [2, 3, 4]
},
:e => 5
},
:f => 6
}
p hash.each, hash.each {}, hash.each.size
# #<Enumerator: {:a=>{:b=>{:c=>1, :d=>[2, 3, 4]}, :e=>5}, :f=>6}:each>
# {:a=>{:b=>{:c=>1, :d=>[2, 3, 4]}, :e=>5}, :f=>6}
# 2
p hash.each(true), hash.each(true) {}, hash.each(true).size
# #<Enumerator: [[[:a, :b, :c], 1], [[:a, :b, :d], [2, 3, 4]], [[:a, :b], {:c=>1, :d=>[2, 3, 4]}], [[:a, :e], 5], [[:a], {:b=>{:c=>1, :d=>[2, 3, 4]}, :e=>5}], [[:f], 6]]:each>
# [[[:a, :b, :c], 1], [[:a, :b, :d], [2, 3, 4]], [[:a, :b], {:c=>1, :d=>[2, 3, 4]}], [[:a, :e], 5], [[:a], {:b=>{:c=>1, :d=>[2, 3, 4]}, :e=>5}], [[:f], 6]]
# 6
hash.each do |key, value|
puts "#{key} => #{value}"
end
# a => {:b=>{:c=>1, :d=>[2, 3, 4]}, :e=>5}
# f => 6
hash.each(true) do |key, value|
puts "#{key} => #{value}"
end
# [:a, :b, :c] => 1
# [:a, :b, :d] => [2, 3, 4]
# [:a, :b] => {:c=>1, :d=>[2, 3, 4]}
# [:a, :e] => 5
# [:a] => {:b=>{:c=>1, :d=>[2, 3, 4]}, :e=>5}
# [:f] => 6
hash.each_pair(recursive=true) do |key, value|
puts "#{key} => #{value}" unless value.is_a?(Hash)
end
# [:a, :b, :c] => 1
# [:a, :b, :d] => [2, 3, 4]
# [:a, :e] => 5
# [:f] => 6
Aquí hay un ejemplo de la pregunta en sí:
hash = {
:key1 => ''value1'',
:key2 => ''value2'',
:key3 => {
:key4 => ''value4'',
:key5 => ''value5''
},
:key6 => {
:key7 => ''value7'',
:key8 => {
:key9 => ''value9''
}
}
}
hash.each_pair(recursive=true) do |key, value|
puts "#{key} => #{value}" unless value.is_a?(Hash)
end
# [:key1] => value1
# [:key2] => value2
# [:key3, :key4] => value4
# [:key3, :key5] => value5
# [:key6, :key7] => value7
# [:key6, :key8, :key9] => value9
También eche un vistazo a mi versión recursiva de Hash::merge
( Hash::merge!
) here .
Esto debería ser bueno para JSON. Mejoras menores al código de Mark donde convierte todo a mayúsculas en un hash dado:
def capitalize_hash(myHash)
myHash.each {|key, value|
puts "isHash: #{value.is_a?(Hash)}: " + value.to_s
value.is_a?(Hash) ? capitalize_hash(value) : ( value.is_a?(Array) ? (myHash[key] = capitalize_array(value)) : (myHash[key] = value.try(:upcase)))
}
end
def capitalize_array(myArray)
myArray.each {|value|
puts "isHash: #{value.is_a?(Hash)}: " + value.to_s
value.is_a?(Array) ? capitalize_array(value) : ( value.is_a?(Hash) ? capitalize_hash(value) : value.try(:upcase))
}
end
Recomiendo usar #deep_locate de hashie gem https://www.rubydoc.info/github/intridea/hashie/Hashie/Extensions/DeepLocate#deep_locate-instance_method
poco hacky siempre devuelve falso no buscar
hash.extend(Hashie::Extensions::DeepLocate)
hash.deep_locate -> (key, value, object) do
# what you want to do here!
# key: hash key
# value: hash value
# object: hash_object
false # prevent to stop seeking
end
Sé que esta es una respuesta tardía, pero acabo de implementar una solución no recursiva a su problema y pensé que valía la pena compartirla.
class Hash
def deep_traverse(&block)
stack = self.map{ |k,v| [ [k], v ] }
while not stack.empty?
key, value = stack.pop
yield(key, value)
if value.is_a? Hash
value.each{ |k,v| stack.push [ key.dup << k, v ] }
end
end
end
end
Luego, volviendo a su problema original, puede hacer:
h = {
:key1 => ''value1'',
:key2 => ''value2'',
:key3 => {
:key4 => ''value4'',
:key5 => ''value5''
},
:key6 => {
:key7 => ''value7'',
:key8 => {
:key9 => ''value9''
}
}
}
h.deep_traverse{ |path,value| p [ path, value ] }
# => [[:key6], {:key7=>"value7", :key8=>{:key9=>"value9"}}]
# [[:key6, :key8], {:key9=>"value9"}]
# [[:key6, :key8, :key9], "value9"]
# [[:key6, :key7], "value7"]
# [[:key3], {:key4=>"value4", :key5=>"value5"}]
# [[:key3, :key5], "value5"]
# [[:key3, :key4], "value4"]
# [[:key2], "value2"]
# [[:key1], "value1"]
También hay una versión esencial .
Si entiendo el objetivo, entonces debes poder pasar al padre a tu método de guardar. Para el nivel superior, será nulo. A continuación, se muestra la idea en la que se usa puts
como marcador de posición para "guardar".
def save_pair(parent, myHash)
myHash.each {|key, value|
value.is_a?(Hash) ? save_pair(key, value) :
puts("parent=#{parent.nil? ? ''none'':parent}, (#{key}, #{value})")
}
end
Aquí hay un ejemplo de llamada:
hash = Hash.new
hash["key1"] = "value1"
hash["key2"] = "value2"
hash["key3"] = Hash.new
hash["key3"]["key4"] = "value4"
hash["key3"]["key5"] = "value5"
hash["key6"] = Hash.new
hash["key6"]["key7"] = "value7"
hash["key6"]["key8"] = Hash.new
hash["key6"]["key8"]["key9"] = "value9"
save_pair(nil, hash)
class Hash
def each_with_parent(parent=nil, &blk)
each do |k, v|
Hash === v ? v.each_with_parent(k, &blk) : blk.call([parent, k, v])
end
end
end
h = { :a => 1, :b => { :c => 3, :d => 4, :e => { :f => 5 } } }
h.each_with_parent { |i| p i }
# [nil, :a, 1]
# [:b, :c, 3]
# [:b, :d, 4]
# [:e, :f, 5]