org.conqat.lib.commons.collections.CollectionUtils Maven / Gradle / Ivy
Show all versions of teamscale-lib-commons Show documentation
/*
* Copyright (c) CQSE GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.conqat.lib.commons.collections;
import java.lang.reflect.Array;
import java.util.AbstractSet;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.RandomAccess;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.function.BiConsumerWithException;
import org.conqat.lib.commons.function.ConsumerWithException;
import org.conqat.lib.commons.function.FunctionWithException;
import org.conqat.lib.commons.function.PredicateWithException;
import org.conqat.lib.commons.string.StringUtils;
import org.jetbrains.annotations.Contract;
import com.google.common.collect.Sets;
/**
* This class offers utility methods for collections. In can be seen as an extension to
* {@link Collections}.
*/
public class CollectionUtils {
/** Empty unmodifiable list. */
private static final UnmodifiableList EMPTY_LIST = new UnmodifiableList<>(Collections.emptyList());
/** Empty unmodifiable map. */
private static final UnmodifiableMap EMPTY_MAP = new UnmodifiableMap<>(Collections.emptyMap());
/** Empty unmodifiable set. */
private static final UnmodifiableSet EMPTY_SET = new UnmodifiableSet<>(Collections.emptySet());
/** Delimiter used in strings to separate list values. */
public static final Character MULTI_VALUE_DELIMITER = ',';
/**
* Prefix used to indicate the start of a line comment inside a multi-value connector configuration
* text field.
*/
private static final String CONNECTOR_CONFIG_MULTI_VALUE_INPUT_LINE_COMMENT_PREFIX = "##";
/**
* Create a hashed set from an array.
*
* @param
* type
* @param elements
* elements in the set.
* @return the set.
* @see Arrays#asList(Object[])
*/
@SafeVarargs
public static HashSet asHashSet(T... elements) {
HashSet result = new HashSet<>(elements.length);
Collections.addAll(result, elements);
return result;
}
/** Creates a new unmodifiable {@link HashMap} based on given pairs. */
@SafeVarargs
public static UnmodifiableMap asMap(Pair... pairs) {
return asMap(new HashMap<>(), pairs);
}
/**
* Extends the given map with the pairs and returns an unmodifiable version.
*/
@SafeVarargs
public static UnmodifiableMap asMap(Map baseMap, Pair... pairs) {
for (Pair pair : pairs) {
baseMap.put(pair.getFirst(), pair.getSecond());
}
return asUnmodifiable(baseMap);
}
/** Creates an unmodifiable hash set from an array. */
@SafeVarargs
public static UnmodifiableSet asUnmodifiableHashSet(T... elements) {
return new UnmodifiableSet<>(CollectionUtils.asHashSet(elements));
}
/**
* Returns a new unmodifiable collection wrapping the given collection. This method is here to avoid
* retyping the generic argument for the collection class.
*/
public static UnmodifiableCollection asUnmodifiable(Collection c) {
return new UnmodifiableCollection<>(c);
}
/**
* Returns a new unmodifiable list wrapping the given list. This method is here to avoid retyping
* the generic argument for the list class.
*/
public static UnmodifiableList asUnmodifiable(List l) {
return new UnmodifiableList<>(l);
}
/**
* Returns a new unmodifiable map wrapping the given map. This method is here to avoid retyping the
* generic arguments for the map class.
*/
public static UnmodifiableMap asUnmodifiable(Map m) {
return new UnmodifiableMap<>(m);
}
/**
* Returns a new unmodifiable set wrapping the given set. This method is here to avoid retyping the
* generic argument for the set class.
*/
public static UnmodifiableSet asUnmodifiable(Set s) {
return new UnmodifiableSet<>(s);
}
/**
* Returns a new unmodifiable sorted map wrapping the given sorted map. This method is here to avoid
* retyping the generic arguments for the sorted map class.
*/
public static UnmodifiableSortedMap asUnmodifiable(SortedMap m) {
return new UnmodifiableSortedMap<>(m);
}
/**
* Returns a new unmodifiable sorted set wrapping the given sorted set. This method is here to avoid
* retyping the generic argument for the sorted set class.
*/
public static UnmodifiableSortedSet asUnmodifiable(SortedSet s) {
return new UnmodifiableSortedSet<>(s);
}
/**
* The way this method is defined allows to assign it to all parameterized types, i.e. both of the
* following statements are accepted by the compiler without warnings:
*
*
*
* UnmodifiableList<String> emptyList = CollectionUtils.emptyList();
*
* UnmodifiableList<Date> emptyList = CollectionUtils.emptyList();
*
*
* @return the empty list
*/
@SuppressWarnings("unchecked")
public static UnmodifiableList emptyList() {
return (UnmodifiableList) EMPTY_LIST;
}
/** Returns an empty map. See {@link #emptyList()} for further details. */
@SuppressWarnings("unchecked")
public static UnmodifiableMap emptyMap() {
return (UnmodifiableMap) EMPTY_MAP;
}
/** Returns an empty set. See {@link #emptyList()} for further details. */
@SuppressWarnings("unchecked")
public static UnmodifiableSet emptySet() {
return (UnmodifiableSet) EMPTY_SET;
}
/**
* Sorts the specified list into ascending order, according to the natural ordering of its
* elements.
*
* All elements in the list must implement the Comparable interface. Furthermore, all elements in
* the list must be mutually comparable (that is, e1.compareTo(e2) must not throw a
* ClassCastException
for any elements e1 and e2 in the list).
*
* This method does not modify the original collection.
*/
public static > ArrayList sort(Collection collection) {
ArrayList list = new ArrayList<>(collection);
Collections.sort(list);
return list;
}
/**
* Returns a list that contains the elements of the specified list in reversed order.
*
* This method does not modify the original collection.
*/
public static ArrayList reverse(Collection list) {
ArrayList reversed = new ArrayList<>(list);
Collections.reverse(reversed);
return reversed;
}
/**
* Returns all values, which occur multiple times in the list.
*
* It doesn't tell you how often it occurs, just that it is more than once.
*/
public static Set getDuplicates(List values) {
Set duplicates = new HashSet<>();
Set temp = new HashSet<>();
for (T key : values) {
if (!temp.add(key)) {
duplicates.add(key);
}
}
return duplicates;
}
/**
* Applies the mapper {@link Function} to all items in the collection and returns the resulting
* {@link List}.
*
* This method does not modify the original collection.
*/
public static List map(Collection list, Function mapper) {
List result = new ArrayList<>(list.size());
for (T entry : list) {
result.add(mapper.apply(entry));
}
return result;
}
/**
* Applies the mapper {@link Function} to all items in the collection and returns the resulting
* {@link Set} only containing distinct mapped values.
*
* This method does not modify the original collection.
*/
public static Set mapToSet(Collection list, Function mapper) {
Set result = new HashSet<>();
for (T entry : list) {
result.add(mapper.apply(entry));
}
return result;
}
/**
* Applies the key and value mapper {@link Function}s to all keys/values in the collection and
* returns the resulting {@link Map}.
*
* This method does not modify the original collection.
*/
public static Map map(Map map, Function keyMapper,
Function valueMapper) {
Map result = new HashMap<>(map.size());
map.forEach((key, value) -> result.put(keyMapper.apply(key), valueMapper.apply(value)));
return result;
}
/**
* Applies the mapper {@link Function} to all items in the array and returns the resulting
* {@link List}.
*
* This method does not modify the original array.
*/
public static List map(T[] array, Function mapper) {
return map(Arrays.asList(array), mapper);
}
/**
* Applies the mapper {@link Function} to all items in the collection, but only adds the result item
* to the return list if it is not already in the list. Returns the resulting {@link List}.
*
* This method does not modify the original collection.
*/
public static List mapDistinct(Collection list, Function mapper) {
Set encounteredItems = new HashSet<>();
List result = new ArrayList<>();
for (T entry : list) {
R resultItem = mapper.apply(entry);
if (encounteredItems.add(resultItem)) {
result.add(resultItem);
}
}
return result;
}
/**
* Applies the mapper {@link FunctionWithException} to all items in the list and returns the
* resulting {@link List}.
*
* This method does not modify the original collection.
*
* @throws E
* if the mapper function throws this exception for any of the elements of the original
* list.
*/
public static List mapWithException(Collection list,
FunctionWithException mapper) throws E {
List result = new ArrayList<>(list.size());
for (T entry : list) {
result.add(mapper.apply(entry));
}
return result;
}
/**
* Applies the mapper {@link FunctionWithException} to all items in the array and returns the
* resulting {@link List}.
*
* This method does not modify the original array.
*
* @throws E
* if the mapper function throws this exception for any of the elements of the original
* array.
*/
public static List mapWithException(T[] array,
FunctionWithException mapper) throws E {
return mapWithException(Arrays.asList(array), mapper);
}
/** Returns a new List containing only the elements at the given indices. */
public static ArrayList getIndices(List list, List indices) {
ArrayList filteredList = new ArrayList<>();
for (int index : indices) {
filteredList.add(list.get(index));
}
return filteredList;
}
/**
* Returns a new List containing all elements of the given list except for those with the given
* indices.
*/
public static List returnListWithoutGivenIndices(List elements, Set indices) {
List result = new ArrayList<>();
for (int i = 0; i < elements.size(); i++) {
if (!indices.contains(i)) {
result.add(elements.get(i));
}
}
return result;
}
/**
* Returns the indices of null elements in the given list. The returned list is strictly ordered
* (returnedList.get(x) is greater than returnedList.get(x-1)).
*/
public static List getNullIndices(List<@Nullable ?> list) {
List nullIndices = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == null) {
nullIndices.add(i);
}
}
return nullIndices;
}
/**
* Returns whether the given integer is a valid index in the given list (i.e., >=0 and
* list) {
return index >= 0 && index < list.size();
}
/**
* Replacement for {@link AbstractSet#removeAll(Collection)} (without the boolean return value).
* Using this method mitigates a performance bug in the Java API that occurs when calling
* {@link AbstractSet#removeAll(Collection)} with a {@link List} that is larger (or equal size) than
* the {@link AbstractSet}. In this case, {@link AbstractSet#removeAll(Collection)} will iterate
* over the set and call {@link List#contains(Object)} for each item (expensive).
*
* IntelliJ reports potentially affected Statements with the warning
* Call to 'set.removeAll(list)' may work slowly
. The related Java API bug tickets seem
* to be stuck: JDK-6982173
* JDK-6394757
*
* @param set
* The set from which we remove items.
* @param itemsToBeRemoved
* The items which should be removed from the set.
*/
public static void removeAll(Set set, List itemsToBeRemoved) {
if (itemsToBeRemoved.size() >= set.size()) {
itemsToBeRemoved.forEach(set::remove);
} else {
set.removeAll(itemsToBeRemoved);
}
}
/**
* Returns a new map where the given keys have been removed. In contrast to
* {@link Map#replaceAll(BiFunction)} this does not modify the original map.
*/
@NonNull
public static Map copyWithEntriesRemoved(Map map, Set keysToRemove) {
return map.entrySet().stream().filter(entry -> !keysToRemove.contains(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
/**
* Merges the two maps. The mergeFunction determines the resulting object. The arguments of the
* function can be null if a value exists only in one of the two maps.
*
* The resulting map will contain all keys of both maps.
*/
@NonNull
public static Map merge(Map map1, Map map2,
BiFunction mergeFunction) {
HashMap result = new HashMap<>();
Set keys = Sets.union(map1.keySet(), map2.keySet());
for (K key : keys) {
result.put(key, mergeFunction.apply(map1.get(key), map2.get(key)));
}
return result;
}
/**
* Performs a {@link Map#computeIfAbsent(Object, Function)} calculation on the provided {@code map},
* where the {@code mappingFunction} may throw a checked Exception.
*
* @see Map#computeIfAbsent(Object, Function)
*/
@SuppressWarnings("RedundantThrows") // We want the exception to be mentioned here explicitly
public static V computeIfAbsentWithException(Map map, K key,
FunctionWithException mappingFunction) throws E {
return map.computeIfAbsent(key, asSneakyFunction(mappingFunction));
}
/**
* Throws the provided checked exception without the compiler knowing that it is a checked
* exception. On the JVM (class file) level, all exceptions, checked or not, can be thrown
* regardless of the {@code throws} clause of your methods, which is why this works. The solution is
* necessary as some commonly used interfaces do not allow checked exception (e.g. {@link Function}
* in {@link Map#computeIfAbsent(Object, Function)}).
*
* @apiNote This method should stay private, because the sneaky throws should only be used in some
* rare special cases, where no other option is feasible, and the behaviour is
* well-defined.
*/
@SuppressWarnings("unchecked")
private static RuntimeException sneakyThrow(Throwable t) throws T {
throw (T) t;
}
/**
* Wraps the provided {@link FunctionWithException} in a {@link Function}, which will
* {@link #sneakyThrow(Throwable) sneaky throw} any thrown checked {@link Exception}.
*
* @apiNote This method should stay private, because the sneaky throws should only be used in some
* rare special cases, where no other option is feasible, and the behaviour is
* well-defined.
*/
public static Function asSneakyFunction(
FunctionWithException functionWithException) {
return value -> {
try {
return functionWithException.apply(value);
} catch (Exception e) {
throw sneakyThrow(e);
}
};
}
/**
* Filters the collection by testing all items against the {@link Predicate} and returning a
* {@link List} of those for which it returns true
.
*
*
* This method does not modify the original collection.
*/
public static List filter(Collection collection, Predicate filter) {
ArrayList result = new ArrayList<>();
for (T entry : collection) {
if (filter.test(entry)) {
result.add(entry);
}
}
return result;
}
/**
* Filters the collection by testing all items against the given predicate and returning a
* {@link List} of those for which it returns true
. Propagates checked exceptions
* declared by the given predicate.
*
*
* This method does not modify the original collection.
*/
public static List filterWithException(Collection collection,
FunctionWithException filter) throws E {
ArrayList result = new ArrayList<>();
for (T entry : collection) {
if (filter.apply(entry)) {
result.add(entry);
}
}
return result;
}
/**
* See {@link #filterToSet(Collection, Predicate, Supplier)}; a HashSet is constructed.
*/
public static Set filterToSet(Collection collection, Predicate filter) {
return filterToSet(collection, filter, HashSet::new);
}
/**
* Filters the collection by testing all items against the {@link Predicate} and returning a
* {@link Set} of those for which it returns true
.
*
* This method does not modify the original collection.
*/
public static Set filterToSet(Collection collection, Predicate filter,
Supplier> addToSet) {
Set result = addToSet.get();
for (T entry : collection) {
if (filter.test(entry)) {
result.add(entry);
}
}
return result;
}
/**
* Applies the mapper to all items in the list, for which the filter {@link Predicate} returns
* true
and returns the results as a {@link List}.
*
* This method does not modify the original collection.
*/
public static List filterAndMap(Collection list, Predicate filter,
Function mapper) {
return filterAndMapWithException(list, filter::test, mapper::apply);
}
/**
* Applies the mapper to all items in the list, for which the filter {@link Predicate} returns
* true
and returns the results as a {@link List}.
*
* This method does not modify the original collection.
*/
public static List filterAndMapWithException(
Collection list, PredicateWithException filter,
FunctionWithException mapper) throws X1, X2 {
List result = new ArrayList<>();
for (T entry : list) {
if (filter.test(entry)) {
result.add(mapper.apply(entry));
}
}
return result;
}
/**
* Returns a list that contains all elements of the specified list except the element at the
* specified index. Removes the index from the new List.
*
* This method does not modify the original list.
*/
public static List remove(List list, int index) {
ArrayList result = new ArrayList<>(list);
result.remove(index);
return result;
}
/**
* Sorts the specified list according to the order induced by the specified comparator.
*
* All elements in the list must implement the Comparable interface. Furthermore, all elements in
* the list must be mutually comparable (that is, e1.compareTo(e2) must not throw a
* {@link ClassCastException} for any elements e1 and e2 in the list).
*
* This method does not modify the original collection.
*/
public static List sort(Collection collection, Comparator comparator) {
ArrayList list = new ArrayList<>(collection);
list.sort(comparator);
return list;
}
/** Returns a sorted unmodifiable list for a collection. */
public static > UnmodifiableList asSortedUnmodifiableList(
Collection collection) {
return asUnmodifiable(sort(collection));
}
/**
* Returns one object from an {@link Iterable} or {@code null} if the iterable is empty.
*/
public static @Nullable T getAny(@NonNull Iterable iterable) {
Iterator iterator = iterable.iterator();
if (!iterator.hasNext()) {
return null;
}
return iterator.next();
}
/**
* Convert collection to array. This is a bit cleaner version of
* {@link Collection#toArray(Object[])}.
*/
@SuppressWarnings("unchecked")
public static T[] toArray(Collection collection, Class type) {
T[] result = (T[]) java.lang.reflect.Array.newInstance(type, collection.size());
Iterator it = collection.iterator();
for (int i = 0; i < collection.size(); i++) {
result[i] = it.next();
}
return result;
}
/**
* Copy an array. This is a shortcut for {@link Arrays#copyOf(Object[], int)} that does not require
* to specify the length.
*/
public static T[] copyArray(T[] original) {
return Arrays.copyOf(original, original.length);
}
/**
* Compute list of unordered pairs for all elements contained in a collection.
*/
public static List> computeUnorderedPairs(Collection collection) {
List elements = new ArrayList<>(collection);
List> pairs = new ArrayList<>();
int size = elements.size();
for (int firstIndex = 0; firstIndex < size; firstIndex++) {
for (int secondIndex = firstIndex + 1; secondIndex < size; secondIndex++) {
pairs.add(new ImmutablePair<>(elements.get(firstIndex), elements.get(secondIndex)));
}
}
return pairs;
}
/**
* Returns the last element in list or {@code null}, if list is empty.
*/
@SuppressWarnings("unchecked") // needed for Deque optimization, we know that elements must have type T
public static @Nullable T getLast(@NonNull List list) {
if (list.isEmpty()) {
return null;
}
if (list instanceof Deque) {
return ((Deque) list).getLast();
}
return list.get(list.size() - 1);
}
/**
* Returns the sublist of all but the first element in list or {@code null}, if list is empty.
*/
public static @Nullable List getRest(List list) {
if (list.isEmpty()) {
return null;
}
return list.subList(1, list.size());
}
/** Sorts the pair list by first index. */
public static , T> void sortByFirst(PairList list) {
sortByFirst(list, Comparator.naturalOrder());
}
/**
* Sorts the pair list with the given comparator. This is stable sorting, i.e. ordering of same
* elements does not change.
*/
public static void sortByFirst(PairList list, Comparator comparator) {
sortBy(list, PairList::getFirst, comparator);
}
/**
* Sorts the provided {@link PairList} with the given {@link Comparator} applied on the
* {@link PairList#getSecond(int) second} elements.
*/
public static void sortBySecond(PairList list, Comparator comparator) {
sortBy(list, PairList::getSecond, comparator);
}
private static void sortBy(PairList list, BiFunction, Integer, C> elementRetriever,
Comparator comparator) {
SortableDataUtils.sort(new ISortableData() {
@Override
public void swap(int i, int j) {
list.swapEntries(i, j);
}
@Override
public int size() {
return list.size();
}
@Override
public boolean isLess(int i, int j) {
int compare = comparator.compare(elementRetriever.apply(list, i), elementRetriever.apply(list, j));
if (compare == 0) {
return i < j;
}
return compare < 0;
}
});
}
/**
* Returns a list implementation that allows for efficient random access.
*
* If the passed collection already supports random access, it gets returned directly. Otherwise, a
* list that supports random access is returned with the same content as the passed list.
*/
public static List asRandomAccessList(Collection list) {
// It is not guaranteed that implementations of RandomAccess also
// implement List. Hence, we check for both.
if (list instanceof List && list instanceof RandomAccess) {
return (List) list;
}
return new ArrayList<>(list);
}
@SafeVarargs
private static > C unionCollection(Supplier collectionSupplier,
@Nullable Collection collection1, @Nullable Collection... furtherCollections) {
C result = collectionSupplier.get();
if (collection1 != null) {
result.addAll(collection1);
}
for (Collection collection : furtherCollections) {
if (collection != null) {
result.addAll(collection);
}
}
return result;
}
/**
* Return a set containing the union of all provided collections. We use a {@link HashSet}, i.e. the
* elements should support hashing.
*
* We use two separate arguments to ensure on the interface level that at least one collection is
* provided. This is transparent for the caller.
*
* All arguments can be null. The result will always be non-null and will be an empty set if all
* arguments are null.
*/
@SafeVarargs
public static HashSet unionSet(Collection collection1, Collection... furtherCollections) {
return unionCollection(HashSet::new, collection1, furtherCollections);
}
/**
* Return a set containing the union of all provided {@link EnumSet}s.
*
* We use two separate arguments to ensure on the interface level that at least one collection is
* provided. This is transparent for the caller.
*
* None of the arguments may be null.
*/
@SafeVarargs
public static > EnumSet enumUnionSet(Set initialSet, Set... otherSets) {
EnumSet union = EnumSet.copyOf(initialSet);
for (Set other : otherSets) {
union.addAll(other);
}
return union;
}
/**
* Return a list containing the union of all provided collections. We use a {@link ArrayList} and
* the result preserves duplicates between and within the collections.
*
* We use two separate arguments to ensure on the interface level that at least one collection is
* provided. This is transparent for the caller.
*
* All arguments can be null. The result will always be non-null and will be an empty set if all
* arguments are null.
*/
@SafeVarargs
public static ArrayList unionList(Collection collection1, Collection... furtherCollections) {
return unionCollection(ArrayList::new, collection1, furtherCollections);
}
/**
* Return a set containing the union of all elements of the provided sets. We use a {@link HashSet},
* i.e. the elements should support hashing.
*/
public static HashSet unionSetAll(Collection> sets) {
HashSet result = new HashSet<>();
for (Collection set : sets) {
result.addAll(set);
}
return result;
}
/**
* Creates a new set only containing those elements of the given collection that are not in
* elementsToRemove. Subtracts elementsToRemove from collection. Both collections may be
* {@code null}.
*/
public static Set subtract(@Nullable Collection collection, @Nullable Collection elementsToRemove) {
Set result = new HashSet<>();
if (collection != null) {
result.addAll(collection);
}
if (elementsToRemove != null) {
result.removeAll(elementsToRemove);
}
return result;
}
/**
* Adds all elements of collection2 to collection1. collection2 may be {@code null}, in which case
* nothing happens.
*/
public static void addAllSafe(Collection collection1, @Nullable Collection collection2) {
if (collection2 != null) {
collection1.addAll(collection2);
}
}
/**
* Return a set containing the intersection of all provided collections. We use a {@link HashSet},
* i.e. the elements should support hashing.
*
* We use two separate arguments to ensure on the interface level that at least one collection is
* provided. This is transparent for the caller.
*/
@SafeVarargs
public static HashSet intersectionSet(Collection collection1, Collection... furtherCollections) {
HashSet result = new HashSet<>(collection1);
for (Collection collection : furtherCollections) {
if (collection instanceof Set) {
result.retainAll(collection);
} else {
// if the collection is not already a set, it will be
// significantly faster to first build a set, to speed up the
// containment query in the following call.
result.retainAll(new HashSet<>(collection));
}
}
return result;
}
/**
* Returns the set-theoretic difference between the first and the additional collections, i.e. a set
* containing all elements that occur in the first, but not in any of the other collections. We use
* a {@link HashSet}, so the elements should support hashing.
*/
@SafeVarargs
public static HashSet differenceSet(Collection collection1,
Collection... furtherCollections) {
HashSet result = new HashSet<>(collection1);
for (Collection collection : furtherCollections) {
if (collection instanceof Set) {
result.removeAll(collection);
} else {
// if the collection is not already a set, it will be
// significantly faster to first build a set, to speed up the
// containment query in the following call.
result.removeAll(new HashSet<>(collection));
}
}
return result;
}
/**
* Returns the set-theoretic difference between the first and the additional collections, based on
* the output of the provided {@code keyCalculator}.
*
* This method supports duplicate keys. In case multiple values from the {@code initialCollection}
* are mapped to the same key, they are either all removed or all retained.
*
* @param keyCalculator
* Calculates the key {@code K}, which will be used for element comparison.
* @param initialCollection
* collection upon which the difference will be calculated.
* @param furtherCollections
* Collections containing elements that should be removed from the
* {@code initialCollection}
* @param
* collection value type
* @param
* key type used to distinguish values of {@code T}. Should implement a meaningful
* {@link Object#equals(Object) equals()} and {@link Object#hashCode() hashCode()}.
* @return difference set, based on the calculated values of {@code K}
* @apiNote The result is no {@link Set}, as it is possible that equal elements are evaluated to a
* different key.
*/
@SafeVarargs
public static Collection difference(Function keyCalculator, Collection initialCollection,
Collection... furtherCollections) {
ListMap