Reputation: 25682
I am trying to write a function which returns the value of a hash key, when provided with an array of keys (and 'nil' if the key doesn't exist).
Consider the hash:
my_hash = {
font_size: 10,
font_family: "Arial",
boo: {
name: 'blah'
}
}
A stub of the method might be:
def get_value(hash, array_of_keys)
...
end
The reason being that I can access different keys in the hash which may not actually exist. So for example, I want 'blah', normally I would call my_hash[:boo][:name]
however it may not already exist or it may be very deep. What I would like to do is have my function, which I could call with get_value(my_hash, [:boo, :name])
so that I can test that each key exists (so I don't get exceptions if any of them do not exist, and where it doesn't matter how 'deep' the value might be).
In my case, its not feasible to check the existence of each value I require (I might need to test existence of 10 consecutive keys) I simply need a function that I can say which value I need and get the value if it exists, and nil if it doesn't (so an exception isn't thrown trying to retrieve it).
I gave it a shot, trying to use a recursive function that 'pop's the first element in the array each time it loops but i couldn't workout how to return my value.
def get_value(val, arr)
if arr.count > 1
arr.each do |a|
get_value val[a], arr.pop
end
else
val[a]
end
end
get_value s, [:boo, :name]
This is my attempt (which doesn't work obviously) - can anyone help me solve this problem, or have an alternate solution that might be more elegant? A couple of points:
Upvotes: 3
Views: 3112
Reputation: 168249
[:font_size].inject(my_hash){|h, k| h.to_h[k]} #=> 10
[:boo, :name].inject(my_hash){|h, k| h.to_h[k]} #=> "blah"
[:boo, :foo].inject(my_hash){|h, k| h.to_h[k]} #=> nil
Upvotes: 5
Reputation: 3080
My stab at it, using recursion:
def get_value(hash, keys_array)
key = keys_array.shift
if hash.has_key? key
if hash[key].is_a?(Hash) && keys_array.size >= 1
get_value(hash[key], keys_array)
else
hash[key]
end
else
nil
end
end
Nice problem by the way :)
Upvotes: 1
Reputation: 47862
class Hash
def get_value(array_of_keys)
return nil unless array_of_keys.is_a? Array
return nil if array_of_keys.empty?
return nil unless self.has_key? array_of_keys.first
if self[array_of_keys.first].is_a? Hash
self[array_of_keys.first].get_value(array_of_keys[1..-1])
else
self[array_of_keys.first]
end
end
end
my_hash = {
font_size: 10,
font_family: "Arial",
boo: {
name: 'blah'
} ,
radley: {
one: {
more: {
time: 'now'
}
}
}
}
p my_hash.get_value [:boo, :name]
p my_hash.get_value [:radley, :one, :more, :time]
p my_hash.get_value [:radley, :one, :other, :time]
I'm adding the method directly to the Hash
class so that you can call it as an instance method on existing hashes (makes for a nicer usage).
The method takes your array, returns nil if the argument is not actually an array, if the array is empty, of if the Hash
doesn't contain a key matching the first element of the array.
Next, check to see if the value of that key is itself a Hash
.
If so, call this very method on that Hash
, with the rest of the array!
If the value is anything but a hash, return that value.
Upvotes: 1