diff --git a/primitiveCollections/src/main/java/org/lucares/collections/IntList.java b/primitiveCollections/src/main/java/org/lucares/collections/IntList.java index 9fb4ebb..c1325d4 100644 --- a/primitiveCollections/src/main/java/org/lucares/collections/IntList.java +++ b/primitiveCollections/src/main/java/org/lucares/collections/IntList.java @@ -29,6 +29,9 @@ public final class IntList implements Serializable, Cloneable { // 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. + // TODO memory optimization: add private method ensureExactCapacity, that does + // not make the list 50% larger. This is useful for addAll or internal usage + // where we know the exact size. private static final long serialVersionUID = 2622570032686034909L; @@ -634,7 +637,7 @@ public final class IntList implements Serializable, Cloneable { } /** - * Returns the index of the last occurrence of {@code value}, or -1 if it does + * 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. @@ -642,7 +645,9 @@ public final class IntList implements Serializable, Cloneable { * @param value * the value * @param offset - * the 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 @@ -653,14 +658,74 @@ public final class IntList implements Serializable, Cloneable { public int indexOf(final int value, final int offset) { int result = -1; - if (sorted) { + 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 { - for (int i = offset; i < size; i++) { + 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 int 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 int 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; diff --git a/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java b/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java index 8f64aa6..ed26ed4 100644 --- a/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java +++ b/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java @@ -766,6 +766,14 @@ public class IntListTest { Assert.assertEquals(3, list.indexOf(3, 2)); } + @Test + public void testIndexOfWithOffsetOnSortedListWithOffsetOutOfRange() { + final IntList list = new IntList(); + list.addAll(1); + Assert.assertEquals(0, list.indexOf(1, -1)); + Assert.assertEquals(-1, list.indexOf(1, list.size())); + } + @Test public void testIndexOfWithOffsetOnUnsortedList() { final IntList list = new IntList(); @@ -783,6 +791,51 @@ public class IntListTest { Assert.assertEquals(list.get(list.indexOf(2, 3)), 2); } + @Test + public void testLastIndexOfWithOffsetOnEmptyList() { + final IntList list = IntList.of(); + + Assert.assertEquals(-1, list.lastIndexOf(3, list.size())); + } + + @Test + public void testLastIndexOfWithOffsetOnSortedList() { + final IntList list = IntList.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 IntList list = IntList.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 IntList list = IntList.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 IntList list = new IntList();