user2840647
user2840647

Reputation: 1314

Finding all key from one hash, in an array of hashes

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

Answers (1)

Cary Swoveland
Cary Swoveland

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

Related Questions