I'm having a problem with this function that traverses a Hash. The Hash may contain an Array of Hashes. I want the method to search for an id and then return just the nested hash it finds.
It seems to work for the traversal, but it returns the original value passed in.
require 'rubygems'
require 'ruby-debug'
def find_by_id(node, find_this="")
if node.is_a?(Hash)
node.each do |k,v|
if v.is_a?(Array)
v.each do |elm|
if elm["_id"] == find_this && !find_this.empty?
return elm # THIS IS WHAT I WANT!
else
find_by_id(elm, find_this)
end
end
end
end
end
end
x = {"name" => "first", "_id"=>'4c96a9a56f831b0eb9000005', "items"=>["name" => "second", "_id"=>'4c96a9af6f831b0eb9000009', "others"=>[{"name" => "third", "_id"=>'4c96a9af6f831b0eb9000007'}, {"name" => "fourth", "_id"=>'4c96a9af6f831b0eb9000008'}] ] }
find_by_id(x, '4c96a9af6f831b0eb9000008')
When you invoke find_by_id
recursively, you're not doing anything with the return value. You need to check whether it found something and if so return that, i.e.:
result = find_by_id(elm, find_this)
return result if result
You also need to return nil
at the end of the method (after the each loop), so it returns nil
if nothing was found. If you don't, it'll return the return value of each
which is the hash that you iterated over.
Edit:
Here's the full code with the changes I outlined:
def find_by_id(node, find_this="")
if node.is_a?(Hash)
node.each do |k,v|
if v.is_a?(Array)
v.each do |elm|
if elm["_id"] == find_this && !find_this.empty?
return elm # THIS IS WHAT I WANT!
else
result = find_by_id(elm, find_this)
return result if result
end
end
end
end
end
# Return nil if no match was found
nil
end
Edit2:
An alternative approach, that I find cleaner, is to separate the logic for iterating the structure from the logic for finding the element with the right id:
def dfs(hsh, &blk)
return enum_for(:dfs, hsh) unless blk
yield hsh
hsh.each do |k,v|
if v.is_a? Array
v.each do |elm|
dfs(elm, &blk)
end
end
end
end
def find_by_id(hsh, search_for)
dfs(hsh).find {|node| node["_id"] == search_for }
end
By making dfs
return an Enumerable
we can use the Enumerable#find
method, which makes the code a bit simpler.
This also enables code reuse if you ever need to write another method that needs to iterate through the hash recursively, as you can just reuse the dfs method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With