Kyle Strand
Kyle Strand

Reputation: 16499

Subtract two hashes in Ruby

Can the hash class be modified so that given two hashes, a new hash containing only keys that are present in one hash but not the other can be created?

E.g.:

h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}

h2 = {"Cat" => 100, "Dog" => 5, "Bison" => 30}

h1.difference(h2) = {"Bird" => 2, "Snake" => 10}

Optionally, the difference method could include any key/value pairs such that the key is present in both hashes but the value differs between them.

Upvotes: 13

Views: 11038

Answers (5)

montrealmike
montrealmike

Reputation: 11631

For deep nesting you can add a bit of recursion, something like (untested)

class Hash
  def -(h2)
    raise ArgumentError unless h2.is_a?(Hash)
    h1 = dup
    h1.delete_if do |k, v|
      if v.is_a?(Hash) && h2[k].is_a?(Hash)
        h1[k] = v - h2[k]
        h1[k].blank?
      else
        h2[k] == v
      end
    end
   end
  end
end

Upvotes: 0

Ben
Ben

Reputation: 1342

try using hashdiff gem.

diff=HashDiff.diff(h1,h2)

Upvotes: 0

Kyle Strand
Kyle Strand

Reputation: 16499

Use the reject method:

class Hash
  def difference(other)
    reject do |k,v|
      other.has_key? k
    end
  end
end

To only reject key/value pairs if the values are identical (as per mallanaga's suggestion via a comment on my original answer, which I have deleted):

class Hash
  def difference(other)
    reject do |k,v|
      other.has_key?(k) && other[k] == v
    end
  end
end

Upvotes: 10

Cary Swoveland
Cary Swoveland

Reputation: 110675

h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h2 = {"Cat" => 999, "Dog" => 5, "Bison" => 30}

Case 1: keep all key/value pairs k=>v in h1 for which there is no key k in h2

This is one way:

h1.dup.delete_if { |k,_| h2.key?(k) }
  #=> {"Bird"=>2, "Snake"=>10}

This is another:

class Array
  alias :spaceship :<=>
  def <=>(o)
    first <=> o.first
  end
end

(h1.to_a - h2.to_a).to_h
  #=> {"Bird"=>2, "Snake"=>10}

class Array
  alias :<=> :spaceship
  remove_method(:spaceship)
end

Case 2: keep all key/value pairs in h1 that are not in h2

All you need for this is:

(h1.to_a - h2.to_a).to_h
  #=> {"Cat"=>100, "Bird"=>2, "Snake"=>10}

Array#to_h was introduced in Ruby 2.0. For earlier versions, use Hash[].

Upvotes: 16

sawa
sawa

Reputation: 168101

You can do this:

h2.each_with_object(h1.dup){|(k, v), h| h.delete(k)}

Upvotes: 2

Related Questions