You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2021/10/01 22:18:03 UTC

[groovy] branch master updated: GROOVY-10275: Align intersect which takes a closure/comparator ordering to be the same as other languages.

This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 903366a  GROOVY-10275: Align intersect which takes a closure/comparator ordering to be the same as other languages.
903366a is described below

commit 903366a781796910047add22a70f1bd888a3a4a8
Author: Paul King <pa...@asert.com.au>
AuthorDate: Thu Sep 30 19:24:44 2021 +1000

    GROOVY-10275: Align intersect which takes a closure/comparator ordering to be the same as other languages.
---
 .../groovy/runtime/DefaultGroovyMethods.java       | 75 ++++++++++++++++++----
 src/test/groovy/GroovyMethodsTest.groovy           |  3 +-
 src/test/groovy/ListTest.groovy                    |  8 ++-
 3 files changed, 68 insertions(+), 18 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index 69f80f0..a6bc8f4 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -12651,6 +12651,7 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
      * For collections of custom objects; the objects should implement java.lang.Comparable
      * <pre class="groovyTestCase">
      * assert [3,4] == [1,2,3,4].intersect([3,4,5,6], Comparator.naturalOrder())
+     * assert [2,4] == [1,2,3,4].intersect([4,8,12,16,20], (x, y) {@code -> x * x <=> y})
      * </pre>
      * <pre class="groovyTestCase">
      * def one = ['a', 'B', 'c', 'd']
@@ -12658,8 +12659,8 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
      * def compareIgnoreCase = { a, b {@code ->} a.toLowerCase() {@code <=>} b.toLowerCase() }
      * assert one.intersect(two) == ['d']
      * assert two.intersect(one) == ['d']
-     * assert one.intersect(two, compareIgnoreCase) == ['b', 'C', 'd']
-     * assert two.intersect(one, compareIgnoreCase) == ['B', 'c', 'd']
+     * assert one.intersect(two, compareIgnoreCase) == ['B', 'c', 'd']
+     * assert two.intersect(one, compareIgnoreCase) == ['b', 'C', 'd']
      * </pre>
      *
      * @param left  a Collection
@@ -12674,11 +12675,11 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
 
         Collection<T> result = createSimilarCollection(left, Math.min(left.size(), right.size()));
         //creates the collection to look for values.
-        Collection<T> pickFrom = new TreeSet<>(comparator);
-        pickFrom.addAll(left);
+        Collection<T> compareWith = new TreeSet<>(comparator);
+        compareWith.addAll(right);
 
-        for (final T t : right) {
-            if (pickFrom.contains(t))
+        for (final T t : left) {
+            if (compareWith.contains(t))
                 result.add(t);
         }
         return result;
@@ -12688,7 +12689,9 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
      * Create a Collection composed of the intersection of both iterables.  Any
      * elements that exist in both iterables are added to the resultant collection.
      * For iterables of custom objects; the objects should implement java.lang.Comparable
-     * <pre class="groovyTestCase">assert [4,5] == [1,2,3,4,5].intersect([4,5,6,7,8])</pre>
+     * <pre class="groovyTestCase">
+     * assert [4,5] == [1,2,3,4,5].intersect([4,5,6,7,8])
+     * </pre>
      * By default, Groovy uses a {@link NumberAwareComparator} when determining if an
      * element exists in both collections.
      *
@@ -12706,7 +12709,9 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
      * Create a Collection composed of the intersection of both iterables.  Any
      * elements that exist in both iterables are added to the resultant collection.
      * For iterables of custom objects; the objects should implement java.lang.Comparable
-     * <pre class="groovyTestCase">assert [3,4] == [1,2,3,4].intersect([3,4,5,6], Comparator.naturalOrder())</pre>
+     * <pre class="groovyTestCase">
+     * assert [3,4] == [1,2,3,4].intersect([3,4,5,6], Comparator.naturalOrder())
+     * </pre>
      *
      * @param left  an Iterable
      * @param right an Iterable
@@ -12719,9 +12724,41 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
     }
 
     /**
+     * Create a Collection composed of the intersection of both iterables.
+     * Elements from teh first iterable which also occur (according to the comparator closure) in the second iterable are added to the result.
+     * If the closure takes a single parameter, the argument passed will be each element,
+     * and the closure should return a value used for comparison (either using
+     * {@link java.lang.Comparable#compareTo(java.lang.Object)} or {@link java.lang.Object#equals(java.lang.Object)}).
+     * If the closure takes two parameters, two items from the Iterator
+     * will be passed as arguments, and the closure should return an
+     * int value (with 0 indicating the items are deemed equal).
+     * <pre class="groovyTestCase">
+     * def one = ['a', 'B', 'c', 'd']
+     * def two = ['b', 'C', 'd', 'e']
+     * def compareIgnoreCase = { it.toLowerCase() }
+     * assert one.intersect(two, compareIgnoreCase) == ['B', 'c', 'd']
+     * assert two.intersect(one, compareIgnoreCase) == ['b', 'C', 'd']
+     * </pre>
+     *
+     * @param left  an Iterable
+     * @param right an Iterable
+     * @param condition a Closure used to determine unique items
+     * @return a Collection as an intersection of both iterables
+     * @since 4.0.0
+     */
+    public static <T> Collection<T> intersect(Iterable<T> left, Iterable<T> right, @ClosureParams(value=FromString.class, options={"T","T,T"}) Closure condition) {
+        Comparator<T> comparator = condition.getMaximumNumberOfParameters() == 1
+                ? new OrderBy<>(condition, true)
+                : new ClosureComparator<>(condition);
+        return intersect(left, right, comparator);
+    }
+
+    /**
      * Create a List composed of the intersection of a List and an Iterable.  Any
      * elements that exist in both iterables are added to the resultant collection.
-     * <pre class="groovyTestCase">assert [4,5] == [1,2,3,4,5].intersect([4,5,6,7,8])</pre>
+     * <pre class="groovyTestCase">
+     * assert [4,5] == [1,2,3,4,5].intersect([4,5,6,7,8])
+     * </pre>
      * By default, Groovy uses a {@link NumberAwareComparator} when determining if an
      * element exists in both collections.
      *
@@ -12738,7 +12775,9 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
     /**
      * Create a List composed of the intersection of a List and an Iterable.  Any
      * elements that exist in both iterables are added to the resultant collection.
-     * <pre class="groovyTestCase">assert [3,4] == [1,2,3,4].intersect([3,4,5,6])</pre>
+     * <pre class="groovyTestCase">
+     * assert [3,4] == [1,2,3,4].intersect([3,4,5,6])
+     * </pre>
      *
      * @param left  a List
      * @param right an Iterable
@@ -12753,7 +12792,9 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
     /**
      * Create a Set composed of the intersection of a Set and an Iterable.  Any
      * elements that exist in both iterables are added to the resultant collection.
-     * <pre class="groovyTestCase">assert [4,5] as Set == ([1,2,3,4,5] as Set).intersect([4,5,6,7,8])</pre>
+     * <pre class="groovyTestCase">
+     * assert [4,5] as Set == ([1,2,3,4,5] as Set).intersect([4,5,6,7,8])
+     * </pre>
      * By default, Groovy uses a {@link NumberAwareComparator} when determining if an
      * element exists in both collections.
      *
@@ -12770,7 +12811,9 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
     /**
      * Create a Set composed of the intersection of a Set and an Iterable.  Any
      * elements that exist in both iterables are added to the resultant collection.
-     * <pre class="groovyTestCase">assert [3,4] as Set == ([1,2,3,4] as Set).intersect([3,4,5,6], Comparator.naturalOrder())</pre>
+     * <pre class="groovyTestCase">
+     * assert [3,4] as Set == ([1,2,3,4] as Set).intersect([3,4,5,6], Comparator.naturalOrder())
+     * </pre>
      *
      * @param left  a Set
      * @param right an Iterable
@@ -12785,7 +12828,9 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
     /**
      * Create a SortedSet composed of the intersection of a SortedSet and an Iterable.  Any
      * elements that exist in both iterables are added to the resultant collection.
-     * <pre class="groovyTestCase">assert [4,5] as SortedSet == ([1,2,3,4,5] as SortedSet).intersect([4,5,6,7,8])</pre>
+     * <pre class="groovyTestCase">
+     * assert [4,5] as SortedSet == ([1,2,3,4,5] as SortedSet).intersect([4,5,6,7,8])
+     * </pre>
      * By default, Groovy uses a {@link NumberAwareComparator} when determining if an
      * element exists in both collections.
      *
@@ -12802,7 +12847,9 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
     /**
      * Create a SortedSet composed of the intersection of a SortedSet and an Iterable.  Any
      * elements that exist in both iterables are added to the resultant collection.
-     * <pre class="groovyTestCase">assert [4,5] as SortedSet == ([1,2,3,4,5] as SortedSet).intersect([4,5,6,7,8])</pre>
+     * <pre class="groovyTestCase">
+     * assert [4,5] as SortedSet == ([1,2,3,4,5] as SortedSet).intersect([4,5,6,7,8])
+     * </pre>
      *
      * @param left  a SortedSet
      * @param right an Iterable
diff --git a/src/test/groovy/GroovyMethodsTest.groovy b/src/test/groovy/GroovyMethodsTest.groovy
index 1098a45..91803d7 100644
--- a/src/test/groovy/GroovyMethodsTest.groovy
+++ b/src/test/groovy/GroovyMethodsTest.groovy
@@ -1597,7 +1597,8 @@ class GroovyMethodsTest extends GroovyTestCase {
         def list1 = [[language: 'Java'], [language: 'Groovy'], [language: 'Scala']]
         def list2 = [[language: 'Groovy'], [language: 'JRuby'], [language: 'Java']]
         def intersection = list1.intersect(list2)
-        assert intersection == [[language: 'Groovy'], [language: 'Java']]
+        assert intersection == [[language: 'Java'], [language: 'Groovy']]
+        assert list2.intersect(list1) == [[language: 'Groovy'], [language: 'Java']]
 
         def locs1 = [[loc: [0, 1]], [loc: [1, 1]]]
         def locs2 = [[loc: [2, 1]], [loc: [1, 1]]]
diff --git a/src/test/groovy/ListTest.groovy b/src/test/groovy/ListTest.groovy
index 8a92f54..8e892a0 100644
--- a/src/test/groovy/ListTest.groovy
+++ b/src/test/groovy/ListTest.groovy
@@ -170,12 +170,14 @@ class ListTest extends GroovyTestCase {
     void testIntersect() {
         def l1 = [1, 1, "wrer", 2, 3, 3, "wrewer", 4, 5, "w", "w"]
         def l2 = [1, 2, "f", "w"]
-        assert l1.intersect(l2) == [1, 2, "w"]
+        assert l1.intersect(l2) == [1, 1, 2, 'w', 'w']
+        assert l2.intersect(l1) == [1, 2, "w"]
 
-        // GROOVY-1006    
+        // GROOVY-1006
         l1 = [1, 1.0, "wrer", 2, 3, 3L, "wrewer", 4, 5, "w", "w"]
         l2 = [(double) 1, 2L, "f", "w"]
-        assert l1.intersect(l2) == [1, 2, "w"]
+        assert l1.intersect(l2) == [1, 1.0, 2, 'w', 'w']
+        assert l2.intersect(l1) == [1, 2, "w"]
     }
 
     // GROOVY-1006