Найти пары ключ/значение внутри хеша, содержащего произвольное количество вложенных хэшей и массивов
Веб-служба возвращает хэш, содержащий неизвестное количество вложенных хэшей, некоторые из которых содержат массив, который, в свою очередь, содержит неизвестное количество вложенных хэшей.
Некоторые из ключей не уникальны, то есть присутствуют в более чем одной из вложенных хэшей.
Однако все ключи, которые мне действительно волнуют, уникальны.
Я могу дать ключ к хэшу верхнего уровня и вернуть его значение, даже если пара ключ-значение глубоко погружена в это болото?
(Веб-сервис - это API рекламы продукта 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
Ответ 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, поэтому сильные параметры не сохраняются.