Iterate over a deeply nested level of hashes in Ruby

生来就可爱ヽ(ⅴ<●) 提交于 2020-01-09 06:20:53

问题


So I have a hash, and for each level of the hash, I want to store its key and value. The problem is, a value can be another hash array. Furthermore, that hash can contain key value pairs where the value is again another hash array, etc, etc. Also, I won't know how deeply nested each hash will be. To give an example:

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

..And so on. What I want to do is save each key, value pair and the id of its parent. I figure this will probably be done recursively, I'm just unsure how because I'm unfamiliar with recursive functions. I know how to iterate through the data normally:

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

And so I'm guessing the recursive call will be something like this:

def save_pair (myHash)
  myHash.each {|key, value|
    if(value.class != Hash) ? Pair.create(key, value) : save_pair(value)
  }
end

This is untested, and I'm still unsure how to incorporate saving the parent ids regardless.


回答1:


If I understand the goal, then you should be able to pass in the parent to your save method. For the top level, it will be nil. The following shows the idea where puts is used as a place holder for the "save".

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

Here is an example call to it:

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)



回答2:


I know this is a late reply, but I just implemented a non-recursive solution to your problem and thought it is worth sharing.

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

Then, coming back to your original problem, you can do:

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"]

There is also a gist version.




回答3:


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]



回答4:


Here is recursive (read improved) version of Hash::each(Hash::each_pair) with block and enumerator support:

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

Here are usage examples of Hash::each with and without recursive flag:

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

Here is example from the question itself:

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

Also take a look at my recursive version of Hash::merge(Hash::merge!) here.




回答5:


I recomend to use #deep_locate of hashie gem https://www.rubydoc.info/github/intridea/hashie/Hashie/Extensions/DeepLocate#deep_locate-instance_method

little bit hacky always return false not to seeking

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



回答6:


Have you tried something like that?

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)



回答7:


This should do for JSON well. Minor enhancements to Mark's code where it converts everything to uppercase in a given hash:

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



回答8:


If you want to recursively edit the hash, you could do something like this:

# 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

And you can do something like:

hash = {...}
each_recursive(hash) do |parent, path, value|
  parent[path] = value.uppercase
end


来源:https://stackoverflow.com/questions/8748475/iterate-over-a-deeply-nested-level-of-hashes-in-ruby

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!