2011-11-28 53 views
27

Web服務正在返回包含未知數量嵌套哈希的哈希,其中一些包含數組,其中包含未知數嵌套散列的數量。在包含任意數量的嵌套散列和數組的哈希中深入查找鍵/值對

某些鍵不是唯一的 - 即存在於多於一個嵌套散列中。

但是,我真正關心的所有密鑰都是獨一無二的。

是否有某種方式可以給頂層散列鍵一個鍵,並且即使鍵值對深埋在這個泥潭中也能找回它的值?

(Web服務是亞馬遜商品廣告API,它略有不同的結果,這取決於結果在各個產品類別允許的搜索類型的數量,並給出了結構。)

+1

這個問題出現了很多,像[這裏](http://stackoverflow.com/questions/1820451/ruby-style-how-to-check-whether-a-nested-hash-element-exists)和[這裏](http://stackoverflow.com/questions/7139471/transform-a-ruby-hash-into-a-dotted-path-key-string)等等。 –

+1

如果您可以創建一些示例數據來顯示您遇到的情況,它總是有幫助的,所以我們不必去想象。另外,數據如何發送?你收到XML並解析它嗎? JSON?或者,您是否正在使用返回神祕結構的電話,而其他所有電話都是黑匣子? –

回答

24

這裏有一個簡單的遞歸解決方案:

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 
+6

此代碼導致堆棧溢出。我想這是由於字符串和/或其他會響應每個方法的東西。我將'elsif obj.respond_to?(:each)'改爲'elsif obj.is_a?(Hash)或obj.is_a?(Array)'。現在它工作正常。感謝您的解決方案。 – Vigneshwaran

+2

如果這個東西打印出它的路徑(麪包屑?),它會很好...... –

+0

如果有多個哈希包含:bar key會怎樣,如果我們想要每個值的數組,它會是什麼樣的解決方案:酒吧鑰匙? – RSB

9

儘管看似是一個常見的問題,我剛剛花了一段時間試圖找到/拿出正是我需要的,我認爲這是與您的需求。第一個響應中的鏈接都沒有發現。

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) 

會發現相關的數組:交易的關鍵。

這不是最佳的,因爲即使填充了備忘錄,注入也會繼續進行迭代。

23

結合一些上述問題的答案和評論:

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 
0

我用下面的代碼

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 
0

最後我用這一個小線索搜索我寫道:

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 

注意:這只是當前嵌套散列。目前沒有陣列支持。

13

無需猴子修補,只需使用HASHIE寶石: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' } 

任意可枚舉的對象,還有一個伸展的,DeepLo​​cate:https://github.com/intridea/hashie#deeplocate

+0

爲什麼這會被標記下來? –

+1

我發現Hashi :: Extensions :: DeepFind是一個很好的方法。如果您正在尋找重複的密鑰,那麼deep_find_all()方法非常棒。強烈推薦。 – JESii

2

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]

0

由於Rails ActionController的5 ::參數不再哈希繼承,我不得不改變方法,使其對特定參數。

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

不適用於多個參數鍵: {0:[{b:'1'}],1:[{b:'2'}]} .deep_find(:b) return: #>'1 「 – m1l05z