Lucas Oliveira
Lucas Oliveira

Reputation: 191

Hash subtraction in ruby

I have 2 hashes (that later will be converted to array, not sure if this is relevant):

Hash1 = {"X"=>2, "Y"=>1, "Z"=>1}

Hash2 = {"X"=>1, "Y"=>1}

I need to subtract them like (Hash3 = Hash1 - Hash2), and I need the result of Hash3 to be, in this case :

Hash3 = {"X"=>1, "Y"=>0, "Z"=>1}

All examples and answers I've seen led to results where the key which had value equals 0 (Y) to be absent in the resulting hash, which is not what I need.

I'm using Ruby 2.3.3 and Rails 5.0

Upvotes: 5

Views: 1862

Answers (3)

Stefan
Stefan

Reputation: 114178

You can merge them:

h1 = {"X"=>2, "Y"=>1, "Z"=>1}
h2 = {"X"=>1, "Y"=>1}

h1.merge(h2) { |k, v1, v2| v1 - v2 }
#=> {"X"=>1, "Y"=>0, "Z"=>1}

Whenever a key is present in both hashes, the block is called to determine the new value.


Due to this behavior it will not result in negative values if a key is only present in h2:

h1 = {"X"=>2, "Y"=>1}
h2 = {"X"=>1, "Y"=>1, "Z"=>1}

h1.merge(h2) { |k, v1, v2| v1 - v2 }
#=> {"X"=>1, "Y"=>0, "Z"=>1}

You might expect:

#=> {"X"=>1, "Y"=>0, "Z"=>-1}

Which is what tadman's solution would return.

Upvotes: 16

m3characters
m3characters

Reputation: 2290

You can also map them (but the merge solution is better since it will take care of getting you the values from 2 even if the key is not present in 1):

hash3 = hash1.map{|k,v| { k: (v-(hash2[k.to_s] || 0)) }.stringify_keys }

This will yield an array

Upvotes: 0

tadman
tadman

Reputation: 211590

It's not too difficult if you break it down into two steps:

def hash_sub(a, b)
  (a.keys + b.keys).uniq.map do |k|
    [ k, a[k].to_i - b[k].to_i ]
  end.to_h
end

hash_sub({"X"=>2, "Y"=>1, "Z"=>1}, {"X"=>1, "Y"=>1})
# => {"X"=>1, "Y"=>0, "Z"=>1}

The first step is to compute all the possible (unique) keys by combining the keys from the two hashes, and then convert to a new hash by subtracting one from the other, forcing conversion to an integer with .to_i.

Upvotes: 4

Related Questions