查找包含任意数量嵌套散列和数组的散列深处的键/值对



web服务返回的散列包含未知数量的嵌套散列,其中一些包含数组,而数组又包含未知数量的嵌套散列。

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

然而,所有我真正关心的键都是唯一的

是否有某种方法我可以给顶级哈希一个键,并得到它的值,即使键值对深埋在这个泥潭?

(web服务是Amazon Product Advertising API,它根据结果的数量和每个产品类别中允许的搜索类型稍微改变了结果的结构)

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

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

无需猴子补丁,只需使用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' }

对于任意可枚举对象,还有另一个扩展可用,DeepLocate: https://github.com/intridea/hashie#deeplocate

结合上面的一些答案和评论:

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

Ruby 2.3引入了hash# dig,它允许您执行以下操作:

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

barelyknown解决方案的一种变体:这将在散列中找到键的所有值,而不是第一个匹配。

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]

尽管这似乎是一个常见的问题,但我刚刚花了一段时间试图找到/提出我所需要的,我认为这与您的要求相同。第一个回复中的两个链接都不正确。

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)

将查找与:事务键相关联的数组。

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

我使用以下代码

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

我最终使用了这个小的树搜索,我写:

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

注意:这只是目前嵌套的哈希。目前不支持数组

因为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对象,所以强参数不保留

最新更新