Iterate over a deeply nested level of hashes in Ruby
If I understand the goal, then you should be able to pass in the parent to your save method. For the top level, it will be nil. The following shows the idea where puts
is used as a place holder for the "save".
def save_pair(parent, myHash)
myHash.each {|key, value|
value.is_a?(Hash) ? save_pair(key, value) :
puts("parent=#{parent.nil? ? 'none':parent}, (#{key}, #{value})")
}
end
Here is an example call to it:
hash = Hash.new
hash["key1"] = "value1"
hash["key2"] = "value2"
hash["key3"] = Hash.new
hash["key3"]["key4"] = "value4"
hash["key3"]["key5"] = "value5"
hash["key6"] = Hash.new
hash["key6"]["key7"] = "value7"
hash["key6"]["key8"] = Hash.new
hash["key6"]["key8"]["key9"] = "value9"
save_pair(nil, hash)
Traversing a Hash Recursively in Ruby
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.
How to iterate over deep nested hash without known depth in Ruby
So, you want to parse recursively until there are no more levels to parse into.
It’s super common in software and referred to as “recursion”. Have a search around to learn more about it - it’ll come up again and again in your journey. Welcome to ruby btw!
As for your actual current problem. Have a read of https://mrxpalmeiras.wordpress.com/2017/03/30/how-to-parse-a-nested-yaml-config-file-in-python-and-ruby/
But also, consider the i18n gem. See this answer https://stackoverflow.com/a/51216931/1777331 and the docs for the gem https://github.com/ruby-i18n/i18n This might fix your problem of handling internationalisation without you having to get into the details of handling yaml files.
Improve this recursive function for hash traversal in Ruby
Inspired by some of the other answers, here's a way to do it using recursive send() rather than eval():
def extract(obj, hash)
k, v = hash.to_a[0]
v.is_a?(Hash) ? extract(obj.send(k), v) : obj.send(k).send(v)
end
In the case mentioned in the question, extract(@user, {:club => :title})
would result in @user.send(:club).send(:title)
.
EDIT: as mentioned by zimbatm, an array like [:club, :title, :owner] might be cleaner. If you used that instead (and are running in an environment that supports Symbol#to_proc), you could just do:
def extract2(obj, method_array)
method_array.inject(obj, &:send)
end
How do I flatten a nested hash, recursively, into an array of arrays with a specific format?
As hinted at in the comments:
Looks pretty straightforward. Descend into hashes recursively, taking note of keys you visited in this branch. When you see an array, no need to recurse further. Append it to the list of keys and return
Tracking is easy, just pass the temp state down to recursive calls in arguments.
I meant something like this:
def tree_flatten(tree, path = [], &block)
case tree
when Array
block.call(path + tree)
else
tree.each do |key, sub_tree|
tree_flatten(sub_tree, path + [key], &block)
end
end
end
tree_flatten(tree_def) do |path|
p path
end
This code simply prints each flattened path as it gets one, but you can store it in an array too. Or even modify tree_flatten
to return you a ready array, instead of yielding elements one by one.
Looping through a nested hash
{key1: "value1", key2: {key3: "value2", key4: "value3"}}
Your hash is not consistent hash of hashes, in the first iteration, value
is value1
which is a string, and you cannot iterate over a string
.
to avoid that, you can check beforehand like,
hash.each do |key,value|
p key
if value.is_a?(Hash)
value.each do |k,v|
p k
p v
end
else
p value
end
end
My goal is to access the values of key3 and key4. (I want to put them in a variable of some kind to be used elsewhere)
you can traverse a hash based on key associations. As per your need above, you can simply do:
hash[:key2][:key3]
#=> "value2"
hash[:key2][:key4]
#=> "value3"
Iterate nested hash that contains hash and/or Array
It is not entirely clear what you might want, but both Array
and Hash
implement each
(which, in the case of Hash
, is an alias for each_pair
).
So to get exactly the output you would get if your method would work, you could implement it like this:
def iterate(h)
h.each do |k,v|
# If v is nil, an array is being iterated and the value is k.
# If v is not nil, a hash is being iterated and the value is v.
#
value = v || k
if value.is_a?(Hash) || value.is_a?(Array)
puts "evaluating: #{value} recursively..."
iterate(value)
else
# MODIFY HERE! Look for what you want to find in the hash here
# if v is nil, just display the array value
puts v ? "key: #{k} value: #{v}" : "array value #{k}"
end
end
end
DFS traversal of a deeply nested hash
If all values are hashes, this would work:
hash = {"." => {"foo" => {"hello" => {}, "world" => {}}, "bar" => {}}}
def traverse(hash, depth = 0)
hash.each { |key, value|
puts "#{depth} #{key}"
traverse(value, depth + 1)
}
end
traverse(hash)
Output:
0 .
1 foo
2 hello
2 world
1 bar
Related Topics
How to Check If a Ruby Array Includes One of Several Values
Assign/Replace Params Hash in Rails
What Does ::Myclass Ruby Scope Operator Do
How to Handle a Thread Issue in Zeromq + Ruby
Handling a JavaScript Popup Occurring on a Keyup Event
Unix Commands Work on Server But Not in Ruby Ssh Session
How to Get Last N Records with Activerecord
Dynamic Method Calling in Ruby
Windows/Ruby/Rails Install --- .Cannot Load Such File -- SQLite3/Sqlite3_Native Windows
How to Access Nested Elements of a Hash with a Single String Key
Cannot Login to Amazon with Ruby Mechanize
What Does &: Mean in Ruby, Is It a Block Mixed with a Symbol
Ruby on Rails Display Half a Star for a Decimal Rating, E.G. 4.5