Reputation: 1314
I have a single hash, which I need to compare to an array of hashes.
For example:
# Array of Hashes to compare another hash against
@stored_hash = [
{:a => 1, :b => 2, :c => {:d => 3, :e => 4, :f => 5}},
{:a => 1, :b => 2, :c => {:d => 3, :e => 4, :f => 5}},
{:a => 1, :b => 2, :c => {:d => 3, :e => 4, :f => 5}},
]
# Hash to compare to Array of Hashes
@sample_hash = {:d => 3, :e => 4, :f => 5}
I'm looking for the keys from @sample_hash
that match any of the keys from all @stored_hashes
I can compare a single hash to another hash like so
res = @sample_hash.keys.select { |k| @stored_hash.key?(k) }
But I cannot get it to work for an array of hashes. Help appreciated.
Edit
This is what I have tried so far.
res = @sample_hash.keys.select { |k| @stored_hash.map(&:keys).include?(k) }
Which seems to just stall the program (I'm working with a very large data set)
I also tried this:
res = @sample_hash.keys.select { |k| @stored_hash.map { |x| x.key?(k) } }
Which does return results, but if I do the reverse:
res = @sample_hash.keys.reject { |k| @stored_hash.map { |x| x.key?(k) } }
I get no results (I know for a fact that there are both common and uncommon keys in the hashes)
Upvotes: 0
Views: 184
Reputation: 110675
We can do that in two steps.
Step 1
stored_hash = [
{ :a => 1, :b => 2, :c => { :d => 3, :e => 4, :f => 5 } },
{ :a => 1, :b => 2, :c => { :d => 3, :e => 4, :g => 5 } },
{ :a => 1, :b => 2, :c => { :d => 3, :e => 4, :h => 5 } },
]
Note that I've changed stored_hash
somewhat.
def all_keys_all_hashes(arr)
arr.each_with_object([]) { |h,keys| keys.concat(all_keys(h)) }.uniq
end
def all_keys(h)
h.each_with_object([]) do |(k,v),arr|
arr << k
arr.concat(all_keys(v)) if v.is_a? Hash
end
end
keys = all_keys_all_hashes(stored_hash)
#=> [:a, :b, :c, :d, :e, :f, :g, :h]
Step 2
sample_hash = {:d => 3, :e => 4, :f => 5, :z => 99 }
sample_hash.keys & keys
#=> [:d, :e, :f]
Addendum
If you have a problem like this one that's a one-time task, here's a "quick & dirty" way of getting all the keys in stored_hash
, provided they are all symbols, strings or natural numbers. The approach is to convert the array to a string and then use a regular expression to extract the keys, followed by a bit of processing.
Firstly, to deal with a more general case, let's modify the first key in each of the first two elements of stored_hash
:
stored_hash = [
{"a" => 1, :b => 2, :c => {:d => 3, :e => 4, :f => 5}},
{ 99 => 1, :b => 2, :c => {:d => 3, :e => 4, :f => 5}},
{ :a => 1, :b => 2, :c => {:d => 3, :e => 4, :f => 5}},
]
You could to this:
stored_hash.to_s.scan(/[{,]\s*\K[^{,]+?(?==>)/).uniq.map do |s|
case
when (s[0] == ?:) then s[1..-1].to_sym
when (s =~ /^\d+$/) then s.to_i
else s.gsub(?", '')
end
end
#=> ["a", :b, :c, :d, :e, :f, 99, :a]
\K
instructs Ruby to match on what comes before, but not include it in the match that it retains. It's particularly handy when the preceding match is variable-length (\s*
), as a positive lookbehind cannot be used for variable-length matches.
This does not deal with symbols with quotes (e.g., :"text"
) properly, but it would not be difficult to modify the regex to handle that case.
Here are the steps:
str = stored_hash.to_s
#=> "[{\"a\"=>1, :b=>2, :c=>{:d=>3, :e=>4, :f=>5}}, " +
# "{99=>1, :b=>2, :c=>{:d=>3, :e=>4, :f=>5}}, " +
# "{:a=>1, :b=>2, :c=>{:d=>3, :e=>4, :f=>5}}]"
(I've broken the string into three pieces so it can be read without the use of horizontal scrolling.)
a1 = str.scan(/[{,]\s*\K[^{,]+?(?==>)/)
#=> ["\"a\"", ":b", ":c", ":d", ":e", ":f",
# "99", ":b", ":c", ":d", ":e", ":f",
# ":a", ":b", ":c", ":d", ":e", ":f"]
a2 = a1.uniq
#=> ["\"a\"", ":b", ":c", ":d", ":e", ":f", "99", ":a"]
a2.map do |s|
case
when (s[0] == ?:) then s[1..-1].to_sym
when (s =~ /^\d+$/) then s.to_i
else s.gsub(?", '')
end
end
#=> ["a", :b, :c, :d, :e, :f, 99, :a]
Upvotes: 2