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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8301566/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 04:32:29  来源:igfitidea点击:

Find key/value pairs deep inside a hash containing an arbitrary number of nested hashes and arrays

rubyarrayshashnestedamazon

提问by steven_noble

A web service is returning a hash that contains an unknown number of nested hashes, some of which contain an array, which in turn contains an unknown number of nested hashes.

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

Some of the keys are not unique -- i.e. are present in more than one of the nested hashes.

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

However, all the keys that I actually care about are all unique.

但是,我真正关心的所有键都是独一无二的。

Is there someway I can give a key to the top-level hash, and get back it's value even if the key-value pair is buried deep in this morass?

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

(The web service is Amazon Product Advertising API, which slightly varies the structure of the results that it gives depending on the number of results and the search types permitted in each product category.)

(该 Web 服务是 Amazon Product Advertising API,根据结果数量和每个产品类别中允许的搜索类型,它提供的结果结构略有不同。)

采纳答案by Phrogz

Here's a simple recursive solution:

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

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

回答by denis.peplin

No need for monkey patching, just use Hashie gem: https://github.com/intridea/hashie#deepfind

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

For arbitrary Enumerable objects, there is another extension available, DeepLocate: https://github.com/intridea/hashie#deeplocate

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

回答by barelyknown

Combining a few of the answers and comments above:

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

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

回答by Andy Triggs

Despite this appearing to be a common problem, I've just spent a while trying to find/come up with exactly what I need, which I think is the same as your requirement. Neither of the links in the first response are spot-on.

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

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

So given:

所以给出:

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

The following:

下列:

hash.deep_find(:transaction)

will find the array associated with the :transaction key.

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

This is not optimal as the inject will continue to iterate even if memois populated.

这不是最佳的,因为即使填充了备忘录,注入也会继续迭代。

回答by ReggieB

A variation of barelyknown's solution: This will find all the values for a key in a hash rather than the first match.

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

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)will return [1, 2]

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

回答by Chris Edwards

Ruby 2.3 introduces Hash#dig, which allows you to do:

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

h = { foo: {bar: {baz: 1}}}

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

回答by paulz

Because Rails 5 ActionController::Parameters no longer inherits from Hash, I've had to modify the method and make it specific to parameters.

由于 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

If the key is found, it returns the value of that key, but it doesn't return an ActionController::Parameter object so Strong Parameters are not preserved.

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

回答by Kapil Aggarwal

I use the following code

我使用以下代码

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

回答by DaniG2k

I ended up using this for a small trie search I wrote:

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

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

Note: this is just for nested hashes at the moment. Currently no array support.

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