Ori Marko
Ori Marko

Reputation: 58772

Java Differences between maps including values

I need to find only differences between 2 maps, while the different can be by missing key or by different value for key.

I find a general answer for Differences between maps

sources.removeAll(targets) ... leaves only entries in sources that are only in sources, not in target

whereas

sources.retainAll(targets) ... leaves only entries that are in both sets

But I'm not sure it's better than the following code, because I need to check apart from key existence also that the values are different

    Map<K, V> updatedMap = new EnumMap<>(K.class);
    for (Map.Entry<K, V> finalSet : secondMap.entrySet()) {
        K currentKey = finalSet.getKey();
        if (!firstMap.containsKey(currentKey) || firstMap.get(currentKey) != finalSet.getValue()) {
            updatedMap.put(currentKey, finalSet.getValue());
            firstMap.remove(currentKey);
        }
    }
    for (Map.Entry<K, V> currentSet : firstMap.entrySet()) {
        K currentKey = currentSet.getKey();
        if (!secondMap.containsKey(currentKey)) {
            updatedMap.put(currentKey, currentSet.getValue());
        } else if (secondMap.get(currentKey) != currentSet.getValue()) {
            updatedMap.put(currentKey, secondMap.get(currentKey));
        }
    }

Is their a better way of finding the differences between maps including values?

Upvotes: 4

Views: 1502

Answers (2)

Olivier Gr&#233;goire
Olivier Gr&#233;goire

Reputation: 35427

Use Guava's Maps.difference(Map, Map).

Their wiki explains how it works, but you can find below the solution to your problem.

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
Map<String, Integer> right = ImmutableMap.of("b", 2, "c", 4, "d", 5);
MapDifference<String, Integer> diff = Maps.difference(left, right);

Map<String, Integer> output = new HashMap<>();
output.putAll(diff.entriesOnlyOnLeft());
output.putAll(diff.entriesOnlyOnRight());
for (Map.Entry<String,MapDifference.ValueDifference<Integer>> e: diff.entriesDiffering().entrySet()) {
  // Java 10 and later : for (var e: diff.entriesDiffering().entrySet())
  output.put(e.getKey(), e.getValue().rightValue());
}
System.out.println(output); // {a=1, c=4, d=5}

Upvotes: 1

Eugene
Eugene

Reputation: 120858

Well, you could compare Entrys from a Map, because that class overrides equals/hashCode in the manner that you want. It's not entirely clear which entries you would want to keep, the ones from the left map or the right map or any of them.

For example this could be done via :

Map<Integer, String> allDifs = 
             Sets.symmetricDifference(left.entrySet(), right.entrySet())
                 .stream()
                 .collect(Collectors.toMap(Entry::getKey, Entry::getValue));

On the other hand if you just want to keep entries form the second (right) Map:

 Map<Integer, String> result = 
             Sets.difference(right.entrySet(), left.entrySet())
                 .stream()
                 .collect(Collectors.toMap(Entry::getKey, Entry::getValue));

    System.out.println(result); 

Obviously you need guava and java-8 for this...

EDIT

what you actually want is not achievable with Collectors.toMap, but you can do it with:

    Map<Integer, String> result = new HashMap<>();
    Sets.symmetricDifference(right.entrySet(), left.entrySet())
            .stream()
            .forEachOrdered(x -> {
                String previousValue = result.putIfAbsent(x.getKey(), x.getValue());
                if (previousValue != null) {
                    result.replace(x.getKey(), right.get(x.getKey()));
                }
            });

Upvotes: 3

Related Questions