Подтвердить что ты не робот

Найти пары ключ/значение внутри хеша, содержащего произвольное количество вложенных хэшей и массивов

Веб-служба возвращает хэш, содержащий неизвестное количество вложенных хэшей, некоторые из которых содержат массив, который, в свою очередь, содержит неизвестное количество вложенных хэшей.

Некоторые из ключей не уникальны, то есть присутствуют в более чем одной из вложенных хэшей.

Однако все ключи, которые мне действительно волнуют, уникальны.

Я могу дать ключ к хэшу верхнего уровня и вернуть его значение, даже если пара ключ-значение глубоко погружена в это болото?

(Веб-сервис - это API рекламы продукта Amazon, который немного изменяет структуру результатов, которые он дает, в зависимости от количества результатов и типов поиска, разрешенных в каждой категории продуктов.)

4b9b3361

Ответ 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

Ответ 2

Объединив несколько ответов и комментариев выше:

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

Ответ 3

Не нужно исправлять обезьяны, просто используйте драгоценный камень Хаши: 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 доступно еще одно расширение: DeepLocate: https://github.com/intradea/hashie#deeplocate

Ответ 4

Несмотря на то, что это кажется распространенной проблемой, я просто потратил некоторое время, пытаясь найти/придумать именно то, что мне нужно, и я думаю, это то же самое, что и ваше требование. Ни одна из ссылок в первом ответе не обнаружена.

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)

найдет массив, связанный с ключом транзакции.

Это не оптимально, так как инъекция будет продолжать итерацию, даже если заполняется памятка.

Ответ 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

Я использую следующий код

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

Ответ 7

Я закончил использовать это для небольшого поиска 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

Примечание: это только для вложенных хэшей на данный момент. В настоящее время поддержка массивов не поддерживается.

Ответ 8

Поскольку Rails 5 ActionController:: Параметры больше не наследуются от 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, поэтому сильные параметры не сохраняются.