add 'sorted' flag that keeps track whether the list is sorted

The flag can be used in indexOf or for a better retainAll
implementation.
This commit is contained in:
2017-12-02 20:24:32 +01:00
parent 7f3d4872ae
commit c7cacf1ad4
2 changed files with 337 additions and 12 deletions

View File

@@ -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.
* <p>
* 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) {

View File

@@ -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());
}
}