Ruby: Get all keys in a hash (including sub keys)
16,797
Solution 1
This will give you an array of all the keys for any level of nesting.
def get_em(h)
h.each_with_object([]) do |(k,v),keys|
keys << k
keys.concat(get_em(v)) if v.is_a? Hash
end
end
hash = {"a" => 1, "b" => {"c" => {"d" => 3}}}
get_em(hash) # => ["a", "b", "c", "d"]
Solution 2
I find grep
useful here:
def get_keys(hash)
( hash.keys + hash.values.grep(Hash){|sub_hash| get_keys(sub_hash) } ).flatten
end
p get_keys my_nested_hash #=> ["a", "b", "c"]
I like the solution as it is short, yet it reads very nicely.
Solution 3
Version that keeps the hierarchy of the keys
- Works with arrays
- Works with nested hashes
keys_only.rb
# one-liner
def keys_only(h); h.map { |k, v| v = v.first if v.is_a?(Array); v.is_a?(Hash) ? [k, keys_only(v)] : k }; end
# nicer
def keys_only(h)
h.map do |k, v|
v = v.first if v.is_a?(Array);
if v.is_a?(Hash)
[k, keys_only(v)]
else
k
end
end
end
hash = { a: 1, b: { c: { d: 3 } }, e: [{ f: 3 }, { f: 5 }] }
keys_only(hash)
# => [:a, [:b, [[:c, [:d]]]], [:e, [:f]]]
P.S.: Yes, it looks like a lexer :D
Bonus: Print the keys in a nice nested list
# one-liner
def print_keys(a, n = 0); a.each { |el| el.is_a?(Array) ? el[1] && el[1].class == Array ? print_keys(el, n) : print_keys(el, n + 1) : (puts " " * n + "- #{el}") }; nil; end
# nicer
def print_keys(a, n = 0)
a.each do |el|
if el.is_a?(Array)
if el[1] && el[1].class == Array
print_keys(el, n)
else
print_keys(el, n + 1)
end
else
puts " " * n + "- #{el}"
end
end
nil
end
> print_keys(keys_only(hash))
- a
- b
- c
- d
- e
- f
Solution 4
def get_all_keys(hash)
hash.map do |k, v|
Hash === v ? [k, get_all_keys(v)] : [k]
end.flatten
end
Solution 5
Please take a look of following code:
hash = {"a" => 1, "b" => {"c" => 3}}
keys = hash.keys + hash.select{|_,value|value.is_a?(Hash)}
.map{|_,value| value.keys}.flatten
p keys
result:
["a", "b", "c"]
New solution, considering @Bala's comments.
class Hash
def recursive_keys
if any?{|_,value| value.is_a?(Hash)}
keys + select{|_,value|value.is_a?(Hash)}
.map{|_,value| value.recursive_keys}.flatten
else
keys
end
end
end
hash = {"a" => 1, "b" => {"c" => {"d" => 3}}, "e" => {"f" => 3}}
p hash.recursive_keys
result:
["a", "b", "e", "c", "d", "f"]
Related videos on Youtube
Author by
user2211703
Updated on June 18, 2022Comments
-
user2211703 about 2 years
let's have this hash:
hash = {"a" => 1, "b" => {"c" => 3}} hash.get_all_keys => ["a", "b", "c"]
how can i get all keys since
hash.keys
returns just["a", "b"]
-
Cary Swoveland over 10 yearsThanks, but in future consider holding off a bit before selecting an answer, as it may discourage other, possibly better ones being offered.
-
-
Bala over 10 yearsThis may fail if the hash is
hash = {"a" => 1, "b" => {"c" => {"c" => 3}}, "c" => {"b" => 3}}
-
uncutstone over 10 yearsYou are right. If hash is nested multi-level, I should give a recursive solution.
-
uncutstone over 10 years@Bala, I give a new solution.
-
uncutstone over 10 years=== is asymmetric, that means v === Hash gives different result from Hash === v.
-
hirolau over 10 yearsI think === is hard to read and understand, it should really only be used in case statements.
-
Cary Swoveland over 10 yearsInteresting use of
tap
. You might want to use__method__
in place of:get_all_keys
. If the method were renamed and that not changed, it might take awhile to find the bug. Aside: do you know whyrespond to?
does not check to see if its argument is an existing method? -
spickermann over 10 years@CarySwoveland: Thank you, I like the idea with
__method__
. I do not understand your question about the behavior ofrespond_to?
, please explain. It seams like I missed something important. -
Cary Swoveland over 10 years
v.respond_to?(sym)
=> false for any Symbolsym
that is not a method. Ruby could check at runtime to see ifsym
is a method and raise an exception if not, but it evidently does not do so. I was wondering why. It just occurred to me that formethod_missing
to be useful, Ruby probably never checks to see if a Symbol is an existing method. -
Cary Swoveland over 10 yearsArup, I think
each_with_object
is greatly underused. Here, for example, one would often seereduce([])
(akainject
) instead, but that requires the array holding the keys to be returned to the block, which may require that ugly; keys
) at the end. I consideredeach_with_object
here but decided to instead havekeys = []
before andkeys
after. One is often faced with this choice. I think the simpler way is more readable, but it does add two lines. Do you have an opinion about this? -
Arup Rakshit over 10 years@CarySwoveland In this kind of situation, I always used
each_with_object
. For me it looks more expressive in Ruby way. As I often found Ruby experts like short and concise, but expressive code. I tired to do the same. :) -
hirolau over 10 yearsTrue, but that is, just like the case statement, a function build for the case equality operator.
-
spickermann over 10 yearsRuby seams to check only if the method name is in the
methods
array and therefore will not recognize ifmethod_missing
might respond. And to analyze all possible behavior of amethod_missing
is almost impossible without actually running that method, what might have side effects. Interesting read about the newrespond_to_missing?
in Ruby 1.9.2: blog.marc-andre.ca/2010/11/15/methodmissing-politely -
A moskal escaping from Russia over 6 yearsBut it doesn't return nested keys
{ a: { b: 1 } }.keys # [:a]
:(.