0

This is kinda of confusing.

If you have a hash that contains more hashes that also have hashes and so on, how do you determine if a member exists that is more than one layer deep?

For example:

hash 1 =
{
    "layer1" => 
    {
        "layer2" => 
        {
            "layer3" => ['Something', 'Array']
        }
    }
}

How would you go about verifying 'Something' exists in the above hash if the hash only has:

hash2 =
{
    "layer1" => 
    {
        "layer2" => ['Other Array']
    }
}

For example, I would try to do:

if hash2['layer1']['layer2']['layer3'].contains? 'Something'
  puts "Found Something!"
end

But this would error undefined method `contains?' for nil:NilClass. Where layer3 would be the NilClass because it doesn't exist. It would suffice if one of these embedded hashses was Nil then say it doesn't exist, but you can't easily test for their existence because that also will return Nil if you are a layer too deep. Is there a function in ruby that checks each top level layer for Nil recursively instead of the specific member you request when you would call .nil? E.g. What I would think would work!

if hash2['layer1']['layer2']['layer3'].nil?
  puts 'layer3 exists'
end

But .nil? only checks if 'layer3' exists. IS there a method that starts at 'layer1' then checks if 'layer2' exists then 'layer3' and so forth. And at any of the parts are nil it returns false? Because if 'layer2' or 'layer1' didn't exist it would error out saying undefined method `[]' for nil:NilClass.

jStaff
  • 650
  • 1
  • 9
  • 25

2 Answers2

6

Check out Hash#dig(). It takes an array of keys and looks them up recursively, returning nil if any of them are missing. From the docs:

h = { foo: {bar: {baz: 1}}}

h.dig(:foo, :bar, :baz)           #=> 1
h.dig(:foo, :zot)                 #=> nil

Just note that if baz was nil, that first dig call would have returned nil. So it's only a replacement for checking if a nested key exists if you know you won't have nils stored in your hash.

Xavier Holt
  • 14,471
  • 4
  • 43
  • 56
  • 1
    `.dig` is awesome! Also keep in mind it only works in versions 2.3+. If this is going inside a gem or some other library it will not work for users in previous versions. – whodini9 Oct 06 '17 at 18:38
  • Omg, this is exactly what I was looking for. It is hard to look up the solution for this because the way of phrasing this problem can lead to so many different versions. – jStaff Oct 09 '17 at 13:57
0

Not the best solution, but I wrote this:

h = {"layer1"=>
       {"layer2"=>
          {"layer3"=>["Something", "Array"]}
       },
     "layerx" => ["d"],
     "layerz" => {"layera" => "deep"}
}

def vals(h)
  return h if !h.is_a?(Hash)
  h.values.map(&method(:vals)).flatten
end
vals(h) #=> ["Something", "Array", "d", "deep"]

vals gives the values nested deep inside hash-es. You can check if your element is there in that.

kiddorails
  • 12,961
  • 2
  • 32
  • 41