0

let's say i have a multidimensional hash, and in one of the subhashes i have a key=>value pair which i need to retrieve by key. how can i do it?

example hashes:

h={:x=>1,:y=>2,:z=>{:a=>{:k=>"needle"}}}
h={:k=>"needle"}

key is always :k, and i need to get "needle"

i noticed that there is no "flatten" function for hashes in ruby 1.8, but if it'd be there, i imagine i'd just do

h.flatten[:k]

i imagine i need to write a recursive function for that?

thanks

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
Pavel K.
  • 6,697
  • 8
  • 49
  • 80

2 Answers2

9

You can always write your own mission-specific extension to Hash which does the dirty work for you:

class Hash
  def recursive_find_by_key(key)
    # Create a stack of hashes to search through for the needle which
    # is initially this hash
    stack = [ self ]

    # So long as there are more haystacks to search...
    while (to_search = stack.pop)
      # ...keep searching for this particular key...
      to_search.each do |k, v|
        # ...and return the corresponding value if it is found.
        return v if (k == key)

        # If this value can be recursively searched...
        if (v.respond_to?(:recursive_find_by_key))
          # ...push that on to the list of places to search.
          stack << v
        end
      end
    end
  end
end

You can use this quite simply:

h={:x=>1,:y=>2,:z=>{:a=>{:k=>"needle"}}}

puts h.recursive_find_by_key(:k).inspect
# => "needle"

h={:k=>"needle"}

puts h.recursive_find_by_key(:k).inspect
# => "needle"

puts h.recursive_find_by_key(:foo).inspect
# => nil
tadman
  • 208,517
  • 23
  • 234
  • 262
  • Beautiful. Just what I was looking for. – Hakan Ensari Jan 16 '11 at 02:12
  • Where would you put the extended "Hash" class (for instance in a gem) so that it's methods were available to other classes? – thoughtpunch May 22 '11 at 03:02
  • You'd need to load this into some kind of initializer. A gem typically has a main library file, so that might serve the purpose here. Either define it there or `require` it accordingly. – tadman May 24 '11 at 14:04
  • 1
    You can add `yield if block_given?` as the last line of the method to more fully parallel Hash#fetch functionality – Chris Doyle Nov 14 '12 at 19:49
2

If you need simply to fetch key value, but don't know how deep the key is, use this snippet

def find_tag_val(hash, tag)
  hash.map do |k, v|
    return v if k.to_sym == tag
    vr = find_tag_val(v, tag) if v.kind_of?(Hash)
    return vr if vr
  end
  nil #othervice
end 

h = {message: { key: 'val'}}
find_tag_val(h, :key) #=> 'val'
Andrey Yasinishyn
  • 1,851
  • 2
  • 23
  • 36