在Ruby中迭代深度嵌套的哈希层级

33

我有一个哈希表,对于哈希表的每个级别,我想存储它的键和值。问题是,值可以是另一个哈希数组。此外,该哈希表可以包含键值对,其中值再次是另一个哈希数组,以此类推。此外,我不知道每个哈希表的嵌套深度。例如:

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

......等等。 我想做的是保存每个键值对及其父项的ID。 我想这可能需要递归完成,但由于我不熟悉递归函数,所以不确定该如何实现。我知道如何正常迭代数据:

  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

以下是未经测试的代码,我仍不确定如何在其中包含父级 ID 的保存方式。

9个回答

25
如果我理解了目标,那么您应该能够将父级传递给您的保存方法。对于顶层,它将为nil。以下是一个示例,其中puts作为“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

以下是调用它的示例:

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)

15

我知道这是一个迟到的回复,但我刚刚实现了一个非递归解决方案来解决您的问题,并认为值得分享。

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

还有一个gist版本


这正是我所需要的!谢谢你! - Sergio Belevskij
如果你想让这个适用于所有集合,可以使用object.respond_to? :each或者my_object.class.include? Enumerable,这样就不需要进行太多针对哈希的修改了。 - Peter DeWeese
这将以相反的顺序遍历。我假设OP希望它按照原始顺序遍历。将stack = self.map{ |k,v| [ [k], v ] }更改为stack = self.map{ |k,v| [ [k], v ] }.reverse - Dex

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

有没有一种方法可以将循环的结果重新分配给哈希表? - Alex C
我通过在 v.each_with_parent(k, &blk) 中用 v 替换 k,并在循环内部执行 p[k] = v.to_i 来实现。我试图重新格式化一个JSON文件(将字符串更改为布尔值/整数)。 - Alex C

3
这是一个带有枚举器支持的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

以下是使用带有和不带有recursive标志的Hash::each的示例:
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!) 的递归版本,链接在这里


3

1
这应该很适合JSON。在Mark的代码中,将给定哈希中的所有内容转换为大写字母时进行了一些小的改进:
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

0

在尝试将驼峰式哈希键转换为蛇形式时,我遇到了这个问题。这是我想出的解决方案:

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

0
你尝试过这样的东西吗?
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)

0
如果你想递归地编辑哈希表,可以像这样做:
# Iterates over a Hash recursively
def each_recursive(parent, &block)
  parent.each do |path, value|
    if value.kind_of? 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

这是一个打字错误吗?kind_? 不应该是 kind_of? 吗? - spuder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接