diff --git a/primitiveCollections/src/main/java/org/lucares/collections/IntList.java b/primitiveCollections/src/main/java/org/lucares/collections/IntList.java index c052647..9fb4ebb 100644 --- a/primitiveCollections/src/main/java/org/lucares/collections/IntList.java +++ b/primitiveCollections/src/main/java/org/lucares/collections/IntList.java @@ -21,7 +21,14 @@ public final class IntList implements Serializable, Cloneable { // TODO add mod counts // TODO support sublists // TODO add lastIndexOf + // TODO check that methods like intersection/union work with big arrays. They + // should not try to allocate more memory MAX_ARRAY_SIZE // TODO remove bounds checks that are handled by java, e.g. negative indices + // TODO removeIf see ArrayList.removeIf. Cannot handle removeIf(i -> indexOf(i) + // != lastIndexOf(i)) on sorted lists + // TODO wrapper that reverses the order. This implies, that IntList becomes an + // interface, or it could not be final anymore. Being an interface would make it + // possible to have unmodifiable lists, too. private static final long serialVersionUID = 2622570032686034909L; @@ -81,9 +88,8 @@ public final class IntList implements Serializable, Cloneable { * if the specified {@link IntList} is null */ public IntList(final IntList intList) { - data = Arrays.copyOf(intList.data, intList.size()); - size = intList.size(); - sorted = intList.sorted; + data = EMPTY_ARRAY; + addAll(intList); } /** @@ -261,6 +267,36 @@ public final class IntList implements Serializable, Cloneable { 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 IntList 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 int highestOldValue = data[size - 1]; // size has still the old value + final int lowestNewValue = list.get(0); + sorted = highestOldValue <= lowestNewValue; + } + } + + size += list.size(); + } + /** * Removes elements from the list. *

@@ -344,7 +380,7 @@ public final class IntList implements Serializable, Cloneable { int insertPosition = 0; for (int i = 0; i < size; i++) { final int current = data[i]; - if (!predicate.test(current)) { + if (!predicate.test(current, i)) { // keep current element data[insertPosition] = current; insertPosition++; @@ -598,8 +634,8 @@ public final class IntList implements Serializable, Cloneable { } /** - * Returns the index of the first occurrence of {@code value} starting at the - * {@code offset}'s element, or -1 if it does not exist. + * Returns the index of the last occurrence of {@code value}, or -1 if it does + * not exist. *

* This method uses a binary search algorithm if the list is sorted. * @@ -610,6 +646,8 @@ public final class IntList implements Serializable, Cloneable { * @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 int value, final int offset) { @@ -617,7 +655,7 @@ public final class IntList implements Serializable, Cloneable { int result = -1; if (sorted) { int insertionPoint = Arrays.binarySearch(data, offset, size(), value); - while (insertionPoint > 0 && insertionPoint > offset && data[insertionPoint - 1] == value) { + while (insertionPoint > offset && data[insertionPoint - 1] == value) { insertionPoint--; } result = insertionPoint < 0 ? -1 : insertionPoint; @@ -760,12 +798,14 @@ public final class IntList implements Serializable, Cloneable { * 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(IntList)} for a method that modifies the list and keeps * duplicate values. *

- * If both lists are sorted, then the complexity is O(n+m), where n is the + * 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 complexity is O(n*log(m)), where n is the length + * 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 @@ -834,8 +874,8 @@ public final class IntList implements Serializable, Cloneable { } /** - * Implements an algorithm with O(n*log(m)), where n is the length of the - * shorter list and m the length of the longer list. + * 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 @@ -851,8 +891,6 @@ public final class IntList implements Serializable, Cloneable { if (aSize < bSize) { result = new IntList(Math.min(aSize, bSize)); - assert !a.isSorted() || !b.isSorted() : "at least one list must be unsorted"; - for (int l = 0; l < aSize; l++) { final int lv = a.get(l); @@ -870,4 +908,104 @@ public final class IntList implements Serializable, Cloneable { 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((n+m)*log(n+m)), where n is + * the length of the shorter list and m the length of the longer list. + * + * @param a + * the first list + * @param b + * the second list + * @return the union of both lists + */ + public static IntList union(final IntList a, final IntList b) { + final IntList result; + + if (a.isSorted() && b.isSorted()) { + result = unionSorted(a, b); + } else { + result = unionUnsorted(a, b); + } + return result; + } + + private static IntList unionSorted(final IntList a, final IntList b) { + final IntList result = new IntList(a.size() + b.size()); + + final int aSize = a.size(); + final int bSize = b.size(); + + int l = 0; + int r = 0; + + while (l < a.size() && r < b.size()) { + + final int lv = a.get(l); + final int 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 IntList unionUnsorted(final IntList a, final IntList b) { + // TODO use a more efficient algorithm. Especially the removeIf is too + // expensive, because of all the method calls + final IntList result; + result = new IntList(); + result.addAll(a); + result.addAll(b); + result.sort(); + result.removeIf((value, index) -> index > 0 && result.get(index) == result.get(index - 1)); + return result; + } } diff --git a/primitiveCollections/src/main/java/org/lucares/collections/IntPredicate.java b/primitiveCollections/src/main/java/org/lucares/collections/IntPredicate.java index 79d7d48..db8beaa 100644 --- a/primitiveCollections/src/main/java/org/lucares/collections/IntPredicate.java +++ b/primitiveCollections/src/main/java/org/lucares/collections/IntPredicate.java @@ -11,11 +11,13 @@ public interface IntPredicate { /** * Evaluates the predicate. * - * @param i - * the input argument + * @param value + * the value + * @param index + * the index in the list * @return {@code true} iff the input argument matches the predicate */ - boolean test(int i); + boolean test(int value, int index); /** * Returns a predicate that represents the logical AND of {@code this} and @@ -28,7 +30,7 @@ public interface IntPredicate { * if {@code other} is null */ default IntPredicate and(final IntPredicate other) { - return (t) -> test(t) && other.test(t); + return (value, index) -> test(value, index) && other.test(value, index); } /** @@ -42,7 +44,7 @@ public interface IntPredicate { * if {@code other} is null */ default IntPredicate or(final IntPredicate other) { - return (t) -> test(t) || other.test(t); + return (value, index) -> test(value, index) || other.test(value, index); } /** @@ -51,6 +53,6 @@ public interface IntPredicate { * @return the negation of {@code this} */ default IntPredicate negate() { - return (t) -> !test(t); + return (value, index) -> !test(value, index); } } diff --git a/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java b/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java index 8dcd967..8f64aa6 100644 --- a/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java +++ b/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java @@ -224,6 +224,64 @@ public class IntListTest { } } + @Test + public void testAddList() { + final IntList list = new IntList(); + + // adding empty list with no capacity + list.addAll(IntList.of()); + Assert.assertEquals(new IntList(), list); + Assert.assertTrue(list.isSorted()); + + // adding empty list with capacity 2 + list.addAll(new IntList(2)); + Assert.assertEquals(new IntList(), list); + Assert.assertTrue(list.isSorted()); + + // adding sorted list to an empty list + list.addAll(IntList.of(1, 2, 3)); + Assert.assertEquals(IntList.of(1, 2, 3), list); + Assert.assertTrue(list.isSorted()); + + // add empty list to a sorted list + list.addAll(IntList.of()); + Assert.assertEquals(IntList.of(1, 2, 3), list); + Assert.assertTrue(list.isSorted()); + + // adding sorted list to a sorted list so that the list stays sorted + list.addAll(IntList.of(3, 4, 5)); + Assert.assertEquals(IntList.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(IntList.of(1, 2, 3)); + list.addAll(IntList.of(0)); + Assert.assertEquals(IntList.of(1, 2, 3, 0), list); + Assert.assertFalse(list.isSorted()); + + // adding unsorted list to a sorted list + list.clear(); + list.addAll(IntList.of(1, 2, 3)); + list.addAll(IntList.of(6, 5, 4)); + Assert.assertEquals(IntList.of(1, 2, 3, 6, 5, 4), list); + Assert.assertFalse(list.isSorted()); + + // adding unsorted list to an empty list + list.clear(); + list.addAll(IntList.of(3, 2, 1)); + Assert.assertEquals(IntList.of(3, 2, 1), list); + Assert.assertFalse(list.isSorted()); + + // adding sorted list to an unsorted list + list.clear(); + list.addAll(IntList.of(3, 2, 1)); + list.addAll(IntList.of(1, 2, 3)); + Assert.assertEquals(IntList.of(3, 2, 1, 1, 2, 3), list); + Assert.assertFalse(list.isSorted()); + + } + @Test public void testGetArray() { final IntList list = new IntList(); @@ -806,7 +864,7 @@ public class IntListTest { public void testRemoveIf() { final IntList list = IntList.of(1, 2, 3, 4, 5, 6); - list.removeIf(i -> i % 2 == 0); + list.removeIf((value, index) -> value % 2 == 0); Assert.assertArrayEquals(new int[] { 1, 3, 5 }, list.toArray()); } @@ -814,7 +872,7 @@ public class IntListTest { public void testRemoveIfNegationOfPredicate() { final IntList list = IntList.of(1, 2, 3, 4, 5, 6); - final IntPredicate predicate = i -> i % 2 == 0; + final IntPredicate predicate = (value, index) -> value % 2 == 0; list.removeIf(predicate.negate()); Assert.assertArrayEquals(new int[] { 2, 4, 6 }, list.toArray()); } @@ -823,8 +881,8 @@ public class IntListTest { public void testRemoveIfWithAndCombinedPredicates() { final IntList list = IntList.of(1, 2, 3, 4, 5, 6); - final IntPredicate predicateA = i -> i % 2 == 0; - final IntPredicate predicateB = i -> i == 3 || i == 4; + final IntPredicate predicateA = (value, index) -> value % 2 == 0; + final IntPredicate predicateB = (value, index) -> value == 3 || value == 4; list.removeIf(predicateA.and(predicateB)); Assert.assertArrayEquals(new int[] { 1, 2, 3, 5, 6 }, list.toArray()); } @@ -833,8 +891,8 @@ public class IntListTest { public void testRemoveIfWithOrCombinedPredicates() { final IntList list = IntList.of(1, 2, 3, 4, 5, 6); - final IntPredicate predicateA = i -> i % 2 == 0; - final IntPredicate predicateB = i -> i == 3 || i == 4; + final IntPredicate predicateA = (value, index) -> value % 2 == 0; + final IntPredicate predicateB = (value, index) -> value == 3 || value == 4; list.removeIf(predicateA.or(predicateB)); Assert.assertArrayEquals(new int[] { 1, 5 }, list.toArray()); } @@ -843,7 +901,7 @@ public class IntListTest { public void testRemoveIfOnEmptyList() { final IntList list = IntList.of(); - list.removeIf(i -> false); + list.removeIf((value, index) -> false); Assert.assertArrayEquals(new int[] {}, list.toArray()); } @@ -1067,14 +1125,14 @@ public class IntListTest { public void testSortedFlagRemoveIf() { final IntList list = IntList.of(4, 3, 2, 1); - list.removeIf(v -> v >= 3); // removes 3 and 4 + list.removeIf((value, index) -> value >= 3); // removes 3 and 4 Assert.assertFalse("unsorted list with two elements is not sorted", list.isSorted()); - list.removeIf(v -> v >= 2); // removes 2 + 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(v -> true); // remove both elements + list.removeIf((value, index) -> true); // remove both elements Assert.assertTrue("unsorted list with no elements becomes sorted", list.isSorted()); } @@ -1153,4 +1211,73 @@ public class IntListTest { Assert.assertEquals(IntList.of(4), actual); } } + + @Test + public void testUnionSortedLists_emptyLists() { + final IntList a = IntList.of(); + final IntList b = IntList.of(); + + Assert.assertEquals(IntList.of(), IntList.union(a, b)); + Assert.assertEquals(IntList.union(a, b), IntList.union(b, a)); + } + + @Test + public void testUnionSortedLists_uniqueValues() { + final IntList a = IntList.of(0, 1, 3, 4); + final IntList b = IntList.of(2, 4, 5); + + final IntList actual = IntList.union(a, b); + Assert.assertEquals(IntList.of(0, 1, 2, 3, 4, 5), actual); + Assert.assertEquals(IntList.union(a, b), IntList.union(b, a)); + } + + @Test + public void testUnionSortedLists_duplicateValues_inMiddleOfListA() { + final IntList a = IntList.of(1, 2, 2, 3); + final IntList b = IntList.of(1, 3); + + final IntList actual = IntList.union(a, b); + Assert.assertEquals(IntList.of(1, 2, 3), actual); + Assert.assertEquals(IntList.union(a, b), IntList.union(b, a)); + } + + @Test + public void testUnionSortedLists_duplicateValues_inMiddleOfBothLists() { + final IntList a = IntList.of(1, 2, 2, 3); + final IntList b = IntList.of(1, 2, 2, 4); + + final IntList actual = IntList.union(a, b); + Assert.assertEquals(IntList.of(1, 2, 3, 4), actual); + Assert.assertEquals(IntList.union(a, b), IntList.union(b, a)); + } + + @Test + public void testUnionSortedLists_duplicateValues_atEndOfListA_whenHighestValueInBIsSmaller() { + final IntList a = IntList.of(); + final IntList b = IntList.of(2, 2); + + final IntList actual = IntList.union(a, b); + Assert.assertEquals(IntList.of(2), actual); + Assert.assertEquals(IntList.union(a, b), IntList.union(b, a)); + } + + @Test + public void testUnionUnsortedLists() { + final IntList a = IntList.of(1, 0, 3, 4); + final IntList b = IntList.of(2, 5, 4); + + final IntList actual = IntList.union(a, b); + Assert.assertEquals(IntList.of(0, 1, 2, 3, 4, 5), actual); + Assert.assertEquals(IntList.union(a, b), IntList.union(b, a)); + } + + @Test + public void testUnionUnsortedLists_oneListIsSorted() { + final IntList a = IntList.of(1, 2, 3); + final IntList b = IntList.of(2, 5, 4); + + final IntList actual = IntList.union(a, b); + Assert.assertEquals(IntList.of(1, 2, 3, 4, 5), actual); + Assert.assertEquals(IntList.union(a, b), IntList.union(b, a)); + } }