【问题标题】:Find key/value pairs deep inside a hash containing an arbitrary number of nested hashes and arrays在包含任意数量的嵌套散列和数组的散列中查找键/值对
【发布时间】:2012-01-08 05:59:54
【问题描述】:

Web 服务正在返回一个包含未知数量的嵌套哈希的哈希,其中一些包含一个数组,而数组又包含未知数量的嵌套哈希。

一些键不是唯一的——即存在于多个嵌套散列中。

但是,我真正关心的所有键都是唯一的。

有没有什么办法可以给顶级散列一个键,即使键值对深埋在这个泥潭中,也可以取回它的值?

(网络服务是亚马逊产品广告 API,根据结果数量和每个产品类别中允许的搜索类型,它给出的结果结构略有不同。)

【问题讨论】:

  • 这个问题经常出现,比如herehere等等。
  • 如果您可以创建一些示例数据来展示您遇到的情况,这总是有帮助的,所以我们不必想象。另外,数据是如何发送的?您是否接收 XML 并对其进行解析? JSON?或者,您是否使用返回神秘结构而其他一切都是黑匣子的调用?

标签: ruby arrays hash nested amazon


【解决方案1】:

这是一个简单的递归解决方案:

def nested_hash_value(obj,key)
  if obj.respond_to?(:key?) && obj.key?(key)
    obj[key]
  elsif obj.respond_to?(:each)
    r = nil
    obj.find{ |*a| r=nested_hash_value(a.last,key) }
    r
  end
end

h = { foo:[1,2,[3,4],{a:{bar:42}}] }
p nested_hash_value(h,:bar)
#=> 42

【讨论】:

  • 这段代码导致我堆栈溢出。我猜这是由于字符串和/或其他会响应each 方法的原因。我将elsif obj.respond_to?(:each) 更改为elsif obj.is_a?(Hash) or obj.is_a?(Array)。现在它工作正常。感谢您的解决方案。
  • 如果这东西在下降时打印出它的路径(面包屑?)会很好......
  • 如果有多个哈希包含 :bar 键,如果我们想要每个 :bar 键的值的数组,解决方案是什么?
  • @RSB 如果您想得到答案,请将此作为自己的问题。更好的是,尝试自己提出解决方案。 (提示:对于要累积结果的递归函数,您要么需要返回值,要么使用封闭的数据结构;或者,您可以使用非递归深度优先或广度优先爬取,使用队列.)
【解决方案2】:

无需猴子补丁,只需使用 Hashie gem:https://github.com/intridea/hashie#deepfind

user = {
  name: { first: 'Bob', last: 'Boberts' },
  groups: [
    { name: 'Rubyists' },
    { name: 'Open source enthusiasts' }
  ]
}

user.extend Hashie::Extensions::DeepFind

user.deep_find(:name)   #=> { first: 'Bob', last: 'Boberts' }

对于任意 Enumerable 对象,还有另一个可用的扩展,DeepLo​​cate:https://github.com/intridea/hashie#deeplocate

【讨论】:

  • 我发现 Hashi::Extensions::DeepFind 是一个很好的方法。如果您要查找重复的键,那么 deep_find_all() 方法非常棒。强烈推荐。
  • 我没有投反对票,但我不会投赞成票,因为我发现使用 gem 来解决这种简单的解决方案有点过头了。
【解决方案3】:

结合上面的几个答案和cmets:

class Hash
  def deep_find(key, object=self, found=nil)
    if object.respond_to?(:key?) && object.key?(key)
      return object[key]
    elsif object.is_a? Enumerable
      object.find { |*a| found = deep_find(key, a.last) }
      return found
    end
  end
end

【讨论】:

    【解决方案4】:

    Ruby 2.3 引入了Hash#dig,它允许你这样做:

    h = { foo: {bar: {baz: 1}}}
    
    h.dig(:foo, :bar, :baz)           #=> 1
    h.dig(:foo, :zot)                 #=> nil
    

    【讨论】:

    • 这就是我们所需要的!
    【解决方案5】:

    几乎不知道的解决方案的一种变体:这将在哈希而不是第一个匹配项中找到键的所有值。

    class Hash
      def deep_find(key, object=self, found=[])
        if object.respond_to?(:key?) && object.key?(key)
          found << object[key]
        end
        if object.is_a? Enumerable
          found << object.collect { |*a| deep_find(key, a.last) }
        end
        found.flatten.compact
      end
    end
    

    {a: [{b: 1}, {b: 2}]}.deep_find(:b) 将返回 [1, 2]

    【讨论】:

      【解决方案6】:

      尽管这似乎是一个常见问题,但我只是花了一段时间试图找到/想出我真正需要的东西,我认为这与您的要求相同。第一个响应中的两个链接都不是正确的。

      class Hash
        def deep_find(key)
          key?(key) ? self[key] : self.values.inject(nil) {|memo, v| memo ||= v.deep_find(key) if v.respond_to?(:deep_find) }
        end
      end
      

      所以给定:

      hash = {:get_transaction_list_response => { :get_transaction_list_return => { :transaction => [ { ... 
      

      以下内容:

      hash.deep_find(:transaction)
      

      将找到与 :transaction 键关联的数组。

      这不是最优的,因为即使填充了 memo,注入也会继续迭代。

      【讨论】:

        【解决方案7】:

        我使用下面的代码

        def search_hash(hash, key)
          return hash[key] if hash.assoc(key)
          hash.delete_if{|key, value| value.class != Hash}
          new_hash = Hash.new
          hash.each_value {|values| new_hash.merge!(values)}
          unless new_hash.empty?
            search_hash(new_hash, key)
          end
        end
        

        【讨论】:

          【解决方案8】:

          我最终使用它进行了我写的一个小型 trie 搜索:

          def trie_search(str, obj=self)
            if str.length <= 1
              obj[str]
            else
              str_array = str.chars
              next_trie = obj[str_array.shift]
              next_trie ? trie_search(str_array.join, next_trie) : nil
            end
          end
          

          注意:目前这仅适用于嵌套哈希。目前不支持数组。

          【讨论】:

            【解决方案9】:

            由于 Rails 5 ActionController::Parameters 不再继承自 Hash,我不得不修改方法并使其特定于参数。

            module ActionController
              class Parameters
                def deep_find(key, object=self, found=nil)
                  if object.respond_to?(:key?) && object.key?(key)
                    return object[key]
                  elsif object.respond_to?(:each)
                    object = object.to_unsafe_h if object.is_a?(ActionController::Parameters)
                    object.find { |*a| found = deep_find(key, a.last) }
                    return found
                  end
                end
              end
            end
            

            如果找到键,则返回该键的值,但不返回 ActionController::Parameter 对象,因此不保留强参数。

            【讨论】:

            • 不适用于多个参数键:{ 0: [{b: '1'}], 1: [{b: '2'}] }.deep_find(:b) 返回: #> '1'
            猜你喜欢
            • 1970-01-01
            • 2014-09-13
            • 2016-08-03
            • 2015-04-16
            • 1970-01-01
            • 2020-12-13
            • 2021-11-03
            • 2015-02-03
            • 2013-03-14
            相关资源
            最近更新 更多