![JAR search and dependency download from the Maven repository](/logo.png)
net.pincette.util.Collections Maven / Gradle / Ivy
package net.pincette.util;
import static java.lang.Integer.min;
import static java.util.Arrays.copyOf;
import static java.util.Collections.nCopies;
import static java.util.Optional.ofNullable;
import static java.util.regex.Pattern.quote;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static net.pincette.util.Pair.pair;
import static net.pincette.util.StreamUtil.stream;
import static net.pincette.util.Util.countingIterator;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;
import net.pincette.function.SideEffect;
/**
* Collection utilities.
*
* @author Werner Donn\u00e9
*/
public class Collections {
private Collections() {}
/**
* Works like Map.computeIfAbsent
, but avoids a concurrent access exception when the
* given function also modifies the map.
*
* @param map the given map.
* @param key the key to look up.
* @param fn the function that generates a new value if it doesn't exist for the given key.
* @param the key type.
* @param the value type.
* @return The existing or generated value.
* @since 1.8.1
*/
public static V computeIfAbsent(
final Map map, final K key, final Function super K, ? extends V> fn) {
return ofNullable(map.get(key))
.orElseGet(
() ->
Optional.of(fn.apply(key))
.map(v -> SideEffect.run(() -> map.put(key, v)).andThenGet(() -> v))
.orElse(null));
}
/**
* Works like Map.computeIfPresent
, but avoids a concurrent access exception when the
* given function also modifies the map.
*
* @param map the given map.
* @param key the key to look up.
* @param fn the function that generates a new value if one already exists for the given key.
* @param the key type.
* @param the value type.
* @return The new value or null
.
* @since 1.8.1
*/
public static V computeIfPresent(
final Map map, final K key, final BiFunction super K, ? super V, ? extends V> fn) {
return ofNullable(map.get(key))
.map(
v ->
ofNullable(fn.apply(key, v))
.map(val -> SideEffect.run(() -> map.put(key, v)).andThenGet(() -> val))
.orElseGet(
() -> SideEffect.run(() -> map.remove(key)).andThenGet(() -> null)))
.orElse(null);
}
/**
* Concatenates collections
into one list containing the elements of the collections.
*
* @param collections the given collections.
* @param the element type.
* @return The new list.
* @since 1.7
*/
@SafeVarargs
public static List concat(final Collection... collections) {
return concat(Arrays.stream(collections));
}
/**
* Concatenates collections
into one list containing the elements of the collections.
*
* @param collections the given collections.
* @param the element type.
* @return The new list.
* @since 1.7
*/
public static List concat(final Stream> collections) {
return collections.flatMap(Collection::stream).collect(toList());
}
/**
* Returns a new set with the elements of c1
but without those that also occur in
* c2
.
*
* @param c1 the first collection.
* @param c2 the second collection.
* @param the type of the elements in the collections and the result.
* @return The new set.
*/
public static Set difference(final Collection c1, final Collection c2) {
final Set result = new HashSet<>(c1);
result.removeAll(c2);
return result;
}
/**
* Returns a map where all keys that are paths with segments separated by delimiter
* are replaced with the first segment as the key and an expanded submap with the remainder of the
* segments as the value.
*
* @param map the given map.
* @param delimiter the delimiter for the keys in the given map.
* @return The new expanded map.
* @since 1.8
*/
public static Map expand(final Map map, final String delimiter) {
final Map result = new HashMap<>();
map.forEach(
(k, v) -> {
final String[] segments = k.split(quote(delimiter));
Arrays.stream(copyOf(segments, segments.length - 1))
.reduce(
result,
(m, segment) ->
(Map) m.computeIfAbsent(segment, s -> new HashMap<>()),
(m1, m2) -> m1)
.put(segments[segments.length - 1], v);
});
return result;
}
/**
* Returns a map where the keys are paths created by the keys of the maps and the submaps,
* separated by delimiter
. The keys of all the submaps must be strings too. The
* result is a map without submaps.
*
* @param map the given map.
* @param delimiter the delimiter for the keys in the new map.
* @return The new flattened map.
* @since 1.8
*/
public static Map flatten(final Map map, final String delimiter) {
return map.entrySet().stream()
.flatMap(
e ->
e.getValue() instanceof Map
? flatten((Map) e.getValue(), delimiter).entrySet().stream()
.map(sub -> pair(e.getKey() + delimiter + sub.getKey(), sub.getValue()))
: Stream.of(pair(e.getKey(), e.getValue())))
.collect(toMap(p -> p.first, p -> p.second));
}
/**
* Returns an optional value for a key. The value will be empty is the key doesn't exist.
*
* @param map the map that is queried.
* @param key the query key.
* @param the key type.
* @param the value type.
* @return The optional value.
*/
public static Optional get(final Map map, final K key) {
return ofNullable(map.get(key));
}
/**
* Returns a stream of pairs for a list, where the second element of the pair is the zero-based
* position of the element in the list.
*
* @param list the list of which the stream is made.
* @param the type of the elements in the list.
* @return The stream of pairs.
*/
public static Stream> indexedStream(final List list) {
return stream(countingIterator(list.iterator()));
}
/**
* Returns a new set containing all elements that are common in the given collections.
*
* @param collections the given collections.
* @param the element type.
* @return The intersection.
*/
@SafeVarargs
public static Set intersection(final Collection... collections) {
return intersection(Arrays.stream(collections));
}
/**
* Returns a new set containing all elements that are common in the given collections.
*
* @param collections the given collections.
* @param the element type.
* @return The intersection.
* @since 1.7
*/
public static Set intersection(final Stream> collections) {
return collections
.map(HashSet::new)
.reduce((s1, s2) -> SideEffect.>run(() -> s1.retainAll(s2)).andThenGet(() -> s1))
.map(Set.class::cast)
.orElseGet(java.util.Collections::emptySet);
}
/**
* Creates a list with the given elements.
*
* @param elements the given elements.
* @param the element type.
* @return The new list.
*/
@SafeVarargs
public static List list(final T... elements) {
return Arrays.stream(elements).collect(toList());
}
/**
* Creates a map with the given element pairs, where each first element is a key and each second
* element a value.
*
* @param pairs the given pairs.
* @param the key type.
* @param the value type.
* @return The new map.
*/
@SafeVarargs
public static Map map(final Pair... pairs) {
return map(Arrays.stream(pairs));
}
/**
* Creates a map with the given element pairs, where each first element is a key and each second
* element a value.
*
* @param pairs the given pairs.
* @param the key type.
* @param the value type.
* @return The new map.
* @since 1.7
*/
public static Map map(final Stream> pairs) {
return pairs.collect(toMap(pair -> pair.first, pair -> pair.second));
}
/**
* Returns a new map with all the mappings of the given maps combined. When there is more than one
* mapping for a key only the last one will be retained.
*
* @param maps the given maps.
* @param the key type.
* @param the value type.
* @return The new map.
*/
@SafeVarargs
public static Map merge(final Map... maps) {
return merge(Arrays.stream(maps));
}
/**
* Returns a new map with all the mappings of the given maps combined. When there is more than one
* mapping for a key only the last one will be retained.
*
* @param maps the given maps.
* @param the key type.
* @param the value type.
* @return The new map.
* @since 1.7
*/
public static Map merge(final Stream
© 2015 - 2025 Weber Informatics LLC | Privacy Policy