How do you access nested elements of a hash with a single string key?
You can get there with inject
:
def t(key)
key.to_s.split('.').inject(@trans) { |h, k| h[k.to_sym] }
end
Error checking and "no such entry" checking is left as an exercise.
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"}
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}}
Find key/value pairs deep inside a hash containing an arbitrary number of nested hashes and arrays
Here's a simple recursive solution:
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
Extract specific keys from variedly nested hash
▶ hash = { "BreadCrumbs" => {
▷ "Id" => 375,
▷ "Name" => "Willingen",
▷ "Parent" => {
▷ "Id" => 52272,
▷ "Name" => "Wintersport-Arena Sauerland",
▷ "Parent" => {
▷ "Id" => 8,
▷ "Name" => "Germany"
▷ }
▷ }
▷ }}
▶ def concat hash
▷ [hash['Name'], hash['Parent'] ? concat(hash['Parent']) : nil]
▷ end
▶ (concat hash['BreadCrumbs']).flatten.compact
#⇒ ["Willingen", "Wintersport-Arena Sauerland", "Germany"]
I do not flatten on every iteration so that the result still contains hierarchy:
▶ concat hash['BreadCrumbs']
#⇒ ["Willingen", ["Wintersport-Arena Sauerland", ["Germany", nil]]]
The requested string as the result:
▶ (concat hash['BreadCrumbs']).flatten.compact.join ', '
#⇒ "Willingen, Wintersport-Arena Sauerland, Germany"
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.
Find a value in a nested hash
Found a way to do it with nested maps, a compact and a flatten.first:
fnd="theFoo"
data.map{|t,th|th.map{|s,sh|sh.map{|f,fh|fh["id"]if fh["name"]==fnd}.compact}}.flatten.first
==> 1
Related Topics
Replace Words in a String - Ruby
How to Make Rails 3.1 Use SASS (Over SCSS) as the Default
Lisp and Erlang Atoms, Ruby and Scheme Symbols. How Useful Are They
Is It Ok to Use 'Any' to Check If an Array Is Not Empty
Ruby 1.9 - Invalid Multibyte Char (Us-Ascii)
How to Copy File Across Buckets Using Aws-S3 or Aws-Sdk Gem in Ruby on Rails
Array Include Any Value from Another Array
Format the Date Using Ruby on Rails
Getting Uninitialized Constant Error When Trying to Run Tests
Ruby Design Pattern: How to Make an Extensible Factory Class
Ruby and Linux, Preferred Setup
Descending Sort by Value of a Hash in Ruby
Rspec: "Array.Should == Another_Array" But Without Concern for Order