Intersection between array of hashes but returning the full hash

Viewed 95

There are two arrays of hashes

actual = [{id: 1, text: "A", type: "X", state: "enabled"}, {id: 2, text: "B", type: "X", state: "enabled"}]

expected = [{text: "A", type: "X", state: "enabled"}]

I need to get the :id of all Hash not included in "expected". The comparison has to be done using three keys (text, type, state). In this case

results = [{id: 2}]

At the moment I am using this but it's very long and not performing for a big array. Is there a better way?

 actuals        = actuals.map{|a| a.slice(:text, :type, :state)}
 expected       = expected.map{|a| a.slice(:text, :type, :state)}
 not_expected   = actuals - expected
      
      results = actuals.select{|actual| 
         not_expected.find{|n| 
            n[:text]   == actual[:text] &&
            n[:type]   == actual[:type] &&
            n[:state]  == actual[:state]
         }.present?
       } 
2 Answers
actual = [{id: 1, text: "A", type: "X", state: "enabled"}, {id: 2, text: "B", type: "X", state: "enabled"}]
expected = [{text: "A", type: "X", state: "enabled"}]
comparable_expected = expected.map { |e| e.slice(:text, :type, :state) }

results = actual.select do |a| 
  not comparable_expected.include? a.slice(:text, :type, :state)
end

resulting_ids = results.map(&:id)
exp = expected.first
  #=> {text: "A", type: "X", state: "enabled"}

actual.reject { |h| h == h.merge(exp) }.map { |h| h.slice(:id) }
  #=> [{:id=>2}]

Try it

Hashes in actual are rejected if they are unaffected when merged with exp, meaning that the hashes being merged have the same values for all the keys in exp. Each remaining hash h in actual is then mapped to { id: h[:id] }, using Hash#slice.

One advantage of this approach is that the code need not be changed if exp is changed to a hash having different keys.

Related