2012-01-05 13 views
22

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:Iteramos un nivel de hashes profundamente anidado en Ruby

{ 
    :key1 => 'value1', 
    :key2 => 'value2', 
    :key3 => { 
    :key4 => 'value4', 
    :key5 => 'value5' 
    }, 
    :key6 => { 
     :key7 => 'value7', 
     :key8 => { 
     :key9 => 'value9' 
     } 
    } 
    } 

.. Y así sucesivamente. Lo que quiero hacer es guardar cada clave, par de valores y el id de su principal. Me imagino que esto probablemente se hará recursivamente, no estoy seguro de cómo porque no estoy familiarizado con las funciones recursivas. Yo sé la manera de recorrer los datos normalmente:

myHash.each {|key, value| 
    ...Do something with the key and value ... 
    } 

Y así que supongo 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 estoy seguro de cómo para incorporar guardar los identificadores principales independientemente.

Respuesta

18

Si entiendo el objetivo, entonces debería poder pasar el método principal al padre. 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í es un ejemplo de llamada a la misma:

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) 
+1

Sus ajustes funcionan perfectamente. ¡Gracias un montón! – varatis

0

¿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) 
5
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] 
+0

¿Hay alguna manera de reasignar los resultados del bucle al hash? –

+0

@AlexC ¿me puede mostrar un ejemplo? – maprihoda

+0

Lo logré reemplazando k por v en 'v.each_with_parent (k, & blk)' y haciendo p [k] = v.to_i dentro del ciclo. Estaba intentando reformatear un json (cambiando cadenas a booleanos/enteros). –

0

Esto debería hacer por JSON también. Pequeñas mejoras en el código de Marcos donde se convierte todo en 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 
9

Sé que esto es una respuesta tardía, pero acabo implementado una solución no recursiva a su problema y pensé que vale la pena compartir.

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 

Entonces, volviendo a su problema original, que 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 un gist version.

+0

¡Esto es exactamente de mi necesidad! ¡Gracias! –

0

Aquí es recursiva (LEER mejoraron) versión de Hash::each (Hash::each_pair) con bloque y empadronador de apoyo:

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 

Éstos son el uso ejemplos de Hash::each con y sin recursive marca:

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í es ejemplo de la pregunta en sí misma:

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 echar un vistazo a mi versión recursiva de Hash::merge (Hash::merge!) here.

Cuestiones relacionadas