Skip to content

Commit af4a151

Browse files
committed
GROOVY-11903: ensure minus doesn't modify inputs and use fewer iterators
1 parent e3bd260 commit af4a151

1 file changed

Lines changed: 43 additions & 40 deletions

File tree

src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10794,9 +10794,13 @@ public static <T> SortedSet<T> minus(SortedSet<T> self, Object removeMe) {
1079410794
}
1079510795

1079610796
/**
10797-
* Create a List composed of the elements of the first list minus
10797+
* Create a List composed of the elements of the given List minus
1079810798
* every occurrence of elements of the given Collection.
10799-
* <pre class="groovyTestCase">assert [1, "a", true, true, false, 5.3] - [true, 5.3] == [1, "a", false]</pre>
10799+
* <pre class="groovyTestCase">
10800+
* def one = [1, "a", true, true, false, 5.3, null], two = [null, true, 5.3]
10801+
* def sub = one.asUnmodifiable() - two.asUnmodifiable()
10802+
* assert sub == [1, "a", false]
10803+
* </pre>
1080010804
*
1080110805
* @param self a List
1080210806
* @param removeMe the elements to exclude
@@ -10875,7 +10879,9 @@ public static <T> Collection<T> minus(Iterable<T> self, Iterable<?> removeMe, @C
1087510879
* Create a new Collection composed of the elements of the first Iterable minus
1087610880
* every matching occurrence as determined by the condition comparator of elements of the given Iterable.
1087710881
* <pre class="groovyTestCase">
10878-
* assert ['a', 'B', 'c', 'D', 'E'].minus(['b', 'C', 'D'], {@code (i, j) -> i.toLowerCase() <=> j.toLowerCase()}) == ['a', 'E']
10882+
* List&lt;String> one = ['a', 'B', 'c', 'D', 'E'], two = ['b', 'C', 'D']
10883+
* def sub = one.minus(two, Comparator.comparing(String::toLowerCase))
10884+
* assert sub == ['a', 'E']
1087910885
* </pre>
1088010886
*
1088110887
* @param self an Iterable
@@ -10884,48 +10890,45 @@ public static <T> Collection<T> minus(Iterable<T> self, Iterable<?> removeMe, @C
1088410890
* @return a new Collection
1088510891
* @since 4.0.0
1088610892
*/
10887-
@SuppressWarnings("unchecked")
1088810893
public static <T> Collection<T> minus(Iterable<T> self, Iterable<?> removeMe, Comparator<? super T> comparator) {
10889-
Collection<T> ansCollection = createSimilarCollection(self);
10890-
if (!self.iterator().hasNext())
10891-
return ansCollection;
10892-
T head = self.iterator().next();
10893-
10894-
// We can't use the same tactic as for intersection
10895-
// since AbstractCollection only does a remove on the first
10896-
// element it encounters.
10897-
boolean nlgnSort = sameType(new Iterable[]{self, removeMe});
10898-
10899-
if (nlgnSort && (head instanceof Comparable)) {
10900-
//n*LOG(n) version
10901-
Set<T> removeMe2 = new TreeSet<>(comparator);
10902-
for(Object o: removeMe) {
10903-
removeMe2.add((T) o);
10904-
}
10905-
for (T o : self) {
10906-
if (!removeMe2.contains(o))
10907-
ansCollection.add(o);
10908-
}
10909-
} else {
10910-
//n*n version
10911-
Collection<T> tmpAnswer = asCollection(self);
10912-
for (Iterator<T> iter = self.iterator(); iter.hasNext();) {
10913-
T element = iter.next();
10914-
boolean elementRemoved = false;
10915-
for (Iterator<?> iterator = removeMe.iterator(); iterator.hasNext() && !elementRemoved;) {
10916-
Object elt = iterator.next();
10917-
if (DefaultTypeTransformation.compareEqual(element, elt)) {
10918-
iter.remove();
10919-
elementRemoved = true;
10920-
}
10894+
Collection<T> answer = createSimilarCollection(self);
10895+
Iterator<T> iterator = self.iterator();
10896+
if (iterator.hasNext()) {
10897+
T next = iterator.next();
10898+
boolean more = iterator.hasNext();
10899+
Predicate exclude; // the elements of self are discarded if this returns true
10900+
10901+
// We can't use the same tactic as for intersection, since AbstractCollection
10902+
// only does a remove on the first element it encounters.
10903+
if (next instanceof Comparable && sameType(new Iterable[]{self, removeMe})) {
10904+
// O(log(n)) version
10905+
Set removeMe2 = new TreeSet<>(comparator);
10906+
for (Object o : removeMe) {
10907+
removeMe2.add(o);
1092110908
}
10909+
exclude = removeMe2::contains;
10910+
} else {
10911+
// O(n) version
10912+
exclude = (o1) -> {
10913+
for (Object o2 : removeMe) {
10914+
if (DefaultTypeTransformation.compareEqual(o1, o2)) {
10915+
return true;
10916+
}
10917+
}
10918+
return false;
10919+
};
1092210920
}
1092310921

10924-
//remove duplicates
10925-
//can't use treeset since the base classes are different
10926-
ansCollection.addAll(tmpAnswer);
10922+
while (true) {
10923+
if (!exclude.test(next))
10924+
answer.add(next); // include duplicates unless answer dedups
10925+
if (!more) break; else {
10926+
next = iterator.next();
10927+
more = iterator.hasNext();
10928+
}
10929+
}
1092710930
}
10928-
return ansCollection;
10931+
return answer;
1092910932
}
1093010933

1093110934
/**

0 commit comments

Comments
 (0)