Repository: yegor256/cactoos
Pull request #1784 (2025-03-17 19:04:17)

Implement MapDiff class for map difference operations
This PR introduces a new `MapDiff` class that allows computing the difference between two maps. The implementation follows the functional programming principles of Cactoos.
Features
Computes the difference between two maps (entries in the first map that are not in the second map)
Supports various input types:
Maps
Iterables of `Map.Entry`
Iterators of `Map.Entry`
Scalar suppliers of `Iterables`
Implementation Details
`MapDiff` extends `MapEnvelope` to maintain the immutability principle
The difference is computed by removing all keys from the first map that exist in the second map
Comprehensive test coverage ensures correct behavior with different input types and edge cases
Testing
The implementation includes tests for:
Basic map difference computation
Empty map handling (both as first and second argument)
Working with standard Java Map implementations
Working with `Iterables` and `Iterators`
This implementation will be useful for functional operations on maps where finding differences between collections is needed.



Patch:
diff --git a/pom.xml b/pom.xml
index c7b53b8d..559f0cd3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -115,7 +115,7 @@
       <plugin>
         <groupId>org.pitest</groupId>
         <artifactId>pitest-maven</artifactId>
-        <version>1.19.0</version>
+        <version>1.18.2</version>
         <executions>
           <execution>
             <goals>
diff --git a/src/main/java/org/cactoos/map/MapDiff.java b/src/main/java/org/cactoos/map/MapDiff.java
new file mode 100644
index 00000000..807d0f8d
--- /dev/null
+++ b/src/main/java/org/cactoos/map/MapDiff.java
@@ -0,0 +1,109 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2017-2025 Yegor Bugayenko
+ * SPDX-License-Identifier: MIT
+ */
+package org.cactoos.map;
+
+import java.util.Iterator;
+import java.util.Map;
+import org.cactoos.Scalar;
+import org.cactoos.iterable.IterableOf;
+import org.cactoos.scalar.Unchecked;
+
+/**
+ * Map difference.
+ *
+ * <p>There is no thread-safety guarantee.</p>
+ *
+ * @param <K> Type of key
+ * @param <V> Type of value
+ * @since 0.58.0
+ */
+public final class MapDiff<K, V> extends MapEnvelope<K, V> {
+
+    /**
+     * Ctor.
+     * @param first First map
+     * @param second Second map
+     */
+    public MapDiff(final Iterable<Map.Entry<K, V>> first,
+        final Iterable<Map.Entry<K, V>> second) {
+        super(
+            computeDiff(
+                mapFromEntries(first),
+                mapFromEntries(second)
+            )
+        );
+    }
+
+    /**
+     * Ctor.
+     * @param first First iterator
+     * @param second Second iterator
+     */
+    public MapDiff(final Iterator<Map.Entry<K, V>> first,
+        final Iterator<Map.Entry<K, V>> second) {
+        this(
+            new IterableOf<>(first),
+            new IterableOf<>(second)
+        );
+    }
+
+    /**
+     * Ctor.
+     * @param first First iterable supplier
+     * @param second Second iterable supplier
+     */
+    public MapDiff(
+        final Scalar<Iterable<Map.Entry<K, V>>> first,
+        final Scalar<Iterable<Map.Entry<K, V>>> second
+    ) {
+        this(
+            new Unchecked<>(first).value(),
+            new Unchecked<>(second).value()
+        );
+    }
+
+    /**
+     * Ctor.
+     * @param first First map
+     * @param second Second map
+     */
+    public MapDiff(final Map<K, V> first, final Map<K, V> second) {
+        super(computeDiff(first, second));
+    }
+
+    /**
+     * Compute the difference between two maps.
+     * @param first The first map
+     * @param second The second map
+     * @param <K> Type of keys
+     * @param <V> Type of values
+     * @return The difference map (entries in first but not in second)
+     */
+    private static <K, V> MapOf<K, V> computeDiff(
+        final Map<K, V> first,
+        final Map<K, V> second
+    ) {
+        final MapOf<K, V> result = new MapOf<>(first);
+        result.keySet().removeAll(second.keySet());
+        return result;
+    }
+
+    /**
+     * Create a map from entries.
+     * @param entries The entries
+     * @param <K> Type of keys
+     * @param <V> Type of values
+     * @return The map
+     */
+    private static <K, V> MapOf<K, V> mapFromEntries(
+        final Iterable<Map.Entry<K, V>> entries
+    ) {
+        final MapOf<K, V> map = new MapOf<>();
+        for (final Map.Entry<K, V> entry : entries) {
+            map.put(entry.getKey(), entry.getValue());
+        }
+        return map;
+    }
+}


Test patch:
diff --git a/src/test/java/org/cactoos/map/MapDiffTest.java b/src/test/java/org/cactoos/map/MapDiffTest.java
new file mode 100644
index 00000000..cbf98a50
--- /dev/null
+++ b/src/test/java/org/cactoos/map/MapDiffTest.java
@@ -0,0 +1,146 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2017-2025 Yegor Bugayenko
+ * SPDX-License-Identifier: MIT
+ */
+package org.cactoos.map;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.hamcrest.core.IsEqual;
+import org.junit.jupiter.api.Test;
+import org.llorllale.cactoos.matchers.Assertion;
+import org.llorllale.cactoos.matchers.HasEntry;
+
+/**
+ * Test case for {@link MapDiff}.
+ *
+ * @since 0.58.0
+ */
+final class MapDiffTest {
+
+    /**
+     * Tests that map difference can be computed correctly.
+     * @since 0.58.0
+     */
+    @Test
+    void computesMapDifference() {
+        new Assertion<>(
+            "Can't compute the difference of two maps",
+            new MapDiff<>(
+                new MapOf<>(
+                    new MapEntry<>(1, "one"),
+                    new MapEntry<>(2, "two"),
+                    new MapEntry<>(3, "three")
+                ),
+                new MapOf<>(
+                    new MapEntry<>(2, "two"),
+                    new MapEntry<>(3, "three"),
+                    new MapEntry<>(4, "four")
+                )
+            ),
+            new HasEntry<>(1, "one")
+        ).affirm();
+    }
+
+    /**
+     * Tests that map difference with empty second map returns the first map.
+     * @since 0.58.0
+     */
+    @Test
+    void computesMapDifferenceWithEmptySecondMap() {
+        new Assertion<>(
+            "Can't compute the difference with empty second map",
+            new MapDiff<>(
+                new MapOf<>(
+                    new MapEntry<>(5, "five"),
+                    new MapEntry<>(6, "six"),
+                    new MapEntry<>(7, "seven")
+                ),
+                new MapOf<>()
+            ),
+            new HasEntry<>(5, "five")
+        ).affirm();
+    }
+
+    /**
+     * Tests that map difference with empty first map returns empty map.
+     * @since 0.58.0
+     */
+    @Test
+    void computesMapDifferenceWithEmptyFirstMap() {
+        new Assertion<>(
+            "Can't compute the difference with empty first map",
+            new MapDiff<Integer, String>(
+                new MapOf<Integer, String>(),
+                new MapOf<>(
+                    new MapEntry<>(8, "eight"),
+                    new MapEntry<>(9, "nine"),
+                    new MapEntry<>(10, "ten")
+                )
+            ).size(),
+            new IsEqual<>(0)
+        ).affirm();
+    }
+
+    /**
+     * Tests that map difference works with java.util.Map.
+     * @since 0.58.0
+     */
+    @Test
+    void computesMapDifferenceWithJavaUtilMaps() {
+        final Map<Integer, String> first = new HashMap<>();
+        first.put(11, "eleven");
+        first.put(12, "twelve");
+        first.put(13, "thirteen");
+        final Map<Integer, String> second = new HashMap<>();
+        second.put(13, "thirteen");
+        second.put(14, "fourteen");
+        second.put(15, "fifteen");
+        new Assertion<>(
+            "Can't compute the difference of two java.util.Map",
+            new MapDiff<>(first, second),
+            new HasEntry<>(11, "eleven")
+        ).affirm();
+    }
+
+    /**
+     * Tests that map difference works with iterables.
+     * @since 0.58.0
+     */
+    @Test
+    void computesMapDifferenceWithIterables() {
+        new Assertion<>(
+            "Can't compute the difference of two iterables",
+            new MapDiff<>(
+                Collections.singletonList(new MapEntry<>(16, "sixteen")),
+                Collections.singletonList(new MapEntry<>(17, "seventeen"))
+            ),
+            new HasEntry<>(16, "sixteen")
+        ).affirm();
+    }
+
+    /**
+     * Tests that map difference works with iterators.
+     * @since 0.58.0
+     */
+    @Test
+    void computesMapDifferenceWithIterators() {
+        new Assertion<>(
+            "Can't compute the difference of two iterators",
+            new MapDiff<>(
+                new MapOf<>(
+                    new MapEntry<>(18, "eighteen"),
+                    new MapEntry<>(19, "nineteen"),
+                    new MapEntry<>(20, "twenty")
+                ).entrySet().iterator(),
+                new MapOf<>(
+                    new MapEntry<>(20, "twenty"),
+                    new MapEntry<>(21, "twenty-one"),
+                    new MapEntry<>(22, "twenty-two")
+                ).entrySet().iterator()
+            ),
+            new HasEntry<>(18, "eighteen")
+        ).affirm();
+    }
+}