简体   繁体   中英

Method to get value from a hash from array of keys in Ruby

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:

  • The keys are not guaranteed to exist, and I'd like to return nil in those cases, and
  • The hash could potentially be very deep so it needs to handle any hash and any number of keys in the array (which is why I thought recursion might be the answer, but now I'm not so sure).
[: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
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]

Explanation

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.

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
  1. I remove the first key from the keys_array
  2. I check if that key exists in the Hash
  3. If yes I check if the value for that key is a Hash and that we still have keys left in the array, if that's the case I call the method with that value and the array of keys left
  4. If it's not a Hash, I just return the value
  5. If the key does not exist, return nil

Nice problem by the way :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM