views:

63

answers:

4

I'm using ruby 1.8.7 and I need to compare two hashes that I have, which are essentially the attributes of a model. Hash A is smaller than Hash B, and Hash B has all of the attributes of hash A, plus some extra attributes I don't care about. My overarching goal is to see if the elements of A are the same as the respective elements of B. So for example

@hash_a = {:cat => 'Bubby', :dog => 'Gizmo'}  
@hash_b = {:cat => 'Bubby', :dog => 'Gizmo', :lion => 'Simba'}  
@hash_a == @hash_b
#=> true

Now it gets a little bit more complicated than that because the fields don't match up completely, even though they referance the same piece of information

@hash_a = {:cats_name => 'Bubby', :dog => 'Gizmo'}  
@hash_b = {:cat => 'Bubby', :dog => 'Gizmo', :lion => 'Simba'}  
@hash_a == @hash_b
#=> true

What I'm working on is a process that compares two matching items, updates it if the fields have changed, and only if they changed. Or creates a new item if it cannot find a matching item. Changing the names of the hash itself is not an option. Currently I'm just comparing each field in a private method to see if they are equal.

return hash_a[:cat] == hash_b[:cats_name] && hash_a[:dog] == hash_b[:dog] 

I feel like there has to be a better way, I'm looking for something faster and more elegant than this.

A: 

One possibility is to first remap the keys of one hash and then perform a set subset operation:

require 'set'

def remap_keys(hash, key_map)
  hash.inject({}) do |acc, pair|
    key, value = pair
    remapped_key = key_map[key] || key
    acc[remapped_key] = value
    acc
  end
end

def hash_subset?(a, b)
  set_a = Set.new(a)
  set_b = Set.new(b)
  set_a.subset?(set_b)
end

hash_a = {:cats_name => 'Bubby', :dog => 'Gizmo'}  
hash_b = {:cat => 'Bubby', :dog => 'Gizmo', :lion => 'Simba'}  

puts hash_subset?(remap_keys(hash_a, {:cats_name => :cat}), hash_b)

However, I'm sure there are more efficient ways to do it. More than one way to skin a :cat, eh?!

Richard Cook
+1  A: 

Heh, if you really want fast and elegant, here you go:

(a = @hash_a.values; (a & @hash_b.values) == a)

There are certain obvious limitations...

DigitalRoss
doesn't this ignore the keys though? what if {:cat => 'Gizmo', :dog => 'Bubby'}
Mysrt
Si, that's the "limitation".
DigitalRoss
Oh, and you might need to sort the results, although it wasn't needed with your example data.
DigitalRoss
A: 

Here's how I'd do it:

def eql hash1, hash2, rewire = {}
  map = Hash.new {|h, key| rewire[key] || key}
  !hash1.any? do |key, val| 
    hash2[map[key]] != val
  end
end


hash_a = {:cats_name => 'Bubby', :dog => 'Gizmo'}  
hash_b = {:cat => 'Bubby', :dog => 'Gizmo', :lion => 'Simba'}  
p eql(hash_a, hash_b) #=> false

hash_a = {:cats_name => 'Bubby', :dog => 'Gizmo'}  
hash_b = {:cat => 'Bubby', :dog => 'Gizmo', :lion => 'Simba'}  
p eql(hash_a, hash_b, :cats_name => :cat)  #=> true


hash_a = {:cat => 'Bubby', :dog => 'Gizmo'}  
hash_b = {:cat => 'Bubby', :dog => 'Gizmo', :lion => 'Simba'}  
p eql(hash_a, hash_b) #=> true

hash_a = {:cat => 'Bubby', :dog => 'Gizmo', :fish => "Wanda"}  
hash_b = {:cat => 'Bubby', :dog => 'Gizmo', :lion => 'Simba'}  
p eql(hash_a, hash_b) #=> false

Not too long, and seems to work like you want it too :)

ormuriauga
A: 

If you convert the hashes to an array, you can compare them like this.

@hash_a.to_a == (@hash_a.to_a & @hash_b.to_a)

You could also hide this code behind a method in the hash class if you wanted:

class Hash
  def diff_equal(other)
    to_a == (to_a & other.to_a)
  end
end

Then use it like @hash_a.diff_equal(@hash_b). If you chose that path, you might want to check that other is a hash or responds to the to_a method.

Beerlington