net.freeutils.util.Containers Maven / Gradle / Ivy
Show all versions of jelementary Show documentation
/*
* Copyright © 2003-2024 Amichai Rothman
*
* This file is part of JElementary - the Java Elementary Utilities package.
*
* JElementary is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* JElementary is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JElementary. If not, see .
*
* For additional info see https://www.freeutils.net/source/jelementary/
*/
package net.freeutils.util;
import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.BlockingQueue;
/**
* The {@code Containers} class contains static utility methods relating
* to various containers and data structures.
*/
public class Containers {
/**
* Private constructor to avoid external instantiation.
*/
private Containers() {}
/**
* Converts the given collection to an array.
*
* @param the result array component type
* @param c the collection to convert
* @param componentType the component type of the returned array
* @return an array containing all elements from the collection, or null if it is null
*/
@SuppressWarnings("unchecked")
public static T[] toArray(Collection extends T> c, Class componentType) {
return c == null ? null : c.toArray((T[])Array.newInstance(componentType, c.size()));
}
/**
* Concatenates the given array and additional items.
*
* If both arguments are null arrays, returns null.
* If one of the arguments is a null array, a clone of the other is returned.
* If both are not null, a new array is returned containing the contents of
* {@code arr} followed by the contents of {@code items}.
*
* @param the component type of the array and additional items
* @param arr the original array
* @param items the items to concatenate to the original array
* @return the concatenated array
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static T[] concat(T[] arr, T... items) {
if (arr == null)
return items == null ? null : items.clone();
if (items == null || items.length == 0)
return arr.clone();
T[] result = Arrays.copyOf(arr, arr.length + items.length);
System.arraycopy(items, 0, result, arr.length, items.length);
return result;
}
/**
* Returns whether the given arrays contain the same elements, regardless of order or repetitions.
*
* @param a the first array
* @param b the second array
* @return true if the arrays contain the same elements
*/
public static boolean equalsIgnoreOrder(Object[] a, Object[] b) {
return new HashSet<>(Arrays.asList(a)).equals(new HashSet<>(Arrays.asList(b)));
}
/**
* Compares two byte array sections for equality.
*
* @param src the source byte array
* @param srcoffset the offset within src from which to start comparison
* @param dst the destination byte array
* @param dstoffset the offset within dst from which to start comparison
* @param length the number of bytes to compare
* @return true if the byte sequences in the respective specified locations
* are identical, false if there are any differences
*/
public static boolean equals(byte[] src, int srcoffset, byte[] dst, int dstoffset, int length) {
for (int i = 0; i < length; i++)
if (src[srcoffset + i] != dst[dstoffset + i])
return false;
return true;
}
/**
* Returns a List containing all the items in the given collection, in reverse order.
*
* @param the collection element type
* @param c a collection
* @return the reversed collection
*/
public static List reverse(Collection c) {
List reverse = new ArrayList<>(c);
Collections.reverse(reverse);
return reverse;
}
/**
* Returns an array containing all the items in the given array, in reverse order.
*
* @param the array element type
* @param arr an array
* @return the reversed array
*/
@SuppressWarnings("unchecked")
public static T[] reverse(T... arr) {
int len = arr.length;
if (len == 0)
return arr;
T[] reversed = (T[])Array.newInstance(arr.getClass().getComponentType(), len);
len--; // for more efficient indexing
for (int i = 0; i <= len; i++)
reversed[i] = arr[len - i];
return reversed;
}
/**
* Returns a copy of the given array, with all occurrences of the given element removed.
* Equality between each array element and the given element is checked using a reference
* comparison followed (if necessary) by a call to element.equals(e), thus both
* improving performance, and allowing for null elements to be removed.
*
* @param the array element type
* @param arr an array
* @param element the element to remove
* @return a copy of the given array, with all occurrences of the given element removed
*/
@SuppressWarnings("unchecked")
public static T[] remove(T[] arr, T element) {
List elements = new ArrayList<>(arr.length);
for (T e : arr)
if (element != e && (element == null || !element.equals(e)))
elements.add(e);
return Containers.toArray(elements, (Class)arr.getClass().getComponentType());
}
/**
* Puts all the specified key-value entries in the specified map.
*
* @param the map's key type
* @param the map's value type
* @param m the map into which the entries are to be inserted
* @param entries the entries to be put in the map, interpreted as
* key-value pairs, e.g. key1, value1, key2, value2, ..., keyN, keyN
* @return the given map with the inserted entries
*/
@SuppressWarnings("unchecked")
public static Map putAll(Map m, Object... entries) {
for (int i = 0; i < entries.length;)
m.put((K)entries[i++], (V)entries[i++]);
return m;
}
/**
* Puts all the specified key-value entries in a new {@code LinkedHashMap}.
*
* @param the map's key type
* @param the map's value type
* @param entries the entries to be put in the map, interpreted as
* key-value pairs, e.g. key1, value1, key2, value2, ..., keyN, keyN
* @return the newly created map with the inserted entries
*/
public static Map toMap(Object... entries) {
return putAll(new LinkedHashMap<>(entries.length), entries);
}
/**
* Puts all the respective key-value entries from the given collections in a new {@code LinkedHashMap}.
*
* @param the map's key type
* @param the map's value type
* @param keys the map's keys
* @param values the map's values
* @return the newly created map with the inserted entries
* @throws IllegalArgumentException if the keys and values are not of equal length
*/
public static Map toMap(Collection keys, Collection values) {
int len = keys.size();
if (len != values.size())
throw new IllegalArgumentException("keys and values must be of equal length");
Map map = new LinkedHashMap<>(len);
Iterator k = keys.iterator();
Iterator v = values.iterator();
while (k.hasNext()) {
map.put(k.next(), v.next());
}
return map;
}
/**
* Copies all entries of the given map into a new map,
* converting all keys and values to Strings.
*
* This is useful for converting a Properties instance into a
* map of strings.
*
* @param map a map from which entries are copied
* @return the new map into which the entries were copied
*/
public static Map toStringMap(Map, ?> map) {
Map strings = new LinkedHashMap<>(map.size());
for (Map.Entry, ?> e : map.entrySet())
strings.put(e.getKey().toString(), e.getValue().toString());
return strings;
}
/**
* Parses an array of enum constant names into
* a set containing the respective enum constants.
* Whitespace is treated leniently.
*
* @param cls an enum class
* @param names the enum constant names
* @param the enum type
* @return a set containing the enum constants, or an empty set
* @throws IllegalArgumentException if any of the names are not
* valid constant names of the given enum class
*/
public static > EnumSet parseEnumSet(Class cls, String... names) {
EnumSet set = EnumSet.noneOf(cls);
if (names != null) {
for (String name : names) {
name = name.trim();
if (!name.isEmpty())
set.add(Enum.valueOf(cls, name));
}
}
return set;
}
/**
* Parses a comma-separated string of enum constant names
* into a set containing the respective enum constants.
* Whitespace and extra commas are treated leniently.
*
* @param cls an enum class
* @param names a comma-separated string of enum constant names
* @param the enum type
* @return a set containing the enum constants, or an empty set
* @throws IllegalArgumentException if any of the names are not
* valid constant names of the given enum class
*/
public static > EnumSet parseEnumSet(Class cls, String names) {
return parseEnumSet(cls, names == null ? null : names.split(","));
}
/**
* Returns a comma-separated list of the constant names in the given set.
*
* @param set an enum set
* @param the enum type
* @return the string of enum constant names
*/
public static > String toString(EnumSet set) {
StringBuilder sb = new StringBuilder(set.size() * 8);
for (Iterator it = set.iterator(); it.hasNext(); )
sb.append(it.next().name()).append(it.hasNext() ? "," : "");
return sb.toString();
}
/**
* Removes duplicate elements from a collection.
*
* More specifically, this method iterates over the collection's
* elements (in iterator order), and removes any element which is
* either {@link Object#equals equal} to a previously encountered element,
* or is a null element other than the first encountered null.
*
* @param the element type
* @param c a collection
* @return the collection with duplicates removed (the same instance
* is returned, for convenient method chaining)
*/
public static Collection removeDuplicates(Collection c) {
int size = c.size();
if (size < 2)
return c;
Set set = new HashSet<>(size, 1);
for (Iterator it = c.iterator(); it.hasNext(); )
if (!set.add(it.next()))
it.remove();
return c;
}
/**
* Swaps two elements of an array.
*
* @param the array element type
* @param arr an array
* @param i the index of the first element
* @param j the index of the second element
*/
public static void swap(T[] arr, int i, int j) {
T t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
/**
* Randomly permutes a region of the given array.
* All permutations occur with equal likelihood.
*
* @param the array element type
* @param arr an array
* @param off the offset of the first element in the region to shuffle
* @param len the number of elements in the region to shuffle
*/
public static void shuffle(T[] arr, int off, int len) {
for (int i = len; i > 1; i--)
swap(arr, off + i - 1, off + Utils.rand.nextInt(i));
}
/**
* Returns a shuffled copy of the given list.
*
* @param the element type
* @param c the list
* @return a shuffled copy of the given list
*/
public static List shuffle(List c) {
List copy = new ArrayList<>(c);
Collections.shuffle(copy);
return copy;
}
/**
* Returns a column of the given table (two-dimensional array).
*
* @param table a table (two-dimensional array)
* @param column the index of the column
* @param startRow the index of the first row of the returned column
* @param the table type
* @return the column of the given table (as an array)
*/
@SuppressWarnings("unchecked")
public static T[] getColumn(T[][] table, int column, int startRow) {
int len = table.length;
Class> type = table.getClass().getComponentType().getComponentType();
T[] col = (T[])Array.newInstance(type, len - startRow);
for (int i = startRow; i < len; i++)
col[i - startRow] = table[i][column];
return col;
}
/**
* Copies the given collection.
*
* If {@code C} is the class of the given collection, the copy
* is created as if by performing
*
* C copy = new C();
* copy.addAll(c);
*
*
* If the collection cannot be constructed, null is returned
* (this will never happen for standard collection types).
*
* @param the collection element type
* @param the collection type
* @param c the collection to copy
* @return a copy of the collection
*/
@SuppressWarnings("unchecked")
public static > C copy(C c) {
try {
C copy = (C)c.getClass().getDeclaredConstructor().newInstance();
copy.addAll(c);
return copy;
} catch (Exception e) {
return null;
}
}
/**
* Drains all items in the given collection into a new collection of the same type.
*
* If the collection cannot be constructed, null is returned
* (this will never happen for standard collection types).
*
* @param the collection element type
* @param the collection type
* @param c the collection to drain
* @return the drained items
*/
@SuppressWarnings("unchecked")
public static > C drain(C c) {
try {
C copy = (C)c.getClass().getDeclaredConstructor().newInstance();
if (c instanceof BlockingQueue) {
((BlockingQueue)c).drainTo(copy);
} else {
for (Iterator it = c.iterator(); it.hasNext(); ) {
copy.add(it.next());
it.remove();
}
}
return copy;
} catch (Exception e) {
return null;
}
}
/**
* Returns whether a set of objects contains a given object.
*
* @param obj an object
* @param objects a set of objects
* @return true if at least one of the set of objects is equal to the given object
*/
public static boolean in(Object obj, Object... objects) {
for (Object o : objects)
if (obj == o || obj != null && obj.equals(o))
return true;
return false;
}
/**
* Converts a collection of Numbers to a primitive long array.
*
* @param c a collection of Numbers
* @return the corresponding primitive long array
*/
public static long[] toLongArray(Collection extends Number> c) {
long[] arr = new long[c.size()];
int ind = 0;
for (Number n : c)
arr[ind++] = n.longValue();
return arr;
}
/**
* Converts a collection of Numbers to a primitive int array.
*
* @param c a collection of Numbers
* @return the corresponding primitive int array
*/
public static int[] toIntArray(Collection extends Number> c) {
int[] arr = new int[c.size()];
int ind = 0;
for (Number n : c)
arr[ind++] = n.intValue();
return arr;
}
/**
* Returns an Iterable instance which simply returns the given iterator.
* The iteration will thus proceed from the current state of the iterator.
*
* This enables the use of an enhanced for loop (foreach) when only
* the Iterator itself is available (and not its parent Iterable).
*
* @param the element type
* @param iterator the iterator returned by the generated Iterable
* @return an Iterable instance which returns the given iterator
*/
public static Iterable iterable(final Iterator iterator) {
return new Iterable() {
@Override
public Iterator iterator() { return iterator; }
};
}
/**
* Returns an Iterator over an array's elements.
*
* @param the element type
* @param a the elements to iterate over
* @return an Iterator over the array elements
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static Iterator iterator(T... a) {
return Arrays.asList(a).iterator();
}
/**
* Returns an array of all elements returned by the given iterator (in order).
*
* @param the element type
* @param iterator an iterator
* @param componentType the component type of the returned array
* @return an array of all elements returned by the iterator (in order)
*/
public static T[] toArray(Iterator iterator, Class componentType) {
Collection c = new ArrayList<>();
while (iterator.hasNext())
c.add(iterator.next());
return toArray(c, componentType);
}
/**
* Returns an Iterator which comprises an outer iterator and a Mapper which provides
* an inner iterator for each outer element. The returned iterator returns the elements
* of the inner iterators by order.
*
* @param the element type of the inner (and returned) iterator
* @param the element type of the outer iterator
* @param outer the outer iterator from which inner iterators are generated
* @param mapper a mapper which maps each outer element to its inner iterator (or null if
* it has no corresponding inner iterator, in which case it is skipped)
* @return an iterator over the elements of the mapped inner iterators, in order
*/
public static Iterator nestedIterator(final Iterator outer, final Mapper> mapper) {
return new Iterator() {
Iterator inner;
@Override
public boolean hasNext() {
for (;;) {
if (inner != null && inner.hasNext())
return true;
if (outer == null || !outer.hasNext())
return false;
inner = mapper.map(outer.next());
}
}
@Override
public T next() {
if (!hasNext())
throw new NoSuchElementException();
return inner.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Returns an iterator whose elements are generated by mapping the elements of a given iterator.
*
* @param the element type of the given iterator
* @param the element type of the returned iterator
* @param it an iterator
* @param mapper a mapper used to generate elements from the given iterator's elements
* @return an iterator whose elements are generated by mapping the elements of the given iterator;
* the hasNext() and remove() methods are simply delegated to the given iterator
*/
public static Iterator mapIterator(final Iterator it, final Mapper mapper) {
return new Iterator() {
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public S next() {
return mapper.map(it.next());
}
@Override
public void remove() {
it.remove();
}
};
}
/**
* The {@code Mapper} interface defines the {@link #map} method
* which maps a given item to its corresponding mapped value.
*
* @param the item type
* @param the value type
*/
public interface Mapper {
V map(K item);
}
/**
* The {@code Reducer} interface defines the {@code #reduce} method
* which adds an item to the reduction calculation.
*
* @param the item type
* @param the reduction result type
*/
public interface Reducer {
R reduce(R prevResult, T item);
}
/**
* Returns the values corresponding to the given items. The mapping
* between items and values is performed using the given {@code Mapper}.
*
* @param the item type
* @param the value type
* @param mapper the mapper used to map items to their corresponding values
* @param items the items to map
* @return the mapped values corresponding to the given items
*/
public static Collection map(Mapper mapper, Collection items) {
Collection vals = new ArrayList<>(items.size());
for (K item : items)
vals.add(mapper.map(item));
return vals;
}
/**
* Returns the values corresponding to the given items. The mapping
* between items and values is performed using the given {@code Mapper}.
*
* @param the item type
* @param the value type
* @param mapper the mapper used to map items to their corresponding values
* @param items the items to map
* @return the mapped values corresponding to the given items
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static Collection map(Mapper mapper, K... items) {
return map(mapper, Arrays.asList(items));
}
/**
* Returns a map with values corresponding to the given map's values. The mapping
* between source and target values is performed using the given {@code Mapper}.
*
* @param the key type
* @param the source value type
* @param the target value type
* @param mapper the mapper used to map source values to their corresponding target values
* @param map the source map
* @return the mapped values corresponding to the given items
*/
public static Map map(Mapper mapper, Map map) {
Map target = new LinkedHashMap<>(map.size());
for (Map.Entry e: map.entrySet())
target.put(e.getKey(), mapper.map(e.getValue()));
return target;
}
/**
* Returns the reduction result calculated over the given items. The reduction
* result is calculated using the given {@code Reducer}.
*
* @param the item type
* @param the reduction result type
* @param reducer the reducer used to calculate the reduction result
* @param res the initial result value to start calculation with
* @param items the items to reduce
* @return the reduction result
*/
public static R reduce(Reducer reducer, R res, Collection items) {
for (T item : items)
res = reducer.reduce(res, item);
return res;
}
/**
* Returns the reduction result calculated over the given items. The reduction
* result is calculated using the given {@code Reducer}.
*
* @param the item type
* @param the reduction result type
* @param reducer the reducer used to calculate the reduction result
* @param res the initial result value to start calculation with
* @param items the items to reduce
* @return the reduction result
*/
@SafeVarargs
public static R reduce(Reducer reducer, R res, T... items) {
for (T item : items)
res = reducer.reduce(res, item);
return res;
}
/**
* Returns the reduction result calculated over the mapping of the given items.
* The mapping between items and values is performed using the given {@code Mapper}.
* The reduction result is calculated using the given {@code Reducer}.
*
* @param the item type
* @param the mapped value type
* @param the reduction result type
* @param mapper the mapper used to map items to their corresponding values
* @param reducer the reducer used to calculate the reduction result
* @param res the initial result value to start calculation with
* @param items the items to map and reduce
* @return the reduction result
*/
public static R mapreduce(Mapper mapper, Reducer reducer, R res, Collection items) {
for (K item : items)
res = reducer.reduce(res, mapper.map(item));
return res;
}
/**
* Returns the reduction result calculated over the mapping of the given items.
* The mapping between items and values is performed using the given {@code Mapper}.
* The reduction result is calculated using the given {@code Reducer}.
*
* @param the item type
* @param the mapped value type
* @param the reduction result type
* @param mapper the mapper used to map items to their corresponding values
* @param reducer the reducer used to calculate the reduction result
* @param res the initial result value to start calculation with
* @param items the items to map and reduce
* @return the reduction result
*/
@SafeVarargs
public static R mapreduce(Mapper mapper, Reducer reducer, R res, K... items) {
for (K item : items)
res = reducer.reduce(res, mapper.map(item));
return res;
}
/**
* Groups a collection of items according to a key which is calculated for each item.
*
* @param the item type
* @param the key type
* @param key the key calculator
* @param items the items to group
* @return map of keys and their corresponding groups (all items with the same key)
*/
public static Map> group(Mapper key, Collection items) {
int size = items.size();
Map> groups = new HashMap<>(size * 4 / 3);
for (T t : items) {
K k = key.map(t);
Collection group = groups.get(k);
if (group == null) {
group = new ArrayList<>(3);
groups.put(k, group);
}
group.add(t);
}
return groups;
}
/**
* Returns whether the given collection is null or empty.
*
* @param c the collection
* @return true if the collection is null or empty, false otherwise
*/
public static boolean isEmpty(Collection> c) {
return c == null || c.isEmpty();
}
}