This question already has answers here:
Ruby Style: How to check whether a nested hash element exists
(16 answers)
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
I'm working a little utility written in ruby that makes extensive use of nested hashes. Currently, I'm checking access to nested hash elements as follows:
structure = { :a => { :b => 'foo' }}
# I want structure[:a][:b]
value = nil
if structure.has_key?(:a) && structure[:a].has_key?(:b) then
value = structure[:a][:b]
end
Is there a better way to do this? I'd like to be able to say:
value = structure[:a][:b]
And get nil if :a is not a key in structure, etc.
Traditionally, you really had to do something like this:
structure[:a] && structure[:a][:b]
However, Ruby 2.3 added a method Hash#dig that makes this way more graceful:
structure.dig :a, :b # nil if it misses anywhere along the way
There is a gem called ruby_dig that will back-patch this for you.
Hash and Array have a method called dig.
value = structure.dig(:a, :b)
It returns nil if the key is missing at any level.
If you are using a version of Ruby older than 2.3, you can install a gem such as ruby_dig or hash_dig_and_collect, or implement this functionality yourself:
module RubyDig
def dig(key, *rest)
if value = (self[key] rescue nil)
if rest.empty?
value
elsif value.respond_to?(:dig)
value.dig(*rest)
end
end
end
end
if RUBY_VERSION < '2.3'
Array.send(:include, RubyDig)
Hash.send(:include, RubyDig)
end
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 as recursive_hash.
The default behavior for hashes created by recursive_hash[:foo]'s default_proc will be the same as recursive_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=>{}}}}
I made rubygem for this. Try vine.
Install:
gem install vine
Usage:
hash.access("a.b.c")
I think one of the most readable solutions is using Hashie:
require 'hashie'
myhash = Hashie::Mash.new({foo: {bar: "blah" }})
myhash.foo.bar
=> "blah"
myhash.foo?
=> true
# use "underscore dot" for multi-level testing
myhash.foo_.bar?
=> true
myhash.foo_.huh_.what?
=> false
value = structure[:a][:b] rescue nil
Solution 1
I suggested this in my question before:
class NilClass; def to_hash; {} end end
Hash#to_hash is already defined, and returns self. Then you can do:
value = structure[:a].to_hash[:b]
The to_hash ensures that you get an empty hash when the previous key search fails.
Solution2
This solution is similar in spirit to mu is too short's answer in that it uses a subclass, but still somewhat different. In case there is no value for a certain key, it does not use a default value, but rather creates a value of empty hash, so that it does not have the problem of confusion in assigment that DigitalRoss's answer has, as was pointed out by mu is too short.
class NilFreeHash < Hash
def [] key; key?(key) ? super(key) : self[key] = NilFreeHash.new end
end
structure = NilFreeHash.new
structure[:a][:b] = 3
p strucrture[:a][:b] # => 3
It departs from the specification given in the question, though. When an undefined key is given, it will return an empty hash instread of nil.
p structure[:c] # => {}
If you build an instance of this NilFreeHash from the beginning and assign the key-values, it will work, but if you want to convert a hash into an instance of this class, that may be a problem.
You could just build a Hash subclass with an extra variadic method for digging all the way down with appropriate checks along the way. Something like this (with a better name of course):
class Thing < Hash
def find(*path)
path.inject(self) { |h, x| return nil if(!h.is_a?(Thing) || h[x].nil?); h[x] }
end
end
Then just use Things instead of hashes:
>> x = Thing.new
=> {}
>> x[:a] = Thing.new
=> {}
>> x[:a][:b] = 'k'
=> "k"
>> x.find(:a)
=> {:b=>"k"}
>> x.find(:a, :b)
=> "k"
>> x.find(:a, :b, :c)
=> nil
>> x.find(:a, :c, :d)
=> nil
This monkey patch function for Hash should be easiest (at least for me). It also doesn't alter structure i.e. changing nil's to {}. It would still also apply even if you're reading a tree from a raw source e.g. JSON. It also doesn't need to produce empty hash objects as it goes or parse a string. rescue nil was actually a good easy solution for me as I'm brave enough for such a low risk but I find it to essentially have a drawback with performance.
class ::Hash
def recurse(*keys)
v = self[keys.shift]
while keys.length > 0
return nil if not v.is_a? Hash
v = v[keys.shift]
end
v
end
end
Example:
> structure = { :a => { :b => 'foo' }}
=> {:a=>{:b=>"foo"}}
> structure.recurse(:a, :b)
=> "foo"
> structure.recurse(:a, :x)
=> nil
What's also good is that you can play around saved arrays with it:
> keys = [:a, :b]
=> [:a, :b]
> structure.recurse(*keys)
=> "foo"
> structure.recurse(*keys, :x1, :x2)
=> nil
The XKeys gem will read and auto-vivify-on-write nested hashes (::Hash) or hashes and arrays (::Auto, based on the key/index type) with a simple, clear, readable, and compact syntax by enhancing #[] and #[]=. The sentinel symbol :[] will push onto the end of an array.
require 'xkeys'
structure = {}.extend XKeys::Hash
structure[:a, :b] # nil
structure[:a, :b, :else => 0] # 0 (contextual default)
structure[:a] # nil, even after above
structure[:a, :b] = 'foo'
structure[:a, :b] # foo
You can use the andand gem, but I'm becoming more and more wary of it:
>> structure = { :a => { :b => 'foo' }} #=> {:a=>{:b=>"foo"}}
>> require 'andand' #=> true
>> structure[:a].andand[:b] #=> "foo"
>> structure[:c].andand[:b] #=> nil
There is the cute but wrong way to do this. Which is to monkey-patch NilClass to add a [] method that returns nil. I say it is the wrong approach because you have no idea what other software may have made a different version, or what behavior change in a future version of Ruby can be broken by this.
A better approach is to create a new object that works a lot like nil but supports this behavior. Make this new object the default return of your hashes. And then it will just work.
Alternately you can create a simple "nested lookup" function that you pass the hash and the keys to, which traverses the hashes in order, breaking out when it can.
I would personally prefer one of the latter two approaches. Though I think it would be cute if the first was integrated into the Ruby language. (But monkey-patching is a bad idea. Don't do that. Particularly not to demonstrate what a cool hacker you are.)
Not that I would do it, but you can Monkeypatch in NilClass#[]:
> structure = { :a => { :b => 'foo' }}
#=> {:a=>{:b=>"foo"}}
> structure[:x][:y]
NoMethodError: undefined method `[]' for nil:NilClass
from (irb):2
from C:/Ruby/bin/irb:12:in `<main>'
> class NilClass; def [](*a); end; end
#=> nil
> structure[:x][:y]
#=> nil
> structure[:a][:y]
#=> nil
> structure[:a][:b]
#=> "foo"
Go with #DigitalRoss's answer. Yes, it's more typing, but that's because it's safer.
In my case, I needed a two-dimensional matrix where each cell is a list of items.
I found this technique which seems to work. It might work for the OP:
$all = Hash.new()
def $all.[](k)
v = fetch(k, nil)
return v if v
h = Hash.new()
def h.[](k2)
v = fetch(k2, nil)
return v if v
list = Array.new()
store(k2, list)
return list
end
store(k, h)
return h
end
$all['g1-a']['g2-a'] << '1'
$all['g1-a']['g2-a'] << '2'
$all['g1-a']['g2-a'] << '3'
$all['g1-a']['g2-b'] << '4'
$all['g1-b']['g2-a'] << '5'
$all['g1-b']['g2-c'] << '6'
$all.keys.each do |group1|
$all[group1].keys.each do |group2|
$all[group1][group2].each do |item|
puts "#{group1} #{group2} #{item}"
end
end
end
The output is:
$ ruby -v && ruby t.rb
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-linux]
g1-a g2-a 1
g1-a g2-a 2
g1-a g2-a 3
g1-a g2-b 4
g1-b g2-a 5
g1-b g2-c 6
I am currently trying out this:
# --------------------------------------------------------------------
# System so that we chain methods together without worrying about nil
# values (a la Objective-c).
# Example:
# params[:foo].try?[:bar]
#
class Object
# Returns self, unless NilClass (see below)
def try?
self
end
end
class NilClass
class MethodMissingSink
include Singleton
def method_missing(meth, *args, &block)
end
end
def try?
MethodMissingSink.instance
end
end
I know the arguments against try, but it is useful when looking into things, like say, params.
Related
I have a nested hash, to which I need to add more deeply nested property/value pairs.
Sample A:
a = {}
a['x']['y']['z'] << 8
Normally I'd have to do this:
Sample B:
a = {}
a['x'] ||= a['x'] = {}
a['x']['y'] ||= a['x']['y'] = {}
a['x']['y']['z'] ||= a['x']['y']['z'] = []
Otherwise, I will get undefined method '<<' for nil:NillClass.
Is there some type of shorthand or function along the lines of code A instead of code B?
The most elegant solution for the deep-nested hash of any depth would be:
hash = Hash.new { |h, k| h[k] = h.dup.clear }
or, even better (credits to #Stefan)
hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
That way one might access any level:
hash[:a1][:a2][:a3][:a4] = :foo
#⇒ {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}
The idea is to clone the default_proc within the hash itself.
To give a little backgroud, there's a method Hash#dig which is present since ruby 2.3. With this you can safely attempt to read any number of keys:
{a: {b: {c: 1}}}.dig :a, :b, :c # => 1
{}.dig :a, :b, :c # => nil
of course, this doesn't solve your problem. You're looking for a write version. This has been proposed but rejected in Ruby core in the form of Hash#bury.
This method does almost exactly what you are looking for, however it can only set nested hash values and not append to nested arrays:
# start with empty hash
hash = {}
# define the inner array
hash.bury :x, :y, :z, []
# add to the inner array
hash[:x][:y][:z] << :some_val
You can get this method through the ruby-bury gem, or alternatively you can take their implementation from their source code
Here are a couple of ways that could be done.
arr = [:a1, :a2, :a3, :a4, :foo]
Use Enumerable#reduce (aka inject)
def hashify(arr)
arr[0..-3].reverse_each.reduce(arr[-2]=>arr[-1]) { |h,x| { x=>h } }
end
hashify(arr)
#=> {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}
Use recursion
def hashify(arr)
first, *rest = arr
rest.size == 1 ? { first=>rest.first } : { first=>hashify(rest) }
end
hashify(arr)
#=> {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}
You can consider using the get and set methods from the rodash gem in order to set a deeply nested value into a hash with a default value.
require 'rodash'
a = {}
key = ['x', 'y', 'z']
default_value = []
value = 8
current_value = Rodash.get(a, key, default_value)
Rodash.set(a, key, current_value << value)
a
# => {"x"=>{"y"=>{"z"=>[8]}}}
I use the following to deeply set/initialize arrays/hashes using a list of keys. If they keys are ints, it assumes indexing into an array, otherwise it assumes a hash:
def deep_set(target, path, value)
key = path.shift
return target[key] = value if path.empty?
child = target[key]
return deep_set(child, path, value) if child
deep_set(
target[key] = path[0].is_a?(Integer) ? [] : {},
path,
value,
)
end
Here is an example of using it:
target = {first: [:old_data]}
deep_set(target, [:first, 1, 1, :lol], 'foo')
puts target # {:first=>[:old_data, [nil, {:lol=>"foo"}]]}
It uses the fact that ruby allows you to set-and-expand arrays on the fly, which is a bit wonky, but works nicely here.
There's a nice idiom for adding to lists stored in a hash table:
(hash[key] ||= []) << new_value
Now, suppose I write a derivative hash class, like the ones found in Hashie, which does a deep-convert of any hash I store in it. Then what I store will not be the same object I passed to the = operator; Hash may be converted to Mash or Clash, and arrays may be copied.
Here's the problem. Ruby apparently returns, from the var= method, the value passed in, not the value that's stored. It doesn't matter what the var= method returns. The code below demonstrates this:
class C
attr_reader :foo
def foo=(value)
#foo = (value.is_a? Array) ? (value.clone) : value
end
end
c=C.new
puts "assignment: #{(c.foo ||= []) << 5}"
puts "c.foo is #{c.foo}"
puts "assignment: #{(c.foo ||= []) << 6}"
puts "c.foo is #{c.foo}"
output is
assignment: [5]
c.foo is []
assignment: [6]
c.foo is [6]
When I posted this as a bug to Hashie, Danielle Sucher explained what was happening and pointed out that "foo.send :bar=, 1" returns the value returned by the bar= method. (Hat tip for the research!) So I guess I could do:
c=C.new
puts "clunky assignment: #{(c.foo || c.send(:foo=, [])) << 5}"
puts "c.foo is #{c.foo}"
puts "assignment: #{(c.foo || c.send(:foo=, [])) << 6}"
puts "c.foo is #{c.foo}"
which prints
clunky assignment: [5]
c.foo is [5]
assignment: [5, 6]
c.foo is [5, 6]
Is there any more elegant way to do this?
Assignments evaluate to the value that is being assigned. Period.
In some other languages, assignments are statements, so they don't evaluate to anything. Those are really the only two sensible choices. Either don't evaluate to anything, or evaluate to the value being assigned. Everything else would be too surprising.
Since Ruby doesn't have statements, there is really only one choice.
The only "workaround" for this is: don't use assignment.
c.foo ||= []
c.foo << 5
Using two lines of code isn't the end of the world, and it's easier on the eyes.
The prettiest way to do this is to use default value for hash:
# h = Hash.new { [] }
h = Hash.new { |h,k| h[k] = [] }
But be ware that you cant use Hash.new([]) and then << because of way how Ruby store variables:
h = Hash.new([])
h[:a] # => []
h[:b] # => []
h[:a] << 10
h[:b] # => [10] O.o
it's caused by that Ruby store variables by reference, so as we created only one array instance, ad set it as default value then it will be shared between all hash cells (unless it will be overwrite, i.e. by h[:a] += [10]).
It is solved by using constructor with block (doc) Hash.new { [] }. With this each time when new key is created block is called and each value is different array.
EDIT: Fixed error that #Uri Agassi is writing about.
I am looking for a way to have, I would say synonym keys in the hash.
I want multiple keys to point to the same value, so I can read/write a value through any of these keys.
As example, it should work like that (let say :foo and :bar are synonyms)
hash[:foo] = "foo"
hash[:bar] = "bar"
puts hash[:foo] # => "bar"
Update 1
Let me add couple of details. The main reason why I need these synonyms, because I receive keys from external source, which I can't control, but multiple keys could actually be associated with the same value.
Rethink Your Data Structure
Depending on how you want to access your data, you can make either the keys or the values synonyms by making them an array. Either way, you'll need to do more work to parse the synonyms than the definitional word they share.
Keys as Definitions
For example, you could use the keys as the definition for your synonyms.
# Create your synonyms.
hash = {}
hash['foo'] = %w[foo bar]
hash
# => {"foo"=>["foo", "bar"]}
# Update the "definition" of your synonyms.
hash['baz'] = hash.delete('foo')
hash
# => {"baz"=>["foo", "bar"]}
Values as Definitions
You could also invert this structure and make your keys arrays of synonyms instead. For example:
hash = {["foo", "bar"]=>"foo"}
hash[hash.rassoc('foo').first] = 'baz'
=> {["foo", "bar"]=>"baz"}
You could subclass hash and override [] and []=.
class AliasedHash < Hash
def initialize(*args)
super
#aliases = {}
end
def alias(from,to)
#aliases[from] = to
self
end
def [](key)
super(alias_of(key))
end
def []=(key,value)
super(alias_of(key), value)
end
private
def alias_of(key)
#aliases.fetch(key,key)
end
end
ah = AliasedHash.new.alias(:bar,:foo)
ah[:foo] = 123
ah[:bar] # => 123
ah[:bar] = 456
ah[:foo] # => 456
What you can do is completely possible as long as you assign the same object to both keys.
variable_a = 'a'
hash = {foo: variable_a, bar: variable_a}
puts hash[:foo] #=> 'a'
hash[:bar].succ!
puts hash[:foo] #=> 'b'
This works because hash[:foo] and hash[:bar] both refer to the same instance of the letter a via variable_a. This however wouldn't work if you used the assignment hash = {foo: 'a', bar: 'a'} because in that case :foo and :bar refer to different instance variables.
The answer to your original post is:
hash[:foo] = hash[:bar]
and
hash[:foo].__id__ == hash[:bar].__id__it
will hold true as long as the value is a reference value (String, Array ...) .
The answer to your Update 1 could be:
input.reduce({ :k => {}, :v => {} }) { |t, (k, v)|
t[:k][t[:v][v] || k] = v;
t[:v][v] = k;
t
}[:k]
where «input» is an abstract enumerator (or array) of your input data as it comes [key, value]+, «:k» your result, and «:v» an inverted hash that serves the purpose of finding a key if its value is already present.
There is a common idiom of using substitutions like:
def with clazz, &block
yield clazz
clazz
end
with Hash.new |hash|
hash.merge!{:a => 1}
end
Is there a way to go further and define #with to have a possibility of doing:
with Hash.new |hash|
merge!{:a => 1}
end
or even:
with Hash.new do
merge!{:a => 1}
end
?
UPDATE
Later accidentally I found exactly what I was looking for (solution similar to the accepted one):
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/19153
UPDATE 2
It was added to sugar-high/dsl in https://github.com/kristianmandrup/sugar-high
UPDATE 3
docille project on Github exploits this idea very nicely.
If you are referring to the way in which Rails does routing then I think you need to do something like this
def with(instance, &block)
instance.instance_eval(&block)
instance
end
with(Hash.new) do
merge!({:a => 1})
merge!({:b => 1})
end
This is how I can see it being done in the Rails source anyway start by looking at the draw method in action_pack/lib/action_dispatch/routing/route_set
Isn't your pseudo-Ruby:
with Hash.new do |hash|
merge!{:a => 1}
end
The same thing as using 1.9's tap? For example:
>> x = Hash[:a, :b].tap { |h| h.merge!({:c => :d}) }
=> {:a=>:b, :c=>:d}
You still have to name the block argument of course.
You can use the ruby builtin tap:
Hash.new.tap do |hash|
hash.merge! a: 1
end
This can even be "abused" for multiple objects:
[one_long_name, another_long_name].tap do |(a,b)|
a.prop = b.prop
end
Of course both don't give you exactly what with would do according to your example: The block won't be evaluated within the instance of the object. But I prefer a lot to use tap with multiple objects, plus tap return self, so it can be chained:
[one_long_name, another_long_name].tap {|(a,b)| a.prop = b.prop }.inspect
This question already has answers here:
How to avoid NoMethodError for missing elements in nested hashes, without repeated nil checks?
(16 answers)
Closed 7 years ago.
What's the most concise way to determine if #hash[:key1][:key2] is defined, that does not throw an error if #hash or #hash[:key1] are nil?
defined?(#hash[:key1][:key2]) returns True if #hash[:key1] exists (it does not determine whether :key2 is defined)
When using ActiveSupport (Rails) or Backports, you can use try:
#hash[:key1].try(:fetch, :key2)
You could even handle #hash being nil:
#hash.try(:fetch, :key1).try(:fetch, :key2)
If you want #hash to always return a hash for a missing key:
#hash = Hash.new { |h,k| h[k] = {} }
#hash[:foo] # => {}
You could also define this recursive:
def recursive_hash
Hash.new { |h,k| h[k] = recursive_hash }
end
#hash = recursive_hash
#hash[:foo][:bar][:blah] = 10
#hash # => {:foo => {:bar => {:blah => 10}}}
But to answer your question:
module HasNestedKey
Hash.send(:include, self)
def has_nested_key?(*args)
return false unless sub = self[args.shift]
return true if args.empty?
sub.respond_to?(:has_nested_key?) and sub.has_nested_key?(*args)
end
end
#hash.has_nested_key? :key1, :key2
Perhaps I am missing something, but if all you care about is concise...why not:
#hash && #hash[:key1] && #hash[:key1][:key2]
or if you want to save a few characters
#hash && (h = #hash[:key1]) && h[:key2]
if any part of this fails, it returns nil otherwise it returns the value associated with :key2 or true.
The reason the defined? returns true even if :key2 is not there is because it just checks whether the object you are referencing exists, which in that case is the method [] which is an alias for the method fetch which does exist on the hash #hash[:key1] but if that were to return nil, there is no fetch method on nil and it would return nil. That being said, if you had to go n deep into an embedded hash, at some point it would become more efficient to call:
defined?(#hash[:key1][:key2][:key3]) && #hash[:key1][:key2][:key3]
Using Hash#fetch
You can use the Hash#fetch method with a default of {} so that it is safe to call has_key? even if the first level key doesn't exist. e.g.
!hash.nil? && hash.fetch(key1, {}).has_key?(key2)
Alternative
Alternatively you can use the conditional operator e.g.
!hash.nil? && (hash.has_key?(key1) ? hash[key1].has_key?(key2) : false)
i.e. if hash doesn't have key key1 then just return false without looking for the second level key. If it does have key1 then return the result of checking key1's value for key2.
Also, if you want to check that hash[key1]'s value has a has_key? method before calling it:
!hash.nil? && (hash.has_key?(key1) ? hash[key1].respond_to?(:has_key?) &&
hash[key1].has_key?(key2) : false)
#hash[:key1].has_key? :key2
If you don't care about distinguishing nonexistent #hash[:key1][:key2] (at any of 3 levels) from #hash[:key1][:key2] == nil, this is quite clean and works for any depth:
[:key1,:key2].inject(hash){|h,k| h && h[k]}
If you want nil to be treated as existing, use this instead:
(hash[:key1].has_key?(:key2) rescue false)
Another option, one that I just discovered, is to extend Hash with a seek method. Technique comes from Corey O'Daniel.
Stick this in an initializer:
class Hash
def seek(*_keys_)
last_level = self
sought_value = nil
_keys_.each_with_index do |_key_, _idx_|
if last_level.is_a?(Hash) && last_level.has_key?(_key_)
if _idx_ + 1 == _keys_.length
sought_value = last_level[_key_]
else
last_level = last_level[_key_]
end
else
break
end
end
sought_value
end
end
Then just call:
#key_i_need = #hash.seek :one, :two, :three
You'll get the value, or nil if it doesn't exist.