diff --git a/primitiveCollections/src/main/java/org/lucares/collections/LongList.java b/primitiveCollections/src/main/java/org/lucares/collections/LongList.java new file mode 100644 index 0000000..22fe225 --- /dev/null +++ b/primitiveCollections/src/main/java/org/lucares/collections/LongList.java @@ -0,0 +1,1052 @@ +package org.lucares.collections; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.Spliterator.OfLong; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.LongStream; +import java.util.stream.StreamSupport; + +/** + * A list for primitive longs. + *

+ * This class does not (yet) implements all methods a java.util {@link List} + * would have. + */ +public final class LongList implements Serializable, Cloneable { + + private static final long serialVersionUID = 2622570032686034909L; + + private static final int DEFAULT_CAPACITY = 10; + + /** + * The maximum size of an array. + */ + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + private static final long[] EMPTY_ARRAY = {}; + + /** + * The array containing the values. It is transient, so that we can implement + * our own serialization. + */ + transient private long[] data; + + private int size = 0; + + /** + * Keeps track of whether or not the list is sorted. This allows us to use + * binary search for {@link #indexOf(int)}. An empty list is sorted. + */ + private boolean sorted = true; + + /** + * Create a new {@link LongList} with initial capacity 10. + */ + public LongList() { + this(DEFAULT_CAPACITY); + } + + /** + * Create a new {@link LongList}. + * + * @param initialCapacity + * initial capacity + * @throws IllegalArgumentException + * if initial capacity is negative + */ + public LongList(final int initialCapacity) { + if (initialCapacity < 0 || initialCapacity > MAX_ARRAY_SIZE) { + throw new IllegalArgumentException( + "initial capacity must not be negative and not larger than " + MAX_ARRAY_SIZE); + } + + data = initialCapacity > 0 ? new long[initialCapacity] : EMPTY_ARRAY; + } + + /** + * Create a new {@link LongList} with a copy of the elements of + * {@code longList}. + * + * @param longList + * the list to copy + * @throws NullPointerException + * if the specified {@link LongList} is null + */ + public LongList(final LongList longList) { + data = EMPTY_ARRAY; + addAll(longList); + } + + /** + * Create a new {@link LongList} with a copy of the given elements. + * + * @param values + * the values + * @return the list + * @throws NullPointerException + * if the specified array is null + */ + public static LongList of(final long... values) { + final LongList result = new LongList(values.length); + result.addAll(values); + return result; + } + + /** + * Returns {@code true} if this list contains no elements. + * + * @return {@code true} if this list contains no elements + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Returns the number of elements in this list. + * + * @return the number of elements in this list + */ + public int size() { + return size; + } + + /** + * Returns the number of elements this list can hold before a resize is + * necessary, this is the size of the internal backing array. + * + * @return the number of elements this list can hold before a resize is + * necessary + */ + public int getCapacity() { + return data.length; + } + + /** + * Returns whether or not the list is sorted. + *

+ * An empty list is sorted. + * + * @return true iff the list is sorted. + */ + public boolean isSorted() { + return sorted; + } + + /** + * Adds {@code value} to the list. + * + * @param value + * the value to add + */ + public void add(final long value) { + ensureCapacity(1); + + data[size] = value; + + if (sorted) { + sorted = size == 0 ? sorted : data[size - 1] <= data[size]; + } + size++; + } + + /** + * Inserts {@code values} at position {@code pos} into the list. + * + * @param pos + * the position to insert the elements + * @param values + * the elements to insert + * @throws IndexOutOfBoundsException + * if pos is out of bounds {@code pos < 0 || pos > size()} + * @throws NullPointerException + * if the given array is null + */ + public void insert(final int pos, final long... values) { + + if (pos < 0) { + throw new IndexOutOfBoundsException("pos must not be negative, but was: " + pos); + } + if (pos > size) { + throw new IndexOutOfBoundsException("pos must be smaller than size(), but was: " + pos); + } + if (values == null) { + throw new NullPointerException("values parameter must not be null"); + } + if (values.length == 0) { + return; + } + + ensureCapacity(values.length); + + // move everything after the insert position to make room for the new values + System.arraycopy(data, pos, data, pos + values.length, size - pos); + // insert the new values + System.arraycopy(values, 0, data, pos, values.length); + size += values.length; + + if (sorted) { + // compare with element before the insertion + sorted = pos == 0 ? sorted : data[pos - 1] <= data[pos]; + + // check if the inserted values are sorted + for (int i = 1; i < values.length && sorted; i++) { + sorted = data[pos + i - 1] <= data[pos + i]; + } + if (sorted) { + // compare last inserted element with next element in the list + sorted = pos + values.length < size()// + ? data[pos + values.length - 1] <= data[pos + values.length]// + : sorted; + } + } + + } + + /** + * Set the value {@code value} at position {@code pos}. + * + * @param pos + * the position to overwrite + * @param value + * the new value + * @throws IndexOutOfBoundsException + * if pos is out of bounds {@code pos < 0 || pos >= size()} + */ + public void set(final int pos, final long value) { + + if (pos < 0) { + throw new IndexOutOfBoundsException("pos must not be negative, but was: " + pos); + } + + if (pos >= size) { + throw new IndexOutOfBoundsException("pos must not smaller than size(), but was: " + pos); + } + + data[pos] = value; + + if (sorted) { + sorted = pos <= 0 ? sorted : data[pos - 1] <= data[pos]; + sorted = pos + 1 >= size() ? sorted : data[pos] <= data[pos + 1]; + } + } + + /** + * Add {@code values} to the list. + * + * @param values + * the values to add + * @throws NullPointerException + * if the given array is null + */ + public void addAll(final long... values) { + ensureCapacity(values.length); + + System.arraycopy(values, 0, data, size, values.length); + + if (sorted) { + for (int i = 0; i < values.length && sorted; i++) { + sorted = size + i - 1 < 0 ? sorted : data[size + i - 1] <= data[size + i]; + } + } + + size += values.length; + } + + /** + * Add all value of the given list. + * + * @param list + * the list + * @throws NullPointerException + * if the given list is null + */ + public void addAll(final LongList list) { + ensureCapacity(list.size()); + + System.arraycopy(list.data, 0, data, size, list.size()); + + if (sorted) { + sorted = list.isSorted(); + if (list.isSorted() // new list is sorted + && !isEmpty() // if old list is empty, then the new list's sorted value is equal to that of + // the new list + && !list.isEmpty()) // if new list is empty, then old list stays sorted + { + // check that the first new value is bigger than the highest old value + final long highestOldValue = data[size - 1]; // size has still the old value + final long lowestNewValue = list.get(0); + sorted = highestOldValue <= lowestNewValue; + } + } + + size += list.size(); + } + + /** + * Removes elements from the list. + *

+ * This method does not release any memory. Call {@link #trim()} to free unused + * memory. + * + * @param fromIndex + * index of the first element to remove + * @param toIndex + * the index of the last element to remove (exclusive) + * @throws IndexOutOfBoundsException + * if {@code fromIndex} or {@code toIndex} is negative, or if the + * range defined by {@code fromIndex} and {@code toIndex} is out of + * bounds + * @see #trim() + */ + public void remove(final int fromIndex, final int toIndex) { + + if (fromIndex < 0) { + throw new IndexOutOfBoundsException("from must not be negative, but was: " + fromIndex); + } + if (toIndex < fromIndex) { + throw new IndexOutOfBoundsException("toIndex must not be smaller than fromIndex, but was: " + toIndex); + } + if (toIndex > size) { + throw new IndexOutOfBoundsException( + "toIndex must not be larger than the size of this list, but was: " + toIndex); + } + + final int numRemoved = size - toIndex; + System.arraycopy(data, toIndex, data, fromIndex, numRemoved); + + size = size - (toIndex - fromIndex); + + if (!sorted) { + sorted = true; + for (int i = 1; i < size && sorted; i++) { + sorted = data[i - 1] <= data[i]; + } + } + } + + /** + * Remove all elements that contained in the specified list. + *

+ * This method does not release any memory. Call {@link #trim()} to free unused + * memory. + *

+ * If {@code retain} is sorted, then the algorithm has a complexity of + * O(n*log(m)), where n is the length of {@code this} and m the length of + * {@code retain}. If {@code retain} is not sorted, then the complexity is + * O(n*m). + * + * @param remove + * the elements to remove + * @throws NullPointerException + * if the specified {@link LongList} is null + * @see #trim() + */ + public void removeAll(final LongList remove) { + removeIf((val, index) -> remove.indexOf(val) >= 0); + } + + /** + * Remove all elements that match the given predicate. + *

+ * This method does not release any memory. Call {@link #trim()} to free unused + * memory. + * + * + * @param predicate + * the predicate + * @throws NullPointerException + * if the specified predicate is null + * @see #trim() + */ + public void removeIf(final LongPredicate predicate) { + + int insertPosition = 0; + for (int i = 0; i < size; i++) { + final long current = data[i]; + if (!predicate.test(current, i)) { + // keep current element + data[insertPosition] = current; + insertPosition++; + } + } + size = insertPosition; + + if (!sorted) { + sorted = true; + for (int i = 1; i < size && sorted; i++) { + sorted = data[i - 1] <= data[i]; + } + } + } + + /** + * Retains all elements contained in the specified list. + *

+ * This method does not release any memory. Call {@link #trim()} to free unused + * memory. + *

+ * For a method that computes the intersection of two lists and also removes + * duplicate values, see {@link #intersection(LongList, LongList)}. + *

+ * If {@code retain} is sorted, then the algorithm has a complexity of + * O(n*log(m)), where n is the length of {@code this} and m the length of + * {@code retain}. If {@code retain} is not sorted, then the complexity is + * O(n*m). + * + * @param retain + * the elements to retain + * @throws NullPointerException + * if the specified {@link LongList} is null + * @see #trim() + * @see #intersection(LongList, LongList) + */ + public void retainAll(final LongList retain) { + removeIf((val, index) -> retain.indexOf(val) < 0); + } + + /** + * Replaces all values in the list by applying {@code operator}. + * + * @param operator + * the operator + * @throws NullPointerException + * if the specified {@link UnaryLongOperator} is null + */ + public void replaceAll(final UnaryLongOperator operator) { + + for (int i = 0; i < size; i++) { + final long newValue = operator.apply(data[i]); + set(i, newValue); + } + } + + /** + * Returns the element at position {@code pos}. + * + * @param pos + * position of the element to return + * @return the element at position {@code pos} + * @throws IndexOutOfBoundsException + * if {@code pos} is out of bounds + * {@code index < 0 || index >= size()} + */ + public long get(final int pos) { + if (pos < 0 || pos >= size) { + throw new IndexOutOfBoundsException(); + } + return data[pos]; + } + + /** + * Returns the {@code length} elements starting at {@code from}. + * + * @param from + * position of the first element + * @param length + * number of elements + * @return the {@code length} elements starting at {@code from} + * @throws IndexOutOfBoundsException + * if {@code from} or {@code length} is negative, or if the range + * defined by {@code from} and {@code length} is out of bounds + */ + public long[] get(final int from, final int length) { + if (from < 0) { + throw new IndexOutOfBoundsException("from must not be negative, but was: " + from); + } + if (length < 0) { + throw new IndexOutOfBoundsException("length must not be negative, but was: " + length); + } + + if (from + length > size) { + throw new IndexOutOfBoundsException("from: " + from + " length: " + length); + } + return Arrays.copyOfRange(data, from, from + length); + } + + /** + * Returns an array containing all elements of this list. + * + * @return an array containing all elements of this list + */ + public long[] toArray() { + return get(0, size); + } + + /** + * Fills the given array with the elements of this list if the array can hold + * all elements. A new array is returned otherwise. + * + * @param input + * @throws NullPointerException + * if the specified array is null + * @return an array containing all elements of this list + */ + public long[] toArray(final long[] input) { + + if (input.length < size) { + return toArray(); + } + System.arraycopy(data, 0, input, 0, size); + return input; + } + + /** + * Sorts the list into ascending order. + */ + public void sort() { + Arrays.sort(data, 0, size); + sorted = true; + } + + /** + * Sorts the list into ascending order using an algorithm than can use + * parallelism. + */ + public void parallelSort() { + Arrays.parallelSort(data, 0, size); + sorted = true; + } + + /** + * Shuffles the list. The permutation is uniformly distributed. + */ + public void shuffle() { + shuffle(ThreadLocalRandom.current()); + } + + /** + * Shuffles the list. The permutation is uniformly distributed. + * + * @param random + * the random number generator used + */ + public void shuffle(final Random random) { + + /* + * See Knuth, Donald E. Seminumerical algorithms. (1998). The Art of Computer + * Programming. 2. Addison–Wesley pp. 145–146. Algorithm P and exercise 18. + */ + for (int i = size() - 1; i > 0; i--) { + final int other = random.nextInt(i); + + final long tmp = data[other]; + data[other] = data[i]; + data[i] = tmp; + } + + // update sorted + sorted = true; + for (int i = 1; i < size && sorted; i++) { + sorted = data[i - 1] <= data[i]; + } + } + + private void ensureCapacity(final int newElements) { + + final int requiredCapacity = size + newElements; + if (requiredCapacity < 0 || requiredCapacity > MAX_ARRAY_SIZE) { + // overflow, or too big + throw new OutOfMemoryError(); + } + if (requiredCapacity > data.length) { + + int newCapacity = data.length + data.length / 2; + newCapacity = Math.max(requiredCapacity, newCapacity); + newCapacity = Math.min(MAX_ARRAY_SIZE, newCapacity); + + data = Arrays.copyOf(data, newCapacity); + } + } + + /** + * Reduces the capacity to the size of the list. + *

+ * Call this method to reduce the memory consumption of this list. + */ + public void trim() { + if (size == 0) { + data = EMPTY_ARRAY; + } else if (size != data.length) { + data = Arrays.copyOf(data, size); + } + } + + /** + * Removes all elements from this list. + *

+ * The implementation is equivalent to calling {@code remove(0, size())} and + * {@code trim()}. + */ + public void clear() { + data = EMPTY_ARRAY; + size = 0; + sorted = true; + } + + /** + * Returns a sequential {@link LongStream} with this collection as its source. + * + * @return a sequential {@link LongStream} + */ + public LongStream stream() { + + return Arrays.stream(data, 0, size); + } + + /** + * Returns a parallel {@link LongStream} with this collection as its source. + * + * @return a parallel {@link LongStream} + */ + public LongStream parallelStream() { + final OfLong spliterator = Arrays.spliterator(data, 0, size); + return StreamSupport.longStream(spliterator, true); + } + + /** + * Returns the index of the first occurrence of {@code value}, or -1 if it does + * not exist. + *

+ * This method uses a binary search algorithm if the list is sorted. + * + * @param value + * the value + * @return the index, or -1 + * @see #isSorted() + */ + public int indexOf(final long value) { + return indexOf(value, 0); + } + + /** + * Returns the index of the first occurrence of {@code value}, or -1 if it does + * not exist. + *

+ * This method uses a binary search algorithm if the list is sorted. + * + * @param value + * the value + * @param offset + * the offset (inclusive). There is no invalid value. If the offset + * is negative, then it behaves as if it was 0. If it is >= + * {@link #size()}, then -1 is returned. + * @return the index, or -1 + * @throws ArrayIndexOutOfBoundsException + * if offset is negative, or larger than the size of the list + * @throws IllegalArgumentException + * if offset is negative + * @see #isSorted() + */ + public int indexOf(final long value, final int offset) { + + int result = -1; + if (sorted && offset >= 0 && offset < size()) { + int insertionPoint = Arrays.binarySearch(data, offset, size(), value); + while (insertionPoint > offset && data[insertionPoint - 1] == value) { + insertionPoint--; + } + result = insertionPoint < 0 ? -1 : insertionPoint; + } else { + final int start = Math.max(offset, 0); + for (int i = start; i < size; i++) { + if (data[i] == value) { + result = i; + break; + } + } + } + return result; + } + + /** + * Returns the index of the last occurrence of {@code value} searching + * backwards, or -1 if it does not exist. + *

+ * This method uses a binary search algorithm if the list is sorted. + * + * @param value + * the value + * @return the index, or -1 + * @see #isSorted() + */ + public int lastIndexOf(final long value) { + return lastIndexOf(value, size() - 1); + } + + /** + * Returns the index of the last occurrence of {@code value} searching backwards + * from {@code fromIndex}, or -1 if it does not exist. + *

+ * This method uses a binary search algorithm if the list is sorted. + * + * @param value + * the value + * @param fromIndex + * the index the start the search from (inclusive). There is no + * invalid input. If {@code fromIndex} is < 0, then -1 is + * returned. If it is larger than the length of the list, then it + * behaves as if it were {@link #size()}-1. + * @return the index, or -1 + * @see #isSorted() + */ + public int lastIndexOf(final long value, final int fromIndex) { + int result = -1; + + if (sorted) { + final int toIndex = Math.min(size - 1, fromIndex + 1); // toIndex is exclusive in binarySearch, but + // fromIndex is inclusive + if (toIndex > 0) { + int insertionPoint = Arrays.binarySearch(data, 0, toIndex, value); + + // if value exists more than once, then binarySearch can find any one of them, + // but we are looking for the last occurrence + while (insertionPoint >= 0 && insertionPoint < fromIndex && data[insertionPoint + 1] == value) { + insertionPoint++; + } + result = insertionPoint < 0 ? -1 : insertionPoint; + } + } else { + final int startIndex = Math.min(size - 1, fromIndex); + for (int i = startIndex; i >= 0; i--) { + if (data[i] == value) { + result = i; + break; + } + } + } + return result; + } + + @Override + public String toString() { + + assert data != null : "data cannot be null"; + final int iMax = size - 1; + if (iMax == -1) + return "[]"; + + final StringBuilder result = new StringBuilder(); + result.append('['); + for (int i = 0; i < size; i++) { + if (i > 0) { + result.append(", "); + } + result.append(data[i]); + } + return result.append(']').toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + size; + + /* + * only consider values in the range of 0 to size + */ + for (int i = 0; i < size; i++) { + result = 31 * result + (int) data[i]; // low bits + result = 31 * result + (int) (data[i] >> 32); // high bits + } + + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final LongList other = (LongList) obj; + if (size != other.size) + return false; + if (sorted != other.sorted) + return false; + + /* + * only consider values in the range of 0 to size + */ + for (int i = 0; i < size; i++) { + if (data[i] != other.data[i]) { + return false; + } + } + + return true; + } + + /** + * Serialize the {@link LongList}. + *

+ * This only serializes the used part of the data array. + * + * @serialData The length of the array containing the values, followed by the + * values. + */ + private void writeObject(final java.io.ObjectOutputStream s) throws java.io.IOException { + s.defaultWriteObject(); + + // write out the array length. According to the implementation in ArrayList this + // is needed to be compatible with clone. + s.writeInt(size); + + for (int i = 0; i < size; i++) { + s.writeLong(data[i]); + } + } + + /** + * Deserialize the {@link LongList} + */ + private void readObject(final java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { + data = EMPTY_ARRAY; + + s.defaultReadObject(); + + // Read in capacity + s.readInt(); // ignored + + if (size > 0) { + final long[] local_data = new long[size]; + + for (int i = 0; i < size; i++) { + local_data[i] = s.readLong(); + } + data = local_data; + } + } + + @Override + public LongList clone() { + try { + final LongList result = (LongList) super.clone(); + result.data = size == 0 ? EMPTY_ARRAY : Arrays.copyOf(data, size); + return result; + } catch (final CloneNotSupportedException e) { + throw new IllegalStateException(e); + } + } + + /** + * Returns a list with all elements that are in {@code a} and {@code b}. + *

+ * The cardinality of each element will be equal to one (like in a set). This + * method returns a new list. The list can have unused capacity. Consider using + * {@link #trim()}. + *

+ * If both lists were sorted, then the output list will also be sorted. + *

+ * See {@link #retainAll(LongList)} for a method that modifies the list and + * keeps duplicate values. + *

+ * If both lists are sorted, then the time complexity is O(n+m), where n is the + * length of the first list and m the length of the second list. If at least one + * list is not sorted, then the time complexity is O(n*m), where n is the length + * of the shorter list and m the length of the longer list. + * + * @param a + * a sorted {@link LongList} + * @param b + * a sorted {@link LongList} + * @return {@link LongList} containing all elements that are in {@code a} and + * {@code b} + * @throws NullPointerException + * if {@code a} or {@code b} is null + * @see #retainAll(LongList) + * @see #trim() + */ + public static LongList intersection(final LongList a, final LongList b) { + final LongList result; + + if (a.isSorted() && b.isSorted()) { + result = intersectionSorted(a, b); + } else { + result = intersectionUnsorted(a, b); + } + return result; + } + + /** + * Implements an intersection algorithm with O(n+m), where n is the length of + * the first list and m the length of the second list. + * + * @param a + * first list + * @param b + * second list + * @return the intersection + */ + private static LongList intersectionSorted(final LongList a, final LongList b) { + final int aSize = a.size(); + final int bSize = b.size(); + final LongList result = new LongList(Math.min(aSize, bSize)); + + assert a.isSorted() : "first list must be sorted"; + assert b.isSorted() : "second list must be sorted"; + + int l = 0; + int r = 0; + + while (l < aSize && r < bSize) { + + final long lv = a.get(l); + final long rv = b.get(r); + + if (lv < rv) { + l++; + } else if (lv > rv) { + r++; + } else { + result.add(lv); + do { + l++; + } while (l < aSize && lv == a.get(l)); + do { + r++; + } while (r < bSize && rv == b.get(r)); + } + } + return result; + } + + /** + * Implements an algorithm with O(n*m), where n is the length of the shorter + * list and m the length of the longer list. + * + * @param a + * first list + * @param b + * second list + * @return the intersection + */ + private static LongList intersectionUnsorted(final LongList a, final LongList b) { + final int aSize = a.size(); + final int bSize = b.size(); + final LongList result; + + if (aSize < bSize) { + result = new LongList(Math.min(aSize, bSize)); + + for (int l = 0; l < aSize; l++) { + final long lv = a.get(l); + + if (b.indexOf(lv) >= 0) { + result.add(lv); + } + + while (l + 1 < aSize && lv == a.get(l + 1)) { + l++; + } + } + } else { + result = intersectionUnsorted(b, a); + } + + return result; + } + + /** + * Returns a list with all elements that are in list {@code a} or {@code b} + * (logical or). + *

+ * The result does not contain duplicate values. + *

+ * If both lists were sorted, then the output list will also be sorted. If at + * least one list is unsorted, then the order is undefined. + *

+ * If both lists are sorted, then the time complexity is O(n+m), where n is the + * length of the first list and m the length of the second list. If at least one + * list is not sorted, then the time complexity is O(m*log(m)), where m is the + * length of the longer list. + * + * @param a + * the first list + * @param b + * the second list + * @return the union of both lists + */ + public static LongList union(final LongList a, final LongList b) { + final LongList result; + + if (a.isSorted() && b.isSorted()) { + result = unionSorted(a, b); + } else { + result = unionUnsorted(a, b); + } + return result; + } + + private static LongList unionSorted(final LongList a, final LongList b) { + + final int aSize = a.size(); + final int bSize = b.size(); + + final LongList result = new LongList(aSize + bSize); + + int l = 0; + int r = 0; + + while (l < aSize && r < bSize) { + + final long lv = a.get(l); + final long rv = b.get(r); + + if (lv < rv) { + result.add(lv); + l++; + while (l < aSize && lv == a.get(l)) { + l++; + } + } else if (lv > rv) { + result.add(rv); + r++; + while (r < bSize && rv == b.get(r)) { + r++; + } + } else { + result.add(lv); + l++; + r++; + + while (l < aSize && lv == a.get(l)) { + l++; + } + while (r < bSize && rv == b.get(r)) { + r++; + } + } + } + + // add remaining values from list a, if any exist + for (; l < aSize; l++) { + if (l == 0 || a.get(l) != a.get(l - 1)) { + result.add(a.get(l)); + } + } + + // add remaining values from list b, if any exist + for (; r < bSize; r++) { + if (r == 0 || b.get(r) != b.get(r - 1)) { + result.add(b.get(r)); + } + } + + return result; + } + + private static LongList unionUnsorted(final LongList a, final LongList b) { + final LongList aSorted = new LongList(a); + aSorted.parallelSort(); + final LongList bSorted = new LongList(b); + bSorted.parallelSort(); + + return unionSorted(aSorted, bSorted); + } +} diff --git a/primitiveCollections/src/main/java/org/lucares/collections/LongPredicate.java b/primitiveCollections/src/main/java/org/lucares/collections/LongPredicate.java new file mode 100644 index 0000000..7b47687 --- /dev/null +++ b/primitiveCollections/src/main/java/org/lucares/collections/LongPredicate.java @@ -0,0 +1,58 @@ +package org.lucares.collections; + +import java.util.function.Predicate; + +/** + * A {@link Predicate} for primitive longs. + */ +@FunctionalInterface +public interface LongPredicate { + + /** + * Evaluates the predicate. + * + * @param value + * the value + * @param index + * the index in the list + * @return {@code true} iff the input argument matches the predicate + */ + boolean test(long value, int index); + + /** + * Returns a predicate that represents the logical AND of {@code this} and + * another predicate. The and operation is short-circuiting. + * + * @param other + * the other predicate + * @return the result of combining both predicates with a logical AND operation + * @throws NullPointerException + * if {@code other} is null + */ + default LongPredicate and(final LongPredicate other) { + return (value, index) -> test(value, index) && other.test(value, index); + } + + /** + * Returns a predicate that represents the logical OR of {@code this} and + * another predicate. The and operation is short-circuiting. + * + * @param other + * the other predicate + * @return the result of combining both predicates with a logical OR operation + * @throws NullPointerException + * if {@code other} is null + */ + default LongPredicate or(final LongPredicate other) { + return (value, index) -> test(value, index) || other.test(value, index); + } + + /** + * Returns a predicate that negates the result of this predicate. + * + * @return the negation of {@code this} + */ + default LongPredicate negate() { + return (value, index) -> !test(value, index); + } +} diff --git a/primitiveCollections/src/main/java/org/lucares/collections/UnaryLongOperator.java b/primitiveCollections/src/main/java/org/lucares/collections/UnaryLongOperator.java new file mode 100644 index 0000000..f4f7f5f --- /dev/null +++ b/primitiveCollections/src/main/java/org/lucares/collections/UnaryLongOperator.java @@ -0,0 +1,18 @@ +package org.lucares.collections; + +import java.util.function.Function; + +/** + * Represents an operation that maps a long to a long. This is a specialization + * of {@link Function} for a primitive long. + */ +public interface UnaryLongOperator { + /** + * Applies the operation to the integer + * + * @param value + * the input value + * @return the result of the operation + */ + long apply(long value); +} diff --git a/primitiveCollections/src/test/java/org/lucares/collections/LongListTest.java b/primitiveCollections/src/test/java/org/lucares/collections/LongListTest.java new file mode 100644 index 0000000..937c148 --- /dev/null +++ b/primitiveCollections/src/test/java/org/lucares/collections/LongListTest.java @@ -0,0 +1,1454 @@ +package org.lucares.collections; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import java.util.stream.LongStream; + +import org.junit.Assert; +import org.junit.Test; + +public class LongListTest { + + @Test + public void testEmptyList() { + final LongList list = new LongList(); + + Assert.assertEquals(true, list.isEmpty()); + } + + @Test + public void testCopyConstructor() { + final LongList list = new LongList(); + list.addAll(1, 2, 3, 4, 5); + + final LongList copy = new LongList(list); + + Assert.assertArrayEquals(copy.toArray(), list.toArray()); + } + + @Test + public void testInvalidInitialCapacity() { + try { + new LongList(-1); + Assert.fail(); + } catch (final IllegalArgumentException e) { + // expected + } + } + + @Test + public void testAdd() { + // setting initial size to one, so that the first add does not need to resize, + // but the second add must + final LongList list = new LongList(1); + + list.add(1); + + Assert.assertFalse(list.isEmpty()); + Assert.assertEquals(1, list.size()); + + list.add(2); + Assert.assertEquals(2, list.size()); + + Assert.assertEquals(1, list.get(0)); + Assert.assertEquals(2, list.get(1)); + } + + @Test + public void testInsert() { + final LongList list = new LongList(); + + list.insert(0); + Assert.assertArrayEquals(new long[] {}, list.toArray()); + + list.insert(0, 1); + Assert.assertArrayEquals(new long[] { 1 }, list.toArray()); + + list.insert(1, 2, 2, 2); + Assert.assertArrayEquals(new long[] { 1, 2, 2, 2 }, list.toArray()); + + list.insert(1, 3); + Assert.assertArrayEquals(new long[] { 1, 3, 2, 2, 2 }, list.toArray()); + + list.insert(2, 4, 4, 4); + Assert.assertArrayEquals(new long[] { 1, 3, 4, 4, 4, 2, 2, 2 }, list.toArray()); + + list.insert(2); + Assert.assertArrayEquals(new long[] { 1, 3, 4, 4, 4, 2, 2, 2 }, list.toArray()); + + list.insert(8, 5, 5); + Assert.assertArrayEquals(new long[] { 1, 3, 4, 4, 4, 2, 2, 2, 5, 5 }, list.toArray()); + } + + @Test + public void testInsertOutOfBounds() { + final LongList list = new LongList(); + try { + list.insert(-1, 1); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + + try { + list.insert(1, 1); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + + try { + list.add(1); + list.insert(2, 2); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testInsertNull() { + final LongList list = new LongList(); + try { + list.insert(0, null); + Assert.fail(); + } catch (final NullPointerException e) { + // expected + } + } + + @Test + public void testSet() { + final LongList list = new LongList(); + list.addAll(0, 1, 2, 3, 4, 5); + + list.set(0, 10); + Assert.assertArrayEquals(new long[] { 10, 1, 2, 3, 4, 5 }, list.toArray()); + + list.set(1, 11); + Assert.assertArrayEquals(new long[] { 10, 11, 2, 3, 4, 5 }, list.toArray()); + + list.set(5, 55); + Assert.assertArrayEquals(new long[] { 10, 11, 2, 3, 4, 55 }, list.toArray()); + } + + @Test + public void testSetOutOfBounds() { + final LongList list = new LongList(); + try { + list.set(-1, 1); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + + try { + list.set(1, 1); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + + list.addAll(0, 1, 2, 3, 4, 5); + + try { + list.set(6, 1); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testGrow() { + final LongList list = new LongList(1); + + final int size = 100; + for (int i = 0; i < size; i++) { + list.add(i); + } + Assert.assertEquals(size, list.size()); + + for (int i = 0; i < size; i++) { + Assert.assertEquals(i, list.get(i)); + } + } + + @Test + public void testGrowLongListOfCapacityNull() { + final LongList list = new LongList(0); + + final int size = 100; + for (int i = 0; i < size; i++) { + list.add(i); + } + Assert.assertEquals(size, list.size()); + + for (int i = 0; i < size; i++) { + Assert.assertEquals(i, list.get(i)); + } + } + + @Test + public void testAddArray() { + final LongList list = new LongList(); + + list.addAll(); + Assert.assertTrue(list.isEmpty()); + Assert.assertEquals(0, list.size()); + + final int size = 100; + final long[] longs = ThreadLocalRandom.current().longs(size).toArray(); + + list.addAll(longs); + Assert.assertEquals(size, list.size()); + + for (int i = 0; i < size; i++) { + Assert.assertEquals(longs[i], list.get(i)); + } + + final long[] anotherLongs = ThreadLocalRandom.current().longs(size).toArray(); + list.addAll(anotherLongs); + Assert.assertEquals(size * 2, list.size()); + for (int i = 0; i < size; i++) { + Assert.assertEquals(anotherLongs[i], list.get(size + i)); + } + } + + @Test + public void testAddList() { + final LongList list = new LongList(); + + // adding empty list with no capacity + list.addAll(LongList.of()); + Assert.assertEquals(new LongList(), list); + Assert.assertTrue(list.isSorted()); + + // adding empty list with capacity 2 + list.addAll(new LongList(2)); + Assert.assertEquals(new LongList(), list); + Assert.assertTrue(list.isSorted()); + + // adding sorted list to an empty list + list.addAll(LongList.of(1, 2, 3)); + Assert.assertEquals(LongList.of(1, 2, 3), list); + Assert.assertTrue(list.isSorted()); + + // add empty list to a sorted list + list.addAll(LongList.of()); + Assert.assertEquals(LongList.of(1, 2, 3), list); + Assert.assertTrue(list.isSorted()); + + // adding sorted list to a sorted list so that the list stays sorted + list.addAll(LongList.of(3, 4, 5)); + Assert.assertEquals(LongList.of(1, 2, 3, 3, 4, 5), list); + Assert.assertTrue(list.isSorted()); + + // adding sorted list to a sorted list, but the new list is not sorted + list.clear(); + list.addAll(LongList.of(1, 2, 3)); + list.addAll(LongList.of(0)); + Assert.assertEquals(LongList.of(1, 2, 3, 0), list); + Assert.assertFalse(list.isSorted()); + + // adding unsorted list to a sorted list + list.clear(); + list.addAll(LongList.of(1, 2, 3)); + list.addAll(LongList.of(6, 5, 4)); + Assert.assertEquals(LongList.of(1, 2, 3, 6, 5, 4), list); + Assert.assertFalse(list.isSorted()); + + // adding unsorted list to an empty list + list.clear(); + list.addAll(LongList.of(3, 2, 1)); + Assert.assertEquals(LongList.of(3, 2, 1), list); + Assert.assertFalse(list.isSorted()); + + // adding sorted list to an unsorted list + list.clear(); + list.addAll(LongList.of(3, 2, 1)); + list.addAll(LongList.of(1, 2, 3)); + Assert.assertEquals(LongList.of(3, 2, 1, 1, 2, 3), list); + Assert.assertFalse(list.isSorted()); + + } + + @Test + public void testGetArray() { + final LongList list = new LongList(); + + final int size = 100; + final long[] longs = ThreadLocalRandom.current().longs(size).toArray(); + + list.addAll(longs); + + final int length = 20; + final int from = 10; + final long[] actualLongs = list.get(from, length); + + for (int i = 0; i < length; i++) { + Assert.assertEquals(actualLongs[i], list.get(from + i)); + Assert.assertEquals(actualLongs[i], longs[from + i]); + } + } + + @Test + public void testGetOutOfBounds() { + final LongList list = new LongList(); + + list.addAll(0, 1, 2, 3); + + try { + list.get(-1); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + try { + list.get(4); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testGetArrayOutOfBounds() { + final LongList list = new LongList(); + + list.addAll(0, 1, 2, 3); + + try { + list.get(-1, 1); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + try { + list.get(1, -1); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + try { + list.get(3, 2); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + try { + list.get(4, 1); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testToArrayEmptyLIst() { + final LongList list = new LongList(); + + final long[] actual = list.toArray(); + Assert.assertArrayEquals(actual, new long[0]); + } + + @Test + public void testToArray() { + final LongList list = new LongList(); + list.addAll(1, 2, 3, 4, 5, 6); + + { + final long[] input = new long[1]; + final long[] actual = list.toArray(input); + // input is too short -> new array returned + Assert.assertNotSame(input, actual); + Assert.assertArrayEquals(list.toArray(), actual); + } + + { + final long[] input = new long[list.size()]; + final long[] actual = list.toArray(input); + // input fits exactly -> input returned + Assert.assertSame(input, actual); + Assert.assertArrayEquals(list.toArray(), actual); + } + + { + final long[] input = new long[list.size() + 1]; + final long[] expected = { 1, 2, 3, 4, 5, 6, 0 }; + final long[] actual = list.toArray(input); + // input too big -> input returned + Assert.assertSame(input, actual); + Assert.assertArrayEquals(expected, actual); + } + } + + @Test + public void testToArrayWithEmptyList() { + final LongList list = new LongList(); + + { + final long[] input = new long[0]; + final long[] actual = list.toArray(input); + // input fits exactly -> input returned + Assert.assertSame(input, actual); + Assert.assertArrayEquals(list.toArray(), actual); + } + + { + final long[] input = new long[list.size() + 1]; + final long[] expected = { 0 }; + final long[] actual = list.toArray(input); + // input too big -> input returned + Assert.assertSame(input, actual); + Assert.assertArrayEquals(expected, actual); + } + } + + @Test + public void testRemove() { + final LongList list = new LongList(); + + final int size = 10; + final long[] ints = new long[size]; + for (int i = 0; i < size; i++) { + ints[i] = i; + } + + list.addAll(ints); + + // remove nothing + list.remove(2, 2); + Assert.assertArrayEquals(ints, list.toArray()); + + // remove the last element + list.remove(9, 10); + final long[] expectedA = removeElements(ints, 9); + Assert.assertArrayEquals(expectedA, list.toArray()); + + // remove the first element + list.remove(0, 1); + final long[] expectedB = removeElements(expectedA, 0); + Assert.assertArrayEquals(expectedB, list.toArray()); + + // remove single element in the middle + list.remove(3, 4); + final long[] expectedC = removeElements(expectedB, 3); + Assert.assertArrayEquals(expectedC, list.toArray()); + + // remove several elements in the middle + list.remove(2, 5); + final long[] expectedD = removeElements(expectedC, 2, 3, 4); + Assert.assertArrayEquals(expectedD, list.toArray()); + + // remove all elements + list.remove(0, 4); + final long[] expectedE = removeElements(expectedD, 0, 1, 2, 3); + Assert.assertArrayEquals(expectedE, list.toArray()); + } + + @Test + public void testRemoveOutOfRange() { + final LongList list = new LongList(); + + list.addAll(0, 1, 2, 3); + + try { + list.remove(-1, 1); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + try { + list.remove(1, 0); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + try { + list.remove(3, 5); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + try { + list.remove(4, 5); + Assert.fail(); + } catch (final IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testTrim() { + final LongList list = new LongList(); + + list.addAll(0, 1, 2, 3); + + list.trim(); + Assert.assertEquals(4, list.getCapacity()); + } + + @Test + public void testTrimOnEmptyList() { + final LongList list = new LongList(); + + list.trim(); + Assert.assertEquals(0, list.getCapacity()); + } + + @Test + public void testClear() { + final LongList list = LongList.of(2, 0, 1); // unsorted list + list.clear(); + + Assert.assertEquals(0, list.size()); + Assert.assertEquals(0, list.getCapacity()); + Assert.assertTrue(list.isSorted()); + } + + @SuppressWarnings("unlikely-arg-type") + @Test + public void testHashCodeEquals() { + final LongList a = new LongList(); + final LongList b = new LongList(); + + // on empty lists + Assert.assertTrue(a.equals(b)); + Assert.assertEquals(a.hashCode(), b.hashCode()); + + // on equal lists + a.addAll(1, 2, 3, 4); + b.addAll(1, 2, 3, 4); + Assert.assertTrue(a.equals(b)); + Assert.assertEquals(a.hashCode(), b.hashCode()); + + // trim only one of them + // we want to compare the actual content + a.trim(); + Assert.assertTrue(a.equals(b)); + Assert.assertEquals(a.hashCode(), b.hashCode()); + + // change one value + a.remove(2, 3); + a.insert(2, 99); + Assert.assertFalse(a.equals(b)); + Assert.assertNotEquals(a.hashCode(), b.hashCode()); + + // have different length + a.add(66); + Assert.assertFalse(a.equals(b)); + Assert.assertNotEquals(a.hashCode(), b.hashCode()); + + // same object + Assert.assertTrue(a.equals(a)); + + // other is null + Assert.assertFalse(a.equals(null)); + + // equals with a different class + Assert.assertFalse(a.equals("not an LongList")); + + // sorted and unsorted lists with same length + final LongList sorted = LongList.of(1, 2, 3, 4); + final LongList unsorted = LongList.of(4, 3, 2, 1); + Assert.assertFalse(sorted.equals(unsorted)); + Assert.assertNotEquals(sorted.hashCode(), unsorted.hashCode()); + } + + @Test + public void testSort() { + final LongList list = new LongList(); + + list.addAll(4, 2, 3, 1); + list.sort(); + + final long[] expected = new long[] { 1, 2, 3, 4 }; + Assert.assertArrayEquals(expected, list.toArray()); + } + + @Test + public void testSortParallel() { + final LongList list = new LongList(); + + list.addAll(4, 2, 3, 1); + list.parallelSort(); + + final long[] expected = new long[] { 1, 2, 3, 4 }; + Assert.assertArrayEquals(expected, list.toArray()); + } + + @Test + public void testShuffle() { + final LongList list = LongList.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + final LongList original = list.clone(); + final LongList original2 = list.clone(); + + final Random random = new Random(1); + list.shuffle(random); + + Assert.assertEquals(original.size(), list.size()); + + original.removeAll(list); + Assert.assertTrue("original minus shuffled list: " + original, original.isEmpty()); + + list.removeAll(original2); + Assert.assertTrue("shuffled list minus original: " + list, list.isEmpty()); + } + + @Test + public void testShuffle_sortOfEmptyElementList() { + final LongList list = LongList.of(); + list.shuffle(); + + Assert.assertTrue("an empty list is sorted", list.isSorted()); + } + + @Test + public void testShuffle_sortOfOneElementList() { + final LongList list = LongList.of(1); + list.shuffle(); + + Assert.assertTrue("a shuffled list of size 1 is sorted", list.isSorted()); + } + + @Test + public void testShuffle_sortOfListWithIdenticalElements() { + final LongList list = LongList.of(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); + list.shuffle(); + + Assert.assertTrue("a shuffled list with identical elements is sorted", list.isSorted()); + } + + @Test + public void testShuffle_sortOfThreeElementList() { + final LongList list = LongList.of(2, 3, 1); + + int sorted = 0; + int unsorted = 0; + for (int i = 0; i < 20; i++) { + final Random random = new Random(i); + list.shuffle(random); + + sorted += LongList.of(1, 2, 3).equals(list) ? 1 : 0; + unsorted += LongList.of(1, 2, 3).equals(list) ? 0 : 1; + + Assert.assertEquals(LongList.of(1, 2, 3).equals(list), list.isSorted()); + } + + Assert.assertTrue(sorted > 0); + Assert.assertTrue(unsorted > 0); + } + + private long[] removeElements(final long[] data, final int... removedIndices) { + final long[] result = new long[data.length - removedIndices.length]; + final List blacklist = Arrays.stream(removedIndices).boxed().collect(Collectors.toList()); + + int j = 0; + for (int i = 0; i < data.length; i++) { + if (!blacklist.contains(i)) { + result[j] = data[i]; + j++; + } + } + + return result; + } + + @Test + public void testSerialize() throws IOException, ClassNotFoundException { + final LongList emptyList = new LongList(0); + internalSerializeTest(emptyList); + + final LongList emptyListWithCapacity = new LongList(10); + internalSerializeTest(emptyListWithCapacity); + + final LongList threeElements = new LongList(10000); + threeElements.addAll(1, 2, 3); + internalSerializeTest(threeElements); + + final LongList trimmedList = new LongList(10000); + trimmedList.addAll(1, 2, 3); + trimmedList.trim(); + internalSerializeTest(trimmedList); + } + + private void internalSerializeTest(final LongList list) throws IOException, ClassNotFoundException { + final ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); + try (final ObjectOutputStream out = new ObjectOutputStream(byteBuffer)) { + out.writeObject(list); + } + + try (ByteArrayInputStream byteInputStream = new ByteArrayInputStream(byteBuffer.toByteArray()); + ObjectInputStream in = new ObjectInputStream(byteInputStream)) { + final LongList actual = (LongList) in.readObject(); + + Assert.assertEquals(list, actual); + + Assert.assertEquals( + "The capacity of the deserialized list. Should be equal to the number of inserted " + + "values, because we don't want to serialize the unused part of the array", + list.size(), actual.getCapacity()); + } + } + + @Test + public void testClone() { + final LongList list = new LongList(); + list.addAll(1, 2); + + final LongList clone = list.clone(); + + Assert.assertEquals(list, clone); + + list.set(1, 0); + Assert.assertNotEquals(list, clone); + } + + @Test + public void testCloneEmptyList() { + final LongList list = new LongList(); + + final LongList clone = list.clone(); + Assert.assertEquals(list, clone); + } + + @Test + public void testClonePreservesSortedFlagOnUnsortedList() { + final LongList list = LongList.of(3, 2, 1); + + final LongList clone = list.clone(); + Assert.assertEquals(list.isSorted(), clone.isSorted()); + } + + @Test + public void testClonePreservesSortedFlagOnSortedList() { + final LongList list = LongList.of(1, 2, 3); + + final LongList clone = list.clone(); + Assert.assertEquals(list.isSorted(), clone.isSorted()); + } + + @Test + public void testToString() { + Assert.assertEquals("[]", new LongList().toString()); + + final LongList list = new LongList(); + list.addAll(-2, -1, 0, 1, 2); + Assert.assertEquals("[-2, -1, 0, 1, 2]", list.toString()); + Assert.assertEquals("same result as Arrays.toString()", Arrays.toString(list.toArray()), list.toString()); + } + + @Test + public void testSequentialStream() { + + { + final LongList list = new LongList(); + list.addAll(0, 1, 2, 3, 4, 5, 6); + final LongStream stream = list.stream(); + Assert.assertEquals(list.size(), stream.count()); + } + { + final LongList list = new LongList(); + list.addAll(0, 1, 2, 3, 4, 5); + final LongStream stream = list.stream(); + Assert.assertEquals(15, stream.sum()); + } + { + final LongList emptyList = new LongList(); + Assert.assertEquals(0, emptyList.stream().count()); + } + } + + @Test + public void testParallelStream() { + final LongList list = new LongList(); + + final int size = 1000; + final long[] ints = new long[size]; + for (int i = 0; i < size; i++) { + ints[i] = i; + } + + list.addAll(ints); + + final ConcurrentLinkedQueue processingOrder = new ConcurrentLinkedQueue<>(); + final List actualList = list.parallelStream()// + .peek(e -> processingOrder.add(e))// + .boxed()// + .collect(Collectors.toList()); + + Assert.assertEquals("should be sequential, when using collect", actualList.toString(), list.toString()); + Assert.assertNotEquals("should use parallelism during computation", processingOrder.toString(), + list.toString()); + } + + @Test + public void testIndexOfOnSortedList() { + final LongList list = new LongList(); + Assert.assertEquals(-1, list.indexOf(0)); + + list.add(1); + Assert.assertEquals(-1, list.indexOf(0)); + Assert.assertEquals(0, list.indexOf(1)); + + list.add(2); + Assert.assertEquals(-1, list.indexOf(0)); + Assert.assertEquals(0, list.indexOf(1)); + Assert.assertEquals(1, list.indexOf(2)); + } + + @Test + public void testIndexOfOnSortedListReturnsFirstMatch() { + final LongList list = LongList.of(0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4); + Assert.assertEquals(2, list.indexOf(2)); + Assert.assertEquals(4, list.indexOf(2, 4)); + } + + @Test + public void testIndexOfOnUnsortedList() { + final LongList list = LongList.of(2, 0, 1); + Assert.assertEquals(1, list.indexOf(0)); + + Assert.assertEquals(0, list.indexOf(2)); + Assert.assertEquals(2, list.indexOf(1)); + Assert.assertEquals(-1, list.indexOf(3)); + } + + @Test + public void testIndexOfWithOffsetOnSortedList() { + final LongList list = new LongList(); + list.addAll(1, 1, 2, 3, 3); + Assert.assertEquals(0, list.indexOf(1, 0)); + Assert.assertEquals(1, list.indexOf(1, 1)); + Assert.assertEquals(2, list.indexOf(2, 2)); + Assert.assertEquals(-1, list.indexOf(2, 3)); + Assert.assertEquals(3, list.indexOf(3, 2)); + } + + @Test + public void testIndexOfWithOffsetOnSortedListWithOffsetOutOfRange() { + final LongList list = new LongList(); + list.addAll(1); + Assert.assertEquals(0, list.indexOf(1, -1)); + Assert.assertEquals(-1, list.indexOf(1, list.size())); + } + + @Test + public void testIndexOfWithOffsetOnUnsortedList() { + final LongList list = new LongList(); + list.addAll(0, 2, 0, 2); + Assert.assertEquals(1, list.indexOf(2, 0)); + Assert.assertEquals(1, list.indexOf(2, 1)); + Assert.assertEquals(3, list.indexOf(2, 2)); + Assert.assertEquals(3, list.indexOf(2, 3)); + Assert.assertEquals(-1, list.indexOf(2, 4)); + + // indexed returned by indexOf() are consistent with get() + Assert.assertEquals(list.get(list.indexOf(2, 0)), 2); + Assert.assertEquals(list.get(list.indexOf(2, 1)), 2); + Assert.assertEquals(list.get(list.indexOf(2, 2)), 2); + Assert.assertEquals(list.get(list.indexOf(2, 3)), 2); + } + + @Test + public void testLastIndexOfWithOffsetOnEmptyList() { + final LongList list = LongList.of(); + + Assert.assertEquals(-1, list.lastIndexOf(3, list.size())); + } + + @Test + public void testLastIndexOfWithOffsetOnSortedList() { + final LongList list = LongList.of(1, 2, 2, 3, 4, 5, 6, 7, 8, 9); + + Assert.assertEquals(3, list.lastIndexOf(3, list.size())); + Assert.assertEquals(2, list.lastIndexOf(2, list.size())); + Assert.assertEquals(0, list.lastIndexOf(1, list.size())); + + Assert.assertEquals(2, list.lastIndexOf(2, 2)); // fromIndex == result + Assert.assertEquals(1, list.lastIndexOf(2, 1)); // fromIndex == result && the next element would be a hit + + Assert.assertEquals(0, list.lastIndexOf(1, 0)); // fromIndex 0; found + Assert.assertEquals(-1, list.lastIndexOf(99, 0)); // fromIndex 0; not found + Assert.assertEquals(-1, list.lastIndexOf(99, list.size())); // fromIndex larger than list && not found + } + + @Test + public void testLastIndexOfOnSortedListReturnsFirstMatch() { + final LongList list = LongList.of(0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4); + Assert.assertEquals(13, list.lastIndexOf(2)); + Assert.assertEquals(10, list.lastIndexOf(2, 10)); + } + + @Test + public void testLastIndexOfWithOffsetOnUnsortedList() { + final LongList list = LongList.of(1, 2, 3, 1, 2, 3); + + Assert.assertEquals(5, list.lastIndexOf(3, list.size())); + Assert.assertEquals(4, list.lastIndexOf(2, list.size())); + Assert.assertEquals(3, list.lastIndexOf(1, list.size())); + Assert.assertEquals(-1, list.lastIndexOf(99, list.size())); + Assert.assertEquals(-1, list.lastIndexOf(99, 0)); + + Assert.assertEquals(2, list.lastIndexOf(3, list.size() - 2)); + Assert.assertEquals(4, list.lastIndexOf(2, list.size() - 1)); + Assert.assertEquals(1, list.lastIndexOf(2, list.lastIndexOf(2) - 1)); + } + + @Test + public void replaceAll() { + final LongList list = new LongList(); + list.addAll(1, 2, 3); + + list.replaceAll(i -> i * 2); + Assert.assertArrayEquals(new long[] { 2, 4, 6 }, list.toArray()); + } + + @Test + public void testReplaceAllOnEmptyList() { + final LongList list = new LongList(); + + list.replaceAll(i -> i * 3); + Assert.assertArrayEquals(new long[0], list.toArray()); + } + + @Test + public void testRemoveAll() { + final LongList list = LongList.of(-2, -1, 0, 1, 2, 3, 4, 5, 6); + final LongList remove = LongList.of(-1, 2, 4, 5); + + list.removeAll(remove); + Assert.assertArrayEquals(new long[] { -2, 0, 1, 3, 6 }, list.toArray()); + Assert.assertArrayEquals(new long[] { -1, 2, 4, 5 }, remove.toArray()); + } + + @Test + public void testRemoveEmptyList() { + final LongList list = LongList.of(1, 2, 3, 4, 5, 6); + final LongList remove = new LongList(); + + list.removeAll(remove); + Assert.assertArrayEquals(new long[] { 1, 2, 3, 4, 5, 6 }, list.toArray()); + Assert.assertArrayEquals(new long[] {}, remove.toArray()); + } + + @Test + public void testRemoveAllFromEmptyList() { + final LongList list = new LongList(); + final LongList remove = LongList.of(1); + + list.removeAll(remove); + Assert.assertArrayEquals(new long[] {}, list.toArray()); + Assert.assertEquals(0, list.size()); + } + + @Test + public void testRetainAll() { + final LongList list = LongList.of(-2, -1, 0, 1, 2, 3, 4, 5, 6); + final LongList retain = LongList.of(-1, 2, 4, 5, 99); + + list.retainAll(retain); + Assert.assertArrayEquals(new long[] { -1, 2, 4, 5 }, list.toArray()); + Assert.assertArrayEquals(new long[] { -1, 2, 4, 5, 99 }, retain.toArray()); + } + + @Test + public void testRetainAllOnEmptyList() { + final LongList list = LongList.of(); + final LongList retain = LongList.of(-1, 2, 4, 5, 99); + + list.retainAll(retain); + Assert.assertArrayEquals(new long[] {}, list.toArray()); + Assert.assertArrayEquals(new long[] { -1, 2, 4, 5, 99 }, retain.toArray()); + } + + @Test + public void testRetainAllFromEmptyList() { + final LongList list = LongList.of(-2, -1, 0, 1, 2); + final LongList retain = LongList.of(); + + list.retainAll(retain); + Assert.assertArrayEquals(new long[] {}, list.toArray()); + Assert.assertArrayEquals(new long[] {}, retain.toArray()); + } + + @Test + public void testRemoveIf() { + final LongList list = LongList.of(1, 2, 3, 4, 5, 6); + + list.removeIf((value, index) -> value % 2 == 0); + Assert.assertArrayEquals(new long[] { 1, 3, 5 }, list.toArray()); + } + + @Test + public void testRemoveIfNegationOfPredicate() { + final LongList list = LongList.of(1, 2, 3, 4, 5, 6); + + final LongPredicate predicate = (value, index) -> value % 2 == 0; + list.removeIf(predicate.negate()); + Assert.assertArrayEquals(new long[] { 2, 4, 6 }, list.toArray()); + } + + @Test + public void testRemoveIfWithAndCombinedPredicates() { + final LongList list = LongList.of(1, 2, 3, 4, 5, 6); + + final LongPredicate predicateA = (value, index) -> value % 2 == 0; + final LongPredicate predicateB = (value, index) -> value == 3 || value == 4; + list.removeIf(predicateA.and(predicateB)); + Assert.assertArrayEquals(new long[] { 1, 2, 3, 5, 6 }, list.toArray()); + } + + @Test + public void testRemoveIfWithOrCombinedPredicates() { + final LongList list = LongList.of(1, 2, 3, 4, 5, 6); + + final LongPredicate predicateA = (value, index) -> value % 2 == 0; + final LongPredicate predicateB = (value, index) -> value == 3 || value == 4; + list.removeIf(predicateA.or(predicateB)); + Assert.assertArrayEquals(new long[] { 1, 5 }, list.toArray()); + } + + @Test + public void testRemoveIfOnEmptyList() { + final LongList list = LongList.of(); + + list.removeIf((value, index) -> false); + Assert.assertArrayEquals(new long[] {}, list.toArray()); + } + + @Test + public void testSortedFlagSort() { + + Assert.assertTrue("empty list is sorted", new LongList().isSorted()); + + final LongList list = new LongList(); + list.addAll(2, 0, 1); + list.sort(); + Assert.assertTrue("is sorted after calling sort", list.isSorted()); + } + + @Test + public void testSortedFlagEmptyList() { + + Assert.assertTrue("empty list is sorted", new LongList().isSorted()); + + final LongList list = new LongList(); + list.addAll(2, 0, 1); + list.remove(0, list.size()); + Assert.assertTrue("unsorted list initialized by addAll", list.isSorted()); + } + + @Test + public void testSortedFlagAdd() { + + final LongList list = new LongList(); + list.add(1); + Assert.assertTrue("[1]", list.isSorted()); + list.add(2); + Assert.assertTrue("[1,2]", list.isSorted()); + list.add(2); + Assert.assertTrue("[1,2,2]", list.isSorted()); + list.add(1); + Assert.assertFalse("[1,2,2,1]", list.isSorted()); + list.add(1); + Assert.assertFalse("[1,2,2,1,1]", list.isSorted()); + } + + @Test + public void testSortedFlagAddAll() { + + { + final LongList list = new LongList(); + list.addAll(2, 0); + Assert.assertFalse("unsorted list initialized by addAll", list.isSorted()); + } + + { + final LongList list = new LongList(); + list.addAll(1); + Assert.assertTrue("list with one element is sorted", list.isSorted()); + } + + { + final LongList list = new LongList(); + list.addAll(1, 1); + Assert.assertTrue("list with all the same elements is sorted", list.isSorted()); + } + } + + @Test + public void testSortedFlagInsert() { + + /* + * tests that result in a sorted list + */ + { + final LongList list = new LongList(); + list.insert(0, 1, 2, 3); + Assert.assertTrue("insert sorted values into empty list", list.isSorted()); + } + + { + final LongList list = LongList.of(1, 1); + list.insert(0, 2, 2); // -> [2,2,1,1] + Assert.assertFalse("insert before: unsorted", list.isSorted()); + } + { + final LongList list = LongList.of(3, 3); + list.insert(0, 2, 2); // -> [2,2,3,3] + Assert.assertTrue("insert sorted values before: sorted", list.isSorted()); + } + + { + final LongList list = LongList.of(4, 4); + list.insert(2, 2, 2); // -> [4,4,2,2] + Assert.assertFalse("insert sorted values at end: unsorted", list.isSorted()); + } + { + final LongList list = LongList.of(1, 1); + list.insert(2, 2, 2); // -> [1,1,2,2] + Assert.assertTrue("insert sorted values at end: sorted", list.isSorted()); + } + + { + final LongList list = LongList.of(1, 4); + list.insert(1, 2, 2); // -> [1,2,2,4] + Assert.assertTrue("insert sorted values in middle: sorted", list.isSorted()); + } + + /* + * tests that result in an unsorted list + */ + { + final LongList list = LongList.of(1, 4); + list.insert(1, 5); // -> [1,5,4] + Assert.assertFalse("insert sorted values in middle: unsorted", list.isSorted()); + } + { + final LongList list = LongList.of(1, 4); + list.insert(1, 6, 6); // -> [1,6,6,4] + Assert.assertFalse("insert sorted values in middle: unsorted", list.isSorted()); + } + + { + final LongList list = LongList.of(1, 4); + list.insert(1, 0); // -> [1,0,4] + Assert.assertFalse("insert sorted values in middle: unsorted", list.isSorted()); + } + { + final LongList list = LongList.of(1, 4); + list.insert(1, 0, 0); // -> [1,0,0,4] + Assert.assertFalse("insert sorted values in middle: unsorted", list.isSorted()); + } + + { + final LongList list = LongList.of(3, 4); + list.insert(0, 2, 1); // -> [2,1,3,4] + Assert.assertFalse("insert unsorted at begin: unsorted", list.isSorted()); + } + { + final LongList list = LongList.of(1, 4); + list.insert(1, 2, 1); // -> [1,2,1,4] + Assert.assertFalse("insert unsorted middle: unsorted", list.isSorted()); + } + { + final LongList list = LongList.of(3, 4); + list.insert(2, 6, 5); // -> [13,4,6,5] + Assert.assertFalse("insert unsorted at end: unsorted", list.isSorted()); + } + + } + + @Test + public void testSortedFlagSetByIndex() { + + { + final LongList list = LongList.of(0, 1, 2); + list.set(0, -1); + Assert.assertTrue("set first element: [-1,1,2] sorted", list.isSorted()); + list.set(0, 1); + Assert.assertTrue("set first element: [1,1,2] sorted", list.isSorted()); + list.set(0, 2); + Assert.assertFalse("set first element: [2,1,2] not sorted", list.isSorted()); + } + + { + final LongList sortedList = LongList.of(0, 2, 4); + sortedList.set(1, 3); + Assert.assertTrue("set middle element: [0,3,4] sorteed", sortedList.isSorted()); + sortedList.set(1, 4); + Assert.assertTrue("set middle element: [0,4,4] sorteed", sortedList.isSorted()); + sortedList.set(1, 0); + Assert.assertTrue("set middle element: [0,0,4] sorteed", sortedList.isSorted()); + sortedList.set(1, 5); + Assert.assertFalse("set middle element: [0,5,4] not sorteed", sortedList.isSorted()); + } + + { + final LongList sortedList = LongList.of(0, 1, 2); + sortedList.set(2, 3); + Assert.assertTrue("set last element: [0,1,3] sorteed", sortedList.isSorted()); + sortedList.set(2, 1); + Assert.assertTrue("set last element: [0,1,1] sorteed", sortedList.isSorted()); + sortedList.set(2, 0); + Assert.assertFalse("set last element: [0,1,0] not sorteed", sortedList.isSorted()); + } + + { + final LongList sortedList = LongList.of(0, 1, 2); + sortedList.set(2, 0); + sortedList.set(2, 2); + Assert.assertFalse("unsorted lists stay unsorted", sortedList.isSorted()); + } + } + + @Test + public void testSortedFlagRemove() { + + final LongList list = LongList.of(4, 3, 2, 1); + list.remove(0, 2); // removes 4,3 + Assert.assertFalse("unsorted list with two elements is not sorted", list.isSorted()); + + list.remove(0, 1); + Assert.assertTrue("unsorted list with one element becomes sorted", list.isSorted()); + + list.add(-1); // make list unsorted again + list.remove(0, 2); // remove both elements + Assert.assertTrue("unsorted list with no elements becomes sorted", list.isSorted()); + } + + @Test + public void testSortedFlagRemove_unsortedBecomesSorted_emptyList() { + + final LongList list = LongList.of(4, 3, 2, 1); + list.remove(0, 4); // removes all + Assert.assertTrue("empty list is sorted", list.isSorted()); + } + + @Test + public void testSortedFlagRemove_unsortedBecomesSorted_oneElement() { + + final LongList list = LongList.of(4, 3, 2, 1); + list.remove(1, 4); // removes 3,2,1 + Assert.assertTrue("list with one element is", list.isSorted()); + } + + @Test + public void testSortedFlagRemove_unsortedBecomesSorted() { + + final LongList list = LongList.of(1, 2, 777, 4, 5); + list.remove(2, 3); // removes 777 + Assert.assertTrue("list is sorted after remove", list.isSorted()); + } + + @Test + public void testSortedFlagRemoveAll() { + + final LongList list = LongList.of(4, 3, 2, 1); + list.removeAll(LongList.of(4, 3)); + Assert.assertFalse("unsorted list with two elements is not sorted", list.isSorted()); + + list.removeAll(LongList.of(2)); + Assert.assertTrue("unsorted list with one element becomes sorted", list.isSorted()); + + list.add(-1); // make list unsorted again + list.removeAll(LongList.of(-1, 2)); // remove both elements + Assert.assertTrue("unsorted list with no elements becomes sorted", list.isSorted()); + } + + @Test + public void testSortedFlagRemoveIf() { + + final LongList list = LongList.of(4, 3, 2, 1); + list.removeIf((value, index) -> value >= 3); // removes 3 and 4 + Assert.assertFalse("unsorted list with two elements is not sorted", list.isSorted()); + + list.removeIf((value, index) -> value >= 2); // removes 2 + Assert.assertTrue("unsorted list with one element becomes sorted", list.isSorted()); + + list.add(-1); // make list unsorted again + list.removeIf((value, index) -> true); // remove both elements + Assert.assertTrue("unsorted list with no elements becomes sorted", list.isSorted()); + } + + @Test + public void testSortedFlagRemoveIf_unsortedBecomesSorted_emptyAfterRemove() { + + final LongList list = LongList.of(1, 3, 2); + list.removeIf((value, index) -> true); // makes the list sorted + Assert.assertTrue("list is empty", list.isEmpty()); + Assert.assertTrue("empty list is sorted", list.isSorted()); + } + + @Test + public void testSortedFlagRemoveIf_unsortedBecomesSorted_oneElementAfterRemove() { + + final LongList list = LongList.of(1, 3, 2); + list.removeIf((value, index) -> value > 1); // makes the list sorted + Assert.assertTrue("list with one element is sorted", list.isSorted()); + } + + @Test + public void testSortedFlagRemoveIf_unsortedBecomesSorted() { + + final LongList list = LongList.of(1, 2, 3, 777, 4, 5); + list.removeIf((value, index) -> value == 777); // makes the list sorted + Assert.assertTrue("unsorted list becomes sorted", list.isSorted()); + } + + @Test + public void testSortedFlagReplace() { + + final LongList list = LongList.of(1, 2, 3, 4); + list.replaceAll(v -> v >= 3 ? 2 : v); // replace 3 and 4 with 2 -> [1,2,2,2] + Assert.assertTrue("sorted list still sorted after replace", list.isSorted()); + + list.replaceAll(v -> v == 1 ? 3 : v); // replace 1 with 3 -> [3,2,2,2] + Assert.assertFalse("sorted list becomes unsorted after replace", list.isSorted()); + + list.replaceAll(v -> 2); // replace all with 2 -> [2,2,2,2] + Assert.assertFalse("unsorted list stays unsorted", list.isSorted()); + } + + @Test + public void testIntersectionSortedLists() { + { + final LongList a = LongList.of(0, 1, 2, 3, 4); + final LongList b = LongList.of(2, 4, 5); + final LongList actual = LongList.intersection(a, b); + Assert.assertEquals(LongList.of(2, 4), actual); + } + + { + final LongList a = LongList.of(0, 2, 4, 6); + final LongList b = LongList.of(3, 5); + final LongList actual = LongList.intersection(a, b); + Assert.assertEquals(LongList.of(), actual); + } + + /* + * duplicate elements are removed in the result + */ + { + final LongList a = LongList.of(3, 3, 3); + final LongList b = LongList.of(3, 3); + final LongList actual = LongList.intersection(a, b); + Assert.assertEquals(LongList.of(3), actual); + } + { + final LongList a = LongList.of(4, 4); + final LongList b = LongList.of(4, 4, 4); + final LongList actual = LongList.intersection(a, b); + Assert.assertEquals(LongList.of(4), actual); + } + } + + @Test + public void testIntersectionUnsortedLists() { + { + final LongList a = LongList.of(0, 1, 2, 3, 4); + final LongList b = LongList.of(2, 4, 5); + a.shuffle(); + b.shuffle(); + final LongList actual = LongList.intersection(a, b); + actual.sort(); + Assert.assertEquals(LongList.of(2, 4), actual); + } + + /* + * duplicate elements are removed in the result + */ + { + final LongList a = LongList.of(3, 5, 3, 3, 1); + final LongList b = LongList.of(2, 3, 3); + final LongList actual = LongList.intersection(a, b); + Assert.assertEquals(LongList.of(3), actual); + } + { + final LongList a = LongList.of(1, 4); + final LongList b = LongList.of(4, 3, 4, 4, 2); + final LongList actual = LongList.intersection(a, b); + Assert.assertEquals(LongList.of(4), actual); + } + } + + @Test + public void testUnionSortedLists_emptyLists() { + final LongList a = LongList.of(); + final LongList b = LongList.of(); + + Assert.assertEquals(LongList.of(), LongList.union(a, b)); + Assert.assertEquals(LongList.union(a, b), LongList.union(b, a)); + } + + @Test + public void testUnionSortedLists_uniqueValues() { + final LongList a = LongList.of(0, 1, 3, 4); + final LongList b = LongList.of(2, 4, 5); + + final LongList actual = LongList.union(a, b); + Assert.assertEquals(LongList.of(0, 1, 2, 3, 4, 5), actual); + Assert.assertEquals(LongList.union(a, b), LongList.union(b, a)); + } + + @Test + public void testUnionSortedLists_duplicateValues_inMiddleOfListA() { + final LongList a = LongList.of(1, 2, 2, 3); + final LongList b = LongList.of(1, 3); + + final LongList actual = LongList.union(a, b); + Assert.assertEquals(LongList.of(1, 2, 3), actual); + Assert.assertEquals(LongList.union(a, b), LongList.union(b, a)); + } + + @Test + public void testUnionSortedLists_duplicateValues_inMiddleOfBothLists() { + final LongList a = LongList.of(1, 2, 2, 3); + final LongList b = LongList.of(1, 2, 2, 4); + + final LongList actual = LongList.union(a, b); + Assert.assertEquals(LongList.of(1, 2, 3, 4), actual); + Assert.assertEquals(LongList.union(a, b), LongList.union(b, a)); + } + + @Test + public void testUnionSortedLists_duplicateValues_atEndOfListA_whenHighestValueInBIsSmaller() { + final LongList a = LongList.of(); + final LongList b = LongList.of(2, 2); + + final LongList actual = LongList.union(a, b); + Assert.assertEquals(LongList.of(2), actual); + Assert.assertEquals(LongList.union(a, b), LongList.union(b, a)); + } + + @Test + public void testUnionUnsortedLists() { + final LongList a = LongList.of(1, 0, 3, 4); + final LongList b = LongList.of(2, 5, 4); + + final LongList actual = LongList.union(a, b); + Assert.assertEquals(LongList.of(0, 1, 2, 3, 4, 5), actual); + Assert.assertEquals(LongList.union(a, b), LongList.union(b, a)); + } + + @Test + public void testUnionUnsortedLists_oneListIsSorted() { + final LongList a = LongList.of(1, 2, 3); + final LongList b = LongList.of(2, 5, 4); + + final LongList actual = LongList.union(a, b); + Assert.assertEquals(LongList.of(1, 2, 3, 4, 5), actual); + Assert.assertEquals(LongList.union(a, b), LongList.union(b, a)); + } +}