Accessing elements of nested hashes in ruby
The way I usually do this these days is:
h = Hash.new { |h,k| h[k] = {} }
This will give you a hash that creates a new hash as the entry for a missing key, but returns nil for the second level of key:
h['foo'] -> {}
h['foo']['bar'] -> nil
You can nest this to add multiple layers that can be addressed this way:
h = Hash.new { |h, k| h[k] = Hash.new { |hh, kk| hh[kk] = {} } }
h['bar'] -> {}
h['tar']['zar'] -> {}
h['scar']['far']['mar'] -> nil
You can also chain indefinitely by using the default_proc
method:
h = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
h['bar'] -> {}
h['tar']['star']['par'] -> {}
The above code creates a hash whose default proc creates a new Hash with the same default proc. So, a hash created as a default value when a lookup for an unseen key occurs will have the same default behavior.
EDIT: More details
Ruby hashes allow you to control how default values are created when a lookup occurs for a new key. When specified, this behavior is encapsulated as a Proc
object and is reachable via the default_proc
and default_proc=
methods. The default proc can also be specified by passing a block to Hash.new
.
Let's break this code down a little. This is not idiomatic ruby, but it's easier to break it out into multiple lines:
1. recursive_hash = Hash.new do |h, k|
2. h[k] = Hash.new(&h.default_proc)
3. end
Line 1 declares a variable recursive_hash
to be a new Hash
and begins a block to be recursive_hash
's default_proc
. The block is passed two objects: h
, which is the Hash
instance the key lookup is being performed on, and k
, the key being looked up.
Line 2 sets the default value in the hash to a new Hash
instance. The default behavior for this hash is supplied by passing a Proc
created from the default_proc
of the hash the lookup is occurring in; ie, the default proc the block itself is defining.
Here's an example from an IRB session:
irb(main):011:0> recursive_hash = Hash.new do |h,k|
irb(main):012:1* h[k] = Hash.new(&h.default_proc)
irb(main):013:1> end
=> {}
irb(main):014:0> recursive_hash[:foo]
=> {}
irb(main):015:0> recursive_hash
=> {:foo=>{}}
When the hash at recursive_hash[:foo]
was created, its default_proc
was supplied by recursive_hash
's default_proc
. This has two effects:
- The default behavior for
recursive_hash[:foo]
is the same asrecursive_hash
. - The default behavior for hashes created by
recursive_hash[:foo]
'sdefault_proc
will be the same asrecursive_hash
.
So, continuing in IRB, we get the following:
irb(main):016:0> recursive_hash[:foo][:bar]
=> {}
irb(main):017:0> recursive_hash
=> {:foo=>{:bar=>{}}}
irb(main):018:0> recursive_hash[:foo][:bar][:zap]
=> {}
irb(main):019:0> recursive_hash
=> {:foo=>{:bar=>{:zap=>{}}}}
Accessing values in nested hash
To start, you need to understand what iterating over a hash will give you.
Consider this:
exp = {
fam: {cty: "bk", ins: 3},
spec: {cty: "man", ins: 2},
br: {cty: "qns", ins: 1},
aha: {cty: "man", ins: 0}
}
exp.map { |e, c, value| [e, c, value] }
# => [[:fam, {:cty=>"bk", :ins=>3}, nil], [:spec, {:cty=>"man", :ins=>2}, nil], [:br, {:cty=>"qns", :ins=>1}, nil], [:aha, {:cty=>"man", :ins=>0}, nil]]
This is basically what you're doing as you loop and Ruby passes the block the key/value pairs. You're telling Ruby to give you the current hash key in e
, the current hash value in c
and, since there's nothing else being passed in, the value
parameter becomes nil
.
Instead, you need a block variable for the key, one for the value:
exp.map { |k, v| [k, v] }
# => [[:fam, {:cty=>"bk", :ins=>3}], [:spec, {:cty=>"man", :ins=>2}], [:br, {:cty=>"qns", :ins=>1}], [:aha, {:cty=>"man", :ins=>0}]]
Notice that the nil values are gone.
Rewriting your code taking that into account, plus refactoring it for simplicity:
exp = {
fam: {cty: 'bk', ins: 3},
spec: {cty: 'man', ins: 2},
br: {cty: 'qns', ins: 1},
aha: {cty: 'man', ins: 0}
}
exp.each do |k, v|
if v[:cty] == 'man'
puts k
end
end
# >> spec
# >> aha
Now it's returning the keys you want, so it becomes easy to grab the entire hashes. select
is the appropriate method to use when you're trying to locate specific things:
exp = {
fam: {cty: 'bk', ins: 3},
spec: {cty: 'man', ins: 2},
br: {cty: 'qns', ins: 1},
aha: {cty: 'man', ins: 0}
}
e = exp.select { |k, v| v[:cty] == 'man' }
# => {:spec=>{:cty=>"man", :ins=>2}, :aha=>{:cty=>"man", :ins=>0}}
Older versions of Ruby didn't maintain hash output from the hash iterators so we'd have to coerce back to a hash:
e = exp.select { |k, v| v[:cty] == 'man' }.to_h
# => {:spec=>{:cty=>"man", :ins=>2}, :aha=>{:cty=>"man", :ins=>0}}
Use an Array to access nested Hash keys
You could make use of the newer method Hash#dig
(ruby 2.3+) to access the nested hash, and then set the value on that:
# ideally this might be a method on Hash, so you wouldn't need to pass it in
def deep_set(hash, path, value)
*path, final_key = path
to_set = path.empty? ? hash : hash.dig(*path)
return unless to_set
to_set[final_key] = value
end
hash = {
"foo" => {
"bar" => { }
}
}
deep_set(hash, ["foo", "bar", "baz"], "xxxxxx")
deep_set(hash, ["baz"], "yyyyyy")
deep_set(hash, ["foo", "baz", "bar"], "zzzzzz")
puts hash
# => {"foo"=>{"bar"=>{"baz"=>"xxxxxx"}}, "baz"=>"yyyyyy"}
How do I get the elements of a complex nested hash in ruby?
I used a modified 7stud's answer.
Since I also need the values of the keys (e.g., JAVA, JDK, /usr/...., I ended doing this:
alternatives.keys.each do |alt_keys| # Gets the values for JAVA, JDK keys
alternatives[alt_keys].each do |arr| # Gets the values for the /usr/... keys
puts "array key: " + arr.to_s
alternatives[alt_keys][arr].each do |elmt|
puts "elem: " + elmt
end
end
end
Thanks to all, I will try the other answers too later.
Ruby, accessing a nested value in a hash
As you have an array inside the entries
so you can access it using an index like this:
my_hash["entries"][0]["runs"]
You need to follow the same for accessing values inside the runs
as it is also an array.
Hope this helps.
Accessing nested hash map in Ruby
a['key_3'] # [{:book=>"abc", :trade_id=>"125", :ccy=>"eur", :mtm=>1908055, :"161205"=>-23279, :"161206"=>-730, :"161207"=>-513, :"161208"=>19933, :"161209"=>-21555}]
.first # {:book=>"abc", :trade_id=>"125", :ccy=>"eur", :mtm=>1908055, :"161205"=>-23279, :"161206"=>-730, :"161207"=>-513, :"161208"=>19933, :"161209"=>-21555}
.values_at(:ccy, :mtm) # ["eur", 1908055]
#=> ["eur", 1908055]
EDIT
To traverse the hash and map by nested key use Enumerable#map
:
a.map { |_k, v| v.first[:mtm] }
#=> [2000000, -150000, 1908055]
a.map {|k, v| v.first[:"161205"]}
#=> [-5045, 5178, -23279]
P.S.
{"eur","1908055"}
is not a valid object in Ruby.
Related Topics
Restarting a Loop from the Top
How to Check If a Value Exists in an Array in Ruby
How to Match the Last Occurrence of a Pattern
How to Install a Gem or Update Rubygems If It Fails With a Permissions Error
Error: While Executing Gem ... (Gem::Filepermissionerror)
Rails Activerecord Perform Group, Sum and Count in One Query
How to Calculate Number of Chars Common to Two Strings
Why Is It Bad Style to 'Rescue Exception =≫ E' in Ruby
How to Call Shell Commands from Ruby
How to Get a Single Character Without Pressing Enter
Nokogiri/Xpath Namespace Query
Usage of Attr_Accessor in Rails
Altering the Primary Key in Rails to Be a String