diff --git a/docs/asciidoc/utilities/collection.adoc b/docs/asciidoc/utilities/collection.adoc index a915a25398..2ea0c5dfb8 100644 --- a/docs/asciidoc/utilities/collection.adoc +++ b/docs/asciidoc/utilities/collection.adoc @@ -34,6 +34,7 @@ endif::[] | apoc.coll.containsAll(coll, values) | optimized contains-all operation (using a HashSet) returns true or false | apoc.coll.containsSorted(coll, value) | optimized contains on a sorted list operation (Collections.binarySearch) (returns true or false) | apoc.coll.containsAllSorted(coll, value) | optimized contains-all on a sorted list operation (Collections.binarySearch) (returns true or false) +| apoc.coll.isEqualCollection(coll, values) | return true if two collections contain the same elements with the same cardinality in any order (using a HashMap) | apoc.coll.union(first, second) | creates the distinct union of the 2 lists | apoc.coll.unionAll(first, second) | creates the full union with duplicates of the two lists | apoc.coll.subtract(first, second) | returns unique set of first list with all elements of second list removed diff --git a/src/main/java/apoc/coll/Coll.java b/src/main/java/apoc/coll/Coll.java index 586171bd18..26ae203b7d 100644 --- a/src/main/java/apoc/coll/Coll.java +++ b/src/main/java/apoc/coll/Coll.java @@ -1,24 +1,25 @@ package apoc.coll; +import apoc.result.ListResult; import org.apache.commons.math3.util.Combinations; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Path; +import org.neo4j.graphdb.Relationship; import org.neo4j.helpers.collection.Pair; import org.neo4j.kernel.impl.util.statistics.IntCounter; import org.neo4j.procedure.*; -import apoc.result.*; -import org.neo4j.graphdb.GraphDatabaseService; -import org.neo4j.graphdb.Node; -import org.neo4j.graphdb.Relationship; +import java.lang.reflect.Array; import java.util.*; import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; -import java.lang.reflect.Array; import static java.util.Arrays.asList; -import static org.neo4j.helpers.collection.Pair.*; +import static org.neo4j.helpers.collection.Pair.of; public class Coll { @@ -431,6 +432,18 @@ public boolean containsAllSorted(@Name("coll") List coll, @Name("values" return true; } + @UserFunction + @Description("apoc.coll.isEqualCollection(coll, values) return true if two collections contain the same elements with the same cardinality in any order (using a HashMap)") + public boolean isEqualCollection(@Name("coll") List first, @Name("values") List second) { + if (first == null && second == null) return true; + if (first == null || second == null || first.size() != second.size()) return false; + + Map map1 = first.stream() + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + Map map2 = second.stream() + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + return map1.equals(map2); + } @UserFunction @Description("apoc.coll.toSet([list]) returns a unique list backed by a set") diff --git a/src/test/java/apoc/coll/CollTest.java b/src/test/java/apoc/coll/CollTest.java index cc4a1fddec..fe9f3c9e94 100644 --- a/src/test/java/apoc/coll/CollTest.java +++ b/src/test/java/apoc/coll/CollTest.java @@ -2,14 +2,11 @@ import apoc.convert.Json; import apoc.util.TestUtil; -import org.apache.commons.lang.exception.ExceptionUtils; import org.junit.AfterClass; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; -import org.neo4j.graphdb.QueryExecutionException; import org.neo4j.test.TestGraphDatabaseFactory; import java.util.*; @@ -217,10 +214,15 @@ public void testRemove() throws Exception { @Test public void testContainsAll() throws Exception { testCall(db, "RETURN apoc.coll.containsAll([1,2,3],[1,2]) AS value", (res) -> assertEquals(true, res.get("value"))); + testCall(db, "RETURN apoc.coll.containsAll([1,2,3],[2,1]) AS value", (res) -> assertEquals(true, res.get("value"))); testCall(db, "RETURN apoc.coll.containsAll([1,2,3],[1,4]) AS value", (res) -> assertEquals(false, res.get("value"))); testCall(db, "RETURN apoc.coll.containsAll([1,2,3],[]) AS value", (res) -> assertEquals(true, res.get("value"))); testCall(db, "RETURN apoc.coll.containsAll([1,2,3],[1]) AS value", (res) -> assertEquals(true, res.get("value"))); testCall(db, "RETURN apoc.coll.containsAll([1,2,3],[1,2,3,4]) AS value", (res) -> assertEquals(false, res.get("value"))); + testCall(db, "RETURN apoc.coll.containsAll([1,1,2,3],[1,2,2,3]) AS value", (res) -> assertEquals(true, res.get("value"))); + testCall(db, "RETURN apoc.coll.containsAll(null,[1,2,3]) AS value", (res) -> assertEquals(false, res.get("value"))); + testCall(db, "RETURN apoc.coll.containsAll(null,null) AS value", (res) -> assertEquals(true, res.get("value"))); + testCall(db, "RETURN apoc.coll.containsAll([1,2,3],null) AS value", (res) -> assertEquals(false, res.get("value"))); } @Test @@ -232,6 +234,25 @@ public void testContainsAllSorted() throws Exception { testCall(db, "RETURN apoc.coll.containsAllSorted([1,2,3],[1,2,3,4]) AS value", (res) -> assertEquals(false, res.get("value"))); } + @Test + public void testIsEqualCollection() throws Exception { + testCall(db, "RETURN apoc.coll.isEqualCollection([1,2,3],[1,2,3]) AS value", (res) -> assertEquals(true, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection([1,2,3],[3,2,1]) AS value", (res) -> assertEquals(true, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection([1,1,2,2,3],[1,1,2,2,3]) AS value", (res) -> assertEquals(true, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection([1,1,2,3],[1,2,2,3]) AS value", (res) -> assertEquals(false, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection([1,2,3],[1,2]) AS value", (res) -> assertEquals(false, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection([1,2,3],[1,4]) AS value", (res) -> assertEquals(false, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection([1,2,3],[]) AS value", (res) -> assertEquals(false, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection([1,2,3],[1]) AS value", (res) -> assertEquals(false, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection([1,2,3],[1,2,3,4]) AS value", (res) -> assertEquals(false, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection([1,2,3],null) AS value", (res) -> assertEquals(false, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection([1,2,3],[]) AS value", (res) -> assertEquals(false, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection([],null) AS value", (res) -> assertEquals(false, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection([],[]) AS value", (res) -> assertEquals(true, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection(null,null) AS value", (res) -> assertEquals(true, res.get("value"))); + testCall(db, "RETURN apoc.coll.isEqualCollection(null,[]) AS value", (res) -> assertEquals(false, res.get("value"))); + } + @Test public void testIN2() throws Exception { int elements = 1_000_000;