From c7cacf1ad4834ca180c98dfa855f8ae9b1114b9a Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Sat, 2 Dec 2017 20:24:32 +0100 Subject: [PATCH] add 'sorted' flag that keeps track whether the list is sorted The flag can be used in indexOf or for a better retainAll implementation. --- .../java/org/lucares/collections/IntList.java | 73 ++++- .../org/lucares/collections/IntListTest.java | 276 +++++++++++++++++- 2 files changed, 337 insertions(+), 12 deletions(-) diff --git a/primitiveCollections/src/main/java/org/lucares/collections/IntList.java b/primitiveCollections/src/main/java/org/lucares/collections/IntList.java index 3740dbe..8338258 100644 --- a/primitiveCollections/src/main/java/org/lucares/collections/IntList.java +++ b/primitiveCollections/src/main/java/org/lucares/collections/IntList.java @@ -18,9 +18,10 @@ public final class IntList implements Serializable, Cloneable { // TODO support Iterator // TODO add mod counts // TODO support sublists - // TODO add remember if list is sorted so we can use binary search in indexOf + // TODO use sorted flag in indexOf // TODO add lastIndexOf // TODO remove bounds checks that are handled by java, e.g. negative indices + // TODO clear private static final long serialVersionUID = 2622570032686034909L; @@ -41,6 +42,12 @@ public final class IntList implements Serializable, Cloneable { private int index = 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 IntList} with initial capacity 10. */ @@ -77,6 +84,7 @@ public final class IntList implements Serializable, Cloneable { data = new int[intList.getCapacity()]; System.arraycopy(intList.data, 0, data, 0, intList.size()); index = intList.size(); + sorted = intList.sorted; } /** @@ -123,6 +131,17 @@ public final class IntList implements Serializable, Cloneable { 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. * @@ -133,6 +152,10 @@ public final class IntList implements Serializable, Cloneable { ensureCapacity(1); data[index] = value; + + if (sorted) { + sorted = index == 0 ? sorted : data[index - 1] <= data[index]; + } index++; } @@ -159,15 +182,34 @@ public final class IntList implements Serializable, Cloneable { 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, values.length); + System.arraycopy(data, pos, data, pos + values.length, index - pos); // insert the new values System.arraycopy(values, 0, data, pos, values.length); - index += 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; + } + } + } /** @@ -191,6 +233,11 @@ public final class IntList implements Serializable, Cloneable { } data[pos] = value; + + if (sorted) { + sorted = pos <= 0 ? sorted : data[pos - 1] <= data[pos]; + sorted = pos + 1 >= size() ? sorted : data[pos] <= data[pos + 1]; + } } /** @@ -205,6 +252,13 @@ public final class IntList implements Serializable, Cloneable { ensureCapacity(values.length); System.arraycopy(values, 0, data, index, values.length); + + if (sorted) { + for (int i = 0; i < values.length && sorted; i++) { + sorted = index + i - 1 < 0 ? sorted : data[index + i - 1] <= data[index + i]; + } + } + index += values.length; } @@ -241,6 +295,8 @@ public final class IntList implements Serializable, Cloneable { System.arraycopy(data, toIndex, data, fromIndex, numRemoved); index = index - (toIndex - fromIndex); + + sorted = size() <= 1 ? true : sorted; // lists of size 1 or smaller are always sorted } /** @@ -268,6 +324,8 @@ public final class IntList implements Serializable, Cloneable { } } index = insertPosition; + + sorted = size() <= 1 ? true : sorted; // lists of size 1 or smaller are always sorted } /** @@ -297,6 +355,7 @@ public final class IntList implements Serializable, Cloneable { } index = insertPosition; + sorted = size() <= 1 ? true : sorted; // lists of size 1 or smaller are always sorted } /** @@ -334,10 +393,10 @@ public final class IntList implements Serializable, Cloneable { * if the specified {@link UnaryIntOperator} is null */ public void replaceAll(final UnaryIntOperator operator) { - final int size = index; - for (int i = 0; i < size; i++) { - data[i] = operator.apply(data[i]); + for (int i = 0; i < index; i++) { + final int newValue = operator.apply(data[i]); + set(i, newValue); } } @@ -418,6 +477,7 @@ public final class IntList implements Serializable, Cloneable { */ public void sort() { Arrays.sort(data, 0, index); + sorted = true; } /** @@ -426,6 +486,7 @@ public final class IntList implements Serializable, Cloneable { */ public void parallelSort() { Arrays.parallelSort(data, 0, index); + sorted = true; } private void ensureCapacity(final int newElements) { diff --git a/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java b/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java index 69fd03e..f123be5 100644 --- a/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java +++ b/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java @@ -66,20 +66,26 @@ public class IntListTest { public void testInsert() { final IntList list = new IntList(); + list.insert(0); + Assert.assertArrayEquals(new int[] {}, list.toArray()); + list.insert(0, 1); Assert.assertArrayEquals(new int[] { 1 }, list.toArray()); - list.insert(1, 2); - Assert.assertArrayEquals(new int[] { 1, 2 }, list.toArray()); + list.insert(1, 2, 2, 2); + Assert.assertArrayEquals(new int[] { 1, 2, 2, 2 }, list.toArray()); list.insert(1, 3); - Assert.assertArrayEquals(new int[] { 1, 3, 2 }, list.toArray()); + Assert.assertArrayEquals(new int[] { 1, 3, 2, 2, 2 }, list.toArray()); list.insert(2, 4, 4, 4); - Assert.assertArrayEquals(new int[] { 1, 3, 4, 4, 4, 2 }, list.toArray()); + Assert.assertArrayEquals(new int[] { 1, 3, 4, 4, 4, 2, 2, 2 }, list.toArray()); - list.insert(6, 5, 5); - Assert.assertArrayEquals(new int[] { 1, 3, 4, 4, 4, 2, 5, 5 }, list.toArray()); + list.insert(2); + Assert.assertArrayEquals(new int[] { 1, 3, 4, 4, 4, 2, 2, 2 }, list.toArray()); + + list.insert(8, 5, 5); + Assert.assertArrayEquals(new int[] { 1, 3, 4, 4, 4, 2, 2, 2, 5, 5 }, list.toArray()); } @Test @@ -108,6 +114,17 @@ public class IntListTest { } } + @Test + public void testInsertNull() { + final IntList list = new IntList(); + try { + list.insert(0, null); + Assert.fail(); + } catch (final NullPointerException e) { + // expected + } + } + @Test public void testSet() { final IntList list = new IntList(); @@ -530,6 +547,8 @@ public class IntListTest { Assert.assertNotEquals(list, clone); } + // TODO test clone of empty list + @Test public void testToString() { Assert.assertEquals("[]", new IntList().toString()); @@ -737,4 +756,249 @@ public class IntListTest { list.removeIf(i -> false); Assert.assertArrayEquals(new int[] {}, list.toArray()); } + + @Test + public void testSortedFlagSort() { + + Assert.assertTrue("empty list is sorted", new IntList().isSorted()); + + final IntList list = new IntList(); + 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 IntList().isSorted()); + + final IntList list = new IntList(); + list.addAll(2, 0, 1); + list.remove(0, list.size()); + Assert.assertTrue("unsorted list initialized by addAll", list.isSorted()); + } + + @Test + public void testSortedFlagAdd() { + + final IntList list = new IntList(); + 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 IntList list = new IntList(); + list.addAll(2, 0); + Assert.assertFalse("unsorted list initialized by addAll", list.isSorted()); + } + + { + final IntList list = new IntList(); + list.addAll(1); + Assert.assertTrue("list with one element is sorted", list.isSorted()); + } + + { + final IntList list = new IntList(); + 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 IntList list = new IntList(); + list.insert(0, 1, 2, 3); + Assert.assertTrue("insert sorted values into empty list", list.isSorted()); + } + + { + final IntList list = IntList.of(1, 1); + list.insert(0, 2, 2); // -> [2,2,1,1] + Assert.assertFalse("insert before: unsorted", list.isSorted()); + } + { + final IntList list = IntList.of(3, 3); + list.insert(0, 2, 2); // -> [2,2,3,3] + Assert.assertTrue("insert sorted values before: sorted", list.isSorted()); + } + + { + final IntList list = IntList.of(4, 4); + list.insert(2, 2, 2); // -> [4,4,2,2] + Assert.assertFalse("insert sorted values at end: unsorted", list.isSorted()); + } + { + final IntList list = IntList.of(1, 1); + list.insert(2, 2, 2); // -> [1,1,2,2] + Assert.assertTrue("insert sorted values at end: sorted", list.isSorted()); + } + + { + final IntList list = IntList.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 IntList list = IntList.of(1, 4); + list.insert(1, 5); // -> [1,5,4] + Assert.assertFalse("insert sorted values in middle: unsorted", list.isSorted()); + } + { + final IntList list = IntList.of(1, 4); + list.insert(1, 6, 6); // -> [1,6,6,4] + Assert.assertFalse("insert sorted values in middle: unsorted", list.isSorted()); + } + + { + final IntList list = IntList.of(1, 4); + list.insert(1, 0); // -> [1,0,4] + Assert.assertFalse("insert sorted values in middle: unsorted", list.isSorted()); + } + { + final IntList list = IntList.of(1, 4); + list.insert(1, 0, 0); // -> [1,0,0,4] + Assert.assertFalse("insert sorted values in middle: unsorted", list.isSorted()); + } + + { + final IntList list = IntList.of(3, 4); + list.insert(0, 2, 1); // -> [2,1,3,4] + Assert.assertFalse("insert unsorted at begin: unsorted", list.isSorted()); + } + { + final IntList list = IntList.of(1, 4); + list.insert(1, 2, 1); // -> [1,2,1,4] + Assert.assertFalse("insert unsorted middle: unsorted", list.isSorted()); + } + { + final IntList list = IntList.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 IntList list = IntList.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 IntList sortedList = IntList.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 IntList sortedList = IntList.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 IntList sortedList = IntList.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 IntList list = IntList.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 testSortedFlagRemoveAll() { + + final IntList list = IntList.of(4, 3, 2, 1); + list.removeAll(IntList.of(4, 3)); + Assert.assertFalse("unsorted list with two elements is not sorted", list.isSorted()); + + list.removeAll(IntList.of(2)); + Assert.assertTrue("unsorted list with one element becomes sorted", list.isSorted()); + + list.add(-1); // make list unsorted again + list.removeAll(IntList.of(-1, 2)); // remove both elements + Assert.assertTrue("unsorted list with no elements becomes sorted", list.isSorted()); + } + + @Test + public void testSortedFlagRemoveIf() { + + final IntList list = IntList.of(4, 3, 2, 1); + list.removeIf(v -> v >= 3); // removes 3 and 4 + Assert.assertFalse("unsorted list with two elements is not sorted", list.isSorted()); + + list.removeIf(v -> v >= 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 + Assert.assertTrue("unsorted list with no elements becomes sorted", list.isSorted()); + } + + @Test + public void testSortedFlagReplace() { + + final IntList list = IntList.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()); + } }