Hommer Smith
Hommer Smith

Reputation: 27862

Find hash in another array and substract in Ruby

I have two arrays of hashes that look like this:

a = [ { car_id: 1, motor_id: 1, quantity: 5 },
      { car_id: 1, motor_id: 2, quantity: 6 },
      { car_id: 5, motor_id: 3, quantity: 3 } ]

b = [ { car_id: 1, motor_id: 1, quantity: 2 },
      { car_id: 1, motor_id: 2, quantity: 3 } ]

I want to substract the quantities from each hash in b from the hashes in a for those hashes that have the same car_id & motor_id. So, my expected result would be:

c = [ {car_id: 1, motor_id: 1, quantity: 3},
      {car_id: 1, motor_id: 2, quantity: 3},
      {car_id: 5, motor_id: 3, quantity: 3 } ]

What is a good way to do this in Ruby? My thoughts were to iterate over a, and for each element find if there is any in b that have the same car_id and motor_id, if so, substract, and continue.

Upvotes: 0

Views: 86

Answers (3)

ThunderThunder
ThunderThunder

Reputation: 158

Perhaps, you'd find the following more intuitive:

    matched_result = []
    while current_a = a.shift
      if current_b = b.shift
        if current_a[:car_id] == current_b[:car_id]  && current_a[:motor_id] == current_b[:motor_id]
          matched_result << {car_id: current_a[:car_id], motor_id: current_a[:motor_id], quantity: current_a[:quantity] - current_b[:quantity]}
        end
      else
        matched_result << current_a
      end
    end
    p matched_result

Upvotes: 0

Cary Swoveland
Cary Swoveland

Reputation: 110755

I suggest you first create a hash bqty for b that, for each element (hash) g of b, maps [g[:car_id], g[:motor_id]] into g[:quantity]:

bqty = b.each_with_object({}) {|g,h| h[[g[:car_id], g[:motor_id]]] = g[:quantity]}
  #=> {[1, 1]=>2, [1, 2]=>3}

Next, map each element (hash) g of a to the desired hash. This is done by merging g into an empty hash h (or g.dup), then, if there is an element of bqty with the key key = [h[:car_id], h[:motor_id]], subtract bqty[key] from h[:quantity]. Note this leaves a and b unchanged.

a.map do |g|
  {}.merge(g).tap do |h|
    key = [h[:car_id], h[:motor_id]]
    h[:quantity] -= bqty[key] if bqty.key?(key)
  end
end
  #=> [{:car_id=>1, :motor_id=>1, :quantity=>3},
  #    {:car_id=>1, :motor_id=>2, :quantity=>3},
  #    {:car_id=>5, :motor_id=>3, :quantity=>3}]

An alternative to the antepenultimate1 line is:

    h[:quantity] -= bqty[key].to_i

since nil.to_i #=> 0.

1. How can one pass up an opportunity to use such a word?

Upvotes: 2

user12341234
user12341234

Reputation: 7223

This is basically what you suggested, except instead of an explicit check for car_id/motor_id it uses a hash instead.

ids = ->(h){h.values_at(:car_id, :motor_id)} # A function for extracting the key values
lookup = Hash[*a.flat_map{|h| [ids[h], h]}] # A map from ids -> hash w/ quantity
b.each{|h| lookup[ids[h]][:quantity] -= h[:quantity]} # Subtract each b from an a (will error if the a doesn't exist)
a == c # == true

Upvotes: 0

Related Questions