views:

480

answers:

7

How to compare two maps by their values? I have two maps containing equal values and want to compare them by their values. Here is an example:

    Map a = new HashMap();
    a.put("foo", "bar"+"bar");
    a.put("zoo", "bar"+"bar");

    Map b = new HashMap();
    b.put(new String("foo"), "bar"+"bar");
    b.put(new String("zoo"), "bar"+"bar");

    System.out.println("equals: " + a.equals(b));            // obviously false

    .... what to call to obtain a true?

[[ EDIT: someone please edit and fix this question to mean whatever it's actually supposed to mean. The code above prints "true", not "false". ]]

Obviously, to implement a comparison it not difficult, it is enough to compare all keys and their associated values. I don't believe I'm the first one to do this, so there must be already a library functions either in java or in one of the jakarta.commons libraries.

Thanks

+3  A: 

To see if two maps have the same values, you can do the following:

  • Get their Collection<V> values() views
  • Wrap into List<V>
  • Collections.sort those lists
  • Test if the two lists are equals

Something like this works (though its type bounds can be improved on):

static <V extends Comparable<V>>
boolean valuesEquals(Map<?,V> map1, Map<?,V> map2) {
    List<V> values1 = new ArrayList<V>(map1.values());
    List<V> values2 = new ArrayList<V>(map2.values());
    Collections.sort(values1);
    Collections.sort(values2);
    return values1.equals(values2);
}

Test harness:

Map<String, String> map1 = new HashMap<String,String>();
map1.put("A", "B");
map1.put("C", "D");

Map<String, String> map2 = new HashMap<String,String>();
map2.put("A", "D");
map2.put("C", "B");

System.out.println(valuesEquals(map1, map2)); // prints "true"

This is O(N log N) due to Collections.sort.

See also:


To test if the keys are equals is easier, because they're Set<K>:

map1.keySet().equals(map2.keySet())

See also:

polygenelubricants
That won't help. If `map1` had (A=>B,C=>D), and `map2` had (A=>D,C=>B) then they have the same values and keys (and might even enumerate them in the same order; that's not guaranteed!) but are still demonstrably different.
Donal Fellows
@Donald: added the sorted list version and your test harness; it should work now.
polygenelubricants
+1  A: 

Your attempts to construct different strings using concatenation will fail as it's being performed at compile-time. Both of those maps have a single pair; each pair will have "foo" and "barbar" as the key/value, both using the same string reference.

Assuming you really want to compare the sets of values without any reference to keys, it's just a case of:

Set<String> values1 = new hashSet<String>(map1.values());
Set<String> values2 = new HashSet<String>(map2.values());
boolean equal = values1.equals(value2);

It's possible that comparing map1.values() with map2.values() would work - but it's also possible that the order in which they're returned would be used in the equality comparison, which isn't what you want.

Note that using a set has its own problems - because the above code would deem a map of {"a":"0", "b":"0"} and {"c":"0"} to be equal... the value sets are equal, after all.

If you could provide a stricter definition of what you want, it'll be easier to make sure we give you the right answer.

Jon Skeet
A: 

The result of equals in your example is obviously false because you are comparing the map a with some values in it with an empty map b (probably a copy and paste error). I recommend to use proper variable names (so you can avoid these kinds of errors) and make use of generics, too.

    Map<String, String> first = new HashMap<String, String>();
    first.put("f"+"oo", "bar"+"bar");
    first.put("fo"+"o", "bar"+"bar");

    Map second = new HashMap();
    second.put("f"+"oo", "bar"+"bar");
    second.put("fo"+"o", "bar"+"bar");

    System.out.println("equals: " + first.equals(second));

The concatenation of your strings doesn't have any effect because it will be done at compile time.

Daff
A: 

If you assume that there can be duplicate values the only way to do this is to put the values in lists, sort them and compare the lists viz:

List<String> values1 = new ArrayList<String>(map1.values());
List<String> values2 = new ArrayList<String>(map2.values());
Collections.sort(values1);
Collections.sort(values2);
boolean mapsHaveEqualValues = values1.equals(values2);

If values cannot contain duplicate values then you can either do the above without the sort using sets.

Dean Povey
We all wish that `Collections.sort` is "fluid" (I think that's the term), but no, it returns `void`.
polygenelubricants
Err yes, my mistake. Edited it to fix that problem.
Dean Povey
+2  A: 

The correct way to compare maps for value-equality is to:

  1. Check that the maps are the same size(!)
  2. Get the set of keys from one map
  3. For each key from that set you retrieved, check that the value retrieved from each map for that key is the same (if the key is absent from one map, that's a total failure of equality)

In other words (minus error handling):

boolean equalMaps(Map<K,V>m1, Map<K,V>m2) {
   if (m1.size() != m2.size())
      return false;
   foreach (K key: m1.keys())
      if (!m1.get(key).equals(m2.get(key)))
         return false;
   return true;
}
Donal Fellows
How is this different than `Map.equals`?
polygenelubricants
For value-equality, that's too strict a test. It not only tests for value-equality, but also for key-value map equality.
CPerkins
@CPerkins: I guess I just don't see the point in only comparing values. It's missing at least one whole herd of elephants in the room; a differing map just *isn't* the same. @poly: Hmm, it isn't different. I suppose that's why that's what the equality operation is defined to be. :-) I think the original problem was caused by the questioner just updating the wrong map or something equally basic.
Donal Fellows
@Donal - I agree with you about the point, but the OP seemed to be asking about value equality, not key-value map equality. Otherwise there's no point in the question.
CPerkins
@CPerkins: Check the history of the question. In particular, in the original one the two maps were different because of a silly error and I'm beginning to suspect that the *real* answer to the question was to point out that error. +100 Meh.
Donal Fellows
A: 

I don't think there is a "apache-common-like" tool to compare maps since the equality of 2 maps is very ambiguous and depends on the developer needs and the map implementation...

For exemple if you compare two hashmaps in java: - You may want to just compare key/values are the same - You may also want to compare if the keys are ordered the same way - You may also want to compare if the remaining capacity is the same ... You can compare a lot of things!

What such a tool would do when comparing 2 different map implementations such that: - One map allow null keys - The other throw runtime exception on map2.get(null)

You'd better to implement your own solution according to what you really need to do, and i think you already got some answers above :)

Sebastien Lorber
A: 

Since you asked about ready-made Api's ... well Apache's commons. collections library has a CollectionUtils class that provides easy-to-use methods for Collection manipulation/checking, such as intersection, difference, and union.

Narayan