Перебирать глубоко вложенный уровень хэшей в Ruby
Итак, у меня есть хеш, и для каждого уровня хеша я хочу сохранить его ключ и значение. Проблема в том, что значением может быть другой хеш-массив. Кроме того, этот хэш может содержать пары ключ-значение, где значение снова является другим массивом хэшей и т. Д., И т. Д. Кроме того, я не буду знать, насколько глубоко будет вложен каждый хэш. Чтобы привести пример:
{
:key1 => 'value1',
:key2 => 'value2',
:key3 => {
:key4 => 'value4',
:key5 => 'value5'
},
:key6 => {
:key7 => 'value7',
:key8 => {
:key9 => 'value9'
}
}
}
..И так далее. Что я хочу сделать, это сохранить каждый ключ, пару значений и идентификатор его родителя. Я думаю, что это, вероятно, будет сделано рекурсивно, я просто не уверен, как, потому что я не знаком с рекурсивными функциями. Я знаю, как перебирать данные нормально:
myHash.each {|key, value|
...Do something with the key and value ...
}
И поэтому я предполагаю, что рекурсивный вызов будет примерно таким:
def save_pair (myHash)
myHash.each {|key, value|
if(value.class != Hash) ? Pair.create(key, value) : save_pair(value)
}
end
Это не проверено, и я все еще не уверен, как включить сохранение родительских идентификаторов независимо от.
9 ответов
Если я понимаю цель, то у вас должна быть возможность передать родителю свой метод сохранения. Для верхнего уровня это будет ноль. Ниже показана идея, где puts
используется в качестве заполнителя для "сохранения".
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
Вот пример вызова к нему:
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 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
Затем, возвращаясь к исходной проблеме, вы можете сделать:
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"]
Существует также суть версии.
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]
Я рекомендую использовать #deep_locate из hashie gem https://www.rubydoc.info/github/intridea/hashie/Hashie/Extensions/DeepLocate
немного хаки всегда возвращать ложь, а не искать
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
Вот рекурсивная (читай улучшенная) версия Hash::each
(Hash::each_pair
) с поддержкой блоков и перечислителей:
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
Вот примеры использования Hash::each
с и без 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
Вот пример из самого вопроса:
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
Также взгляните на мою рекурсивную версию Hash::merge
(Hash::merge!
) здесь.
Это должно хорошо для JSON. Незначительные улучшения в коде Марка, где он преобразует все в верхний регистр в данном хэше:
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
Вы пробовали что-то подобное?
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)
Я столкнулся с этой проблемой, пытаясь преобразовать хеш-ключи верблюжьего случая в змеиный. вот что я придумал:
def hash_to_underscore(hash)
res = {}
hash.each do |key, value|
res[to_underscore(key)] =
value.instance_of?(Hash) ? hash_to_underscore(value) : value
end
res
end
def to_underscore(key)
key.to_s.underscore.to_sym
end
Если вы хотите рекурсивно редактировать хеш, вы можете сделать что-то вроде этого:
# Iterates over a Hash recursively
def each_recursive(parent, &block)
parent.each do |path, value|
if value.kind_? Hash
each_recursive parent, &block
elsif value.is_a? Array
# @TODo something different for Array?
else
yield(parent, path, container_or_field)
end
end
end
И вы можете сделать что-то вроде:
hash = {...}
each_recursive(hash) do |parent, path, value|
parent[path] = value.uppercase
end