diff --git a/eclipse-collections-code-generator/src/main/resources/api/block/function/primitiveComparator.stg b/eclipse-collections-code-generator/src/main/resources/api/block/function/primitiveComparator.stg new file mode 100644 index 0000000000..3c7c390e29 --- /dev/null +++ b/eclipse-collections-code-generator/src/main/resources/api/block/function/primitiveComparator.stg @@ -0,0 +1,34 @@ +import "copyright.stg" + +targetPath() ::= "org/eclipse/collections/api/block/comparator/primitive" + +fileName(primitive) ::= "Comparator" + +class(primitive) ::= << + +>> + +body(type, name, wrapperName) ::= << + + +package org.eclipse.collections.api.block.comparator.primitive; + +import java.io.Serializable; + +/** + * Comparator is a primitive Comparator that takes two arguments of type and + * otherwise follows the contract of {@code java.util.Comparator}. + * This comparator can be used to sort primitive collections, which support indirect sorting, + * if a sort order other thant the natural one of the collection elements is required. + * + * This file was automatically generated from template file primitiveComparator.stg. + * + */ +@FunctionalInterface +public interface Comparator\ + extends Serializable +{ + int compare( value1, value2); +} + +>> diff --git a/eclipse-collections-code-generator/src/main/resources/api/list/mutablePrimitiveList.stg b/eclipse-collections-code-generator/src/main/resources/api/list/mutablePrimitiveList.stg index 55ad283242..2673dd7c6b 100644 --- a/eclipse-collections-code-generator/src/main/resources/api/list/mutablePrimitiveList.stg +++ b/eclipse-collections-code-generator/src/main/resources/api/list/mutablePrimitiveList.stg @@ -159,6 +159,32 @@ allMethods(type) ::= << */ MutableList sortThis(); +/** + * Sorts the internal data structure of this list and returns the list itself as a convenience. + */ +default MutableList sortThis(Comparator comparator) +{ + throw new UnsupportedOperationException("sortThis(Comparator comparator) is not supported on " + this.getClass()); +} + +/** + * Sorts the internal data structure of this list based on the natural order of the key returned by {@code + * function}. + */ +default \ MutableList sortThisBy(ToObjectFunction\ function) +{ + return sortThisBy(function, (Comparator\) Comparator.naturalOrder()); +} + +/** + * Sorts the internal data structure of this list based on the key returned by {@code + * function} using the provided {@code comparator}. + */ +default \ MutableList sortThisBy(ToObjectFunction\ function, Comparator\ comparator) +{ + return this.sortThis((i1, i2) -> comparator.compare(function.valueOf(i1), function.valueOf(i2))); +} + /** * Randomly permutes this list mutating its contents and returns the same list (this). * @@ -203,7 +229,9 @@ auxiliaryImports ::= [ ] allAuxiliaryImports(type) ::= << +import java.util.Comparator; import java.util.Random; +import org.eclipse.collections.api.block.comparator.primitive.Comparator; >> diff --git a/eclipse-collections-code-generator/src/main/resources/api/primitiveSort.stg b/eclipse-collections-code-generator/src/main/resources/api/primitiveSort.stg new file mode 100644 index 0000000000..1479a84262 --- /dev/null +++ b/eclipse-collections-code-generator/src/main/resources/api/primitiveSort.stg @@ -0,0 +1,154 @@ +import "copyright.stg" + +targetPath() ::= "org/eclipse/collections/impl/utility/primitive" + +fileName(primitive) ::= "QuickSort" + +class(primitive) ::= << + +>> + +body(type, name, wrapperName) ::= << + + +package org.eclipse.collections.impl.utility.primitive; + +import org.eclipse.collections.api.block.comparator.primitive.Comparator; + +/** + * QuickSort is an implementation of the Quick Sort algorithm as described in Donald Knuth's TAOCP with some + * optimizations. It supports indirect array sorting based on primitive comparators and/or key values extracted from + * the array values if a sort order other thant the natural one of the array elements is required. + * + * This file was automatically generated from template file primitiveSort.stg. + * + */ + +public final class QuickSort +{ + private static final int SORT_SMALL_SIZE = 9; + + private QuickSort() + { + } + + public static void sort([] array, int left, int right, Comparator comparator) + { + int size = right - left + 1; + + if (size \<= QuickSort.SORT_SMALL_SIZE) + { + QuickSort.insertionSort(array, left, right, comparator); + } + else + { + // Initialize new stage + int mid = (right - (right / 2)) + (left / 2); + leftVal = array[left]; + rightVal = array[right]; + midVal = array[mid]; + + int swapIndex = -1; + + if (comparator.compare(leftVal, midVal) > 0 && comparator.compare(leftVal, rightVal) > 0) + { + swapIndex = (comparator.compare(midVal, rightVal) \< 0) ? right : mid; + } + else if (comparator.compare(leftVal, midVal) \< 0 && comparator.compare(leftVal, rightVal) \< 0) + { + swapIndex = (comparator.compare(midVal, rightVal) \< 0) ? mid : right; + } + + if (swapIndex > 0) + { + swap(array, left, swapIndex); + } + + pivot = array[left]; + + int i = left + 1; + int j = right; + + while (i \< j) + { + // Compare: Key(i) : Key, skip all which are \<= pivot or until hit j + while (comparator.compare(array[i], pivot) \<= 0 && i \< j) + { + i++; + } + + // Compare Key : Key(j), skip all which are > pivot or until hit i + while (comparator.compare(pivot, array[j]) \< 0 && j > i - 1) + { + j--; + } + + if (i \< j) + { + swap(array, i, j); + } + else + { + swap(array, left, j); + } + } + + // Sort partitions, skipping sequences of elements equal to the pivot + if (right > j + 1) + { + int from = j + 1; + while (right > from && comparator.compare(pivot, array[from]) == 0) + { + from++; + } + if (right > from) + { + QuickSort.sort(array, from, right, comparator); + } + } + + if (left \< j - 1) + { + int to = j - 1; + while (to > left && comparator.compare(pivot, array[to]) == 0) + { + to--; + } + if (left \< to) + { + QuickSort.sort(array, left, to, comparator); + } + } + } + } + + private static void insertionSort([] array, int left, int right, Comparator comparator) + { + for (int j = left + 1; j \<= right; j++) + { + if (comparator.compare(array[j - 1], array[j]) > 0) + { + key = array[j]; + int i = j - 1; + + do + { + array[i + 1] = array[i]; + i--; + } + while (i > -1 && comparator.compare(key, array[i]) \< 0); + + array[i + 1] = key; + } + } + } + + private static void swap([] array, int i1, int i2) + { + value = array[i1]; + array[i1] = array[i2]; + array[i2] = value; + } +} + +>> diff --git a/eclipse-collections-code-generator/src/main/resources/impl/list/mutable/primitiveArrayList.stg b/eclipse-collections-code-generator/src/main/resources/impl/list/mutable/primitiveArrayList.stg index 1cb27b28fd..33e796a56c 100644 --- a/eclipse-collections-code-generator/src/main/resources/impl/list/mutable/primitiveArrayList.stg +++ b/eclipse-collections-code-generator/src/main/resources/impl/list/mutable/primitiveArrayList.stg @@ -28,6 +28,7 @@ import java.util.NoSuchElementException; import org.eclipse.collections.api.Iterable; import org.eclipse.collections.api.LazyIterable; import org.eclipse.collections.api.RichIterable; +import org.eclipse.collections.api.block.comparator.primitive.Comparator; import org.eclipse.collections.api.block.function.primitive.ObjectIntToObjectFunction; import org.eclipse.collections.api.block.function.primitive.ObjectToObjectFunction; import org.eclipse.collections.api.block.function.primitive.ToObjectFunction; @@ -53,6 +54,7 @@ import org.eclipse.collections.impl.primitive.AbstractIterable; import org.eclipse.collections.impl.set.mutable.primitive.HashSet; import org.eclipse.collections.impl.tuple.primitive.PrimitiveTuples; import org.eclipse.collections.impl.utility.Iterate; +import org.eclipse.collections.impl.utility.primitive.QuickSort; import java.util.Spliterator; import java.util.Spliterators; @@ -1011,6 +1013,13 @@ public class ArrayList extends AbstractIterable return this; } + @Override + public ArrayList sortThis(Comparator comparator) + { + QuickSort.sort(this.items, 0, this.size() - 1, comparator); + return this; + } + @Override public ArrayList toReversed() { diff --git a/eclipse-collections-code-generator/src/main/resources/impl/list/mutable/synchronizedPrimitiveList.stg b/eclipse-collections-code-generator/src/main/resources/impl/list/mutable/synchronizedPrimitiveList.stg index be50a257b0..a05fe1a81e 100644 --- a/eclipse-collections-code-generator/src/main/resources/impl/list/mutable/synchronizedPrimitiveList.stg +++ b/eclipse-collections-code-generator/src/main/resources/impl/list/mutable/synchronizedPrimitiveList.stg @@ -17,9 +17,11 @@ body(type, name) ::= << package org.eclipse.collections.impl.list.mutable.primitive; import java.util.Collection; +import java.util.Comparator; import org.eclipse.collections.api.Iterable; import org.eclipse.collections.api.LazyIterable; +import org.eclipse.collections.api.block.comparator.primitive.Comparator; import org.eclipse.collections.api.block.function.primitive.IntToObjectFunction; import org.eclipse.collections.api.block.function.primitive.ToObjectFunction; import org.eclipse.collections.api.block.function.primitive.ObjectIntToObjectFunction; @@ -447,6 +449,36 @@ public MutableList sortThis() return this; } +@Override +public MutableList sortThis(Comparator comparator) +{ + synchronized (this.getLock()) + { + this.getMutableList().sortThis(comparator); + } + return this; +} + +@Override +public \ MutableList sortThisBy(ToObjectFunction\ function) +{ + synchronized (this.getLock()) + { + this.getMutableList().sortThisBy(function); + } + return this; +} + +@Override +public \ MutableList sortThisBy(ToObjectFunction\ function, Comparator\ comparator) +{ + synchronized (this.getLock()) + { + this.getMutableList().sortThisBy(function, comparator); + } + return this; +} + @Override public MutableList shuffleThis() { diff --git a/eclipse-collections-code-generator/src/main/resources/test/list/mutable/abstractPrimitiveListTestCase.stg b/eclipse-collections-code-generator/src/main/resources/test/list/mutable/abstractPrimitiveListTestCase.stg index 2523188940..8f183ccaf4 100644 --- a/eclipse-collections-code-generator/src/main/resources/test/list/mutable/abstractPrimitiveListTestCase.stg +++ b/eclipse-collections-code-generator/src/main/resources/test/list/mutable/abstractPrimitiveListTestCase.stg @@ -26,6 +26,7 @@ import org.eclipse.collections.api.set.MutableSet; import org.eclipse.collections.api.tuple.primitive.IntPair; import org.eclipse.collections.api.tuple.primitive.ObjectPair; import org.eclipse.collections.api.tuple.primitive.Pair; +import org.eclipse.collections.impl.block.factory.Comparators; import org.eclipse.collections.impl.collection.mutable.primitive.AbstractMutableCollectionTestCase; import org.eclipse.collections.impl.factory.Lists; import org.eclipse.collections.impl.factory.Sets; @@ -50,6 +51,9 @@ import java.util.Arrays; */ public abstract class AbstractListTestCase extends AbstractMutableCollectionTestCase { + private static final List SORTED_LONGER_LIST = ArrayList.newListWith(<["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"]:(literal.(type))(); separator=", ">); + private static final List SORTED_SHORTER_LIST = ArrayList.newListWith(<["0", "1", "2", "3", "4", "5"]:(literal.(type))(); separator=", ">); + @Override protected abstract MutableList classUnderTest(); @@ -331,6 +335,95 @@ public abstract class AbstractListTestCase extends AbstractMutableCo Assert.assertEquals(<(literal.(type))("1")>, list.get(0)<(wideDelta.(type))>); } + @Test + public void sortWithPrimitiveComparator() + { + MutableList index = this.newMutableCollectionWith(<["0", "1", "2", "3", "4"]:(literal.(type))(); separator=", ">); // sin: 0, 0.841, 0.909, 0.141, -0.757 + + index.sortThis((i1, i2) -> Double.compare(Math.sin(i1), Math.sin(i2))); + + Assert.assertEquals(ArrayList.newListWith(<["4", "0", "3", "1", "2"]:(literal.(type))(); separator=", ">), index); + } + + @Test + public void sortWithOddEvenComparator() + { + MutableList index = this.newMutableCollectionWith(<["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]:(literal.(type))(); separator=", ">); + + index.sortThis((a, b) -> (int) ((int) ((int) a & 1) - ((int) b & 1))); + + Assert.assertEquals(ArrayList.newListWith(<["0", "2", "4", "6", "8", "1", "3", "5", "7", "9"]:(literal.(type))(); separator=", ">), index); + } + + @Test + public void sortWithKeyExtractorNaturalComparator() + { + MutableList\ list = Lists.mutable.of("Foo", "Bar", "Baz", "Waldo", "Qux"); + MutableList index = this.newMutableCollectionWith(<["0", "1", "2", "3", "4"]:(literal.(type))(); separator=", ">); + + index.sortThisBy(i -> list.get((int) i)); + + Assert.assertEquals(ArrayList.newListWith(<["1", "2", "0", "4", "3"]:(literal.(type))(); separator=", ">), index); + } + + @Test + public void sortWithKeyExtractorUnnaturalComparator() + { + MutableList\ list = Lists.mutable.of("Foo", "Bar", "Baz", "Waldo", "Qux"); + MutableList index = this.newMutableCollectionWith(<["0", "1", "2", "3", "4"]:(literal.(type))(); separator=", ">); + + index.sortThisBy(i -> list.get((int) i), Comparators.naturalOrder().reversed()); + + Assert.assertEquals(ArrayList.newListWith(<["3", "4", "0", "2", "1"]:(literal.(type))(); separator=", ">), index); + } + + @Test + public void sortShuffledInputWithDupes() + { + Assert.assertEquals( + ArrayList.newListWith(<["0", "1", "1", "2", "3", "4"]:(literal.(type))(); separator=", ">), + this.newMutableCollectionWith(<["3", "2", "1", "0", "1", "4"]:(literal.(type))(); separator=", ">).sortThis(::compare)); + Assert.assertEquals( + ArrayList.newListWith(<["1", "2", "2", "2", "3", "4", "6", "7", "8", "10", "11", "12", "13", "14", "15", "15", "15", "17", "18", "19"]:(literal.(type))(); separator=", ">), + this.newMutableCollectionWith(<["17", "1", "15", "12", "10", "4", "2", "19", "2", "8", "18", "15", "15", "13", "3", "11", "7", "2", "14", "6"]:(literal.(type))(); separator=", ">).sortThis(::compare)); + } + + @Test + public void sortShuffledInput() + { + Assert.assertEquals(SORTED_SHORTER_LIST, this.newMutableCollectionWith(<["3", "2", "1", "0", "5", "4"]:(literal.(type))(); separator=", ">).sortThis(::compare)); + + Assert.assertEquals(SORTED_SHORTER_LIST, this.newMutableCollectionWith(<["3", "0", "1", "2", "5", "4"]:(literal.(type))(); separator=", ">).sortThis(::compare)); + + Assert.assertEquals( + SORTED_LONGER_LIST, + this.newMutableCollectionWith(<["17", "1", "16", "12", "10", "4", "2", "19", "5", "8", "18", "15", "20", "13", "3", "11", "7", "9", "14", "6"]:(literal.(type))(); separator=", ">).sortThis(::compare)); + + Assert.assertEquals( + SORTED_LONGER_LIST, + this.newMutableCollectionWith(<["12", "3", "17", "20", "5", "2", "4", "9", "16", "19", "10", "14", "6", "7", "15", "11", "13", "18", "8", "1"]:(literal.(type))(); separator=", ">).sortThis(::compare)); + } + + @Test + public void sortSortedInput() + { + Assert.assertEquals(SORTED_SHORTER_LIST, this.newMutableCollectionWith(<["0", "1", "2", "3", "4", "5"]:(literal.(type))(); separator=", ">).sortThis(::compare)); + + Assert.assertEquals( + SORTED_LONGER_LIST, + this.newMutableCollectionWith(<["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"]:(literal.(type))(); separator=", ">).sortThis(::compare)); + } + + @Test + public void sortReversedSortedInput() + { + Assert.assertEquals(SORTED_SHORTER_LIST, this.newMutableCollectionWith(<["5", "4", "3", "2", "1", "0"]:(literal.(type))(); separator=", ">).sortThis(::compare)); + + Assert.assertEquals( + SORTED_LONGER_LIST, + this.newMutableCollectionWith(<["20", "19", "18", "17", "16", "15", "14", "13", "12", "11", "10", "9", "8", "7", "6", "5", "4", "3", "2", "1"]:(literal.(type))(); separator=", ">).sortThis(::compare)); + } + @Test public void shuffleThis() { diff --git a/eclipse-collections-code-generator/src/main/resources/test/list/mutable/unmodifiablePrimitiveListTest.stg b/eclipse-collections-code-generator/src/main/resources/test/list/mutable/unmodifiablePrimitiveListTest.stg index 9d977be13b..d98c9ca9f4 100644 --- a/eclipse-collections-code-generator/src/main/resources/test/list/mutable/unmodifiablePrimitiveListTest.stg +++ b/eclipse-collections-code-generator/src/main/resources/test/list/mutable/unmodifiablePrimitiveListTest.stg @@ -21,6 +21,7 @@ import java.util.NoSuchElementException; import org.eclipse.collections.api.iterator.Iterator; import org.eclipse.collections.api.iterator.MutableIterator; +import org.eclipse.collections.impl.block.factory.Comparators; import org.eclipse.collections.impl.block.factory.primitive.Predicates; import org.eclipse.collections.impl.test.Verify; import org.junit.Assert; @@ -242,6 +243,62 @@ public class UnmodifiableListTest extends AbstractListTestCase new UnmodifiableList(new ArrayList()).sortThis(); } + @Override + @Test(expected = UnsupportedOperationException.class) + public void sortWithPrimitiveComparator() + { + new UnmodifiableList(new ArrayList()).sortThis(::compare); + } + + @Override + @Test(expected = UnsupportedOperationException.class) + public void sortWithOddEvenComparator() + { + new UnmodifiableList(new ArrayList()).sortThis((a, b) -> (int) ((int) ((int) a & 1) - ((int) b & 1))); + } + + @Override + @Test(expected = UnsupportedOperationException.class) + public void sortWithKeyExtractorNaturalComparator() + { + new UnmodifiableList(new ArrayList()).sortThisBy(::toString); + } + + @Override + @Test(expected = UnsupportedOperationException.class) + public void sortWithKeyExtractorUnnaturalComparator() + { + new UnmodifiableList(new ArrayList()).sortThisBy(::toString, Comparators.naturalOrder().reversed()); + } + + @Override + @Test(expected = UnsupportedOperationException.class) + public void sortShuffledInputWithDupes() + { + new UnmodifiableList(new ArrayList()).sortThis(); + } + + @Override + @Test(expected = UnsupportedOperationException.class) + public void sortShuffledInput() + { + new UnmodifiableList(new ArrayList()).sortThis(); + } + + @Override + @Test(expected = UnsupportedOperationException.class) + public void sortSortedInput() + { + new UnmodifiableList(new ArrayList()).sortThis(); + } + + @Override + @Test(expected = UnsupportedOperationException.class) + public void sortReversedSortedInput() + { + new UnmodifiableList(new ArrayList()).sortThis(); + } + @Override @Test(expected = UnsupportedOperationException.class) public void shuffleThis()