diff --git a/commons-numbers-arrays/pom.xml b/commons-numbers-arrays/pom.xml
index 2d86e9a15..65ab6b023 100644
--- a/commons-numbers-arrays/pom.xml
+++ b/commons-numbers-arrays/pom.xml
@@ -49,6 +49,18 @@
This is a specialised class to implement a reduced API similar to a + * {@link java.util.BitSet}. It uses no bounds range checks and supports only + * the methods required to implement the {@link UpdatingInterval} API. + * + *
An offset is supported to allow the fixed size to cover a range of indices starting + * above 0 with the most efficient usage of storage. + * + *
See the BloomFilter code in Commons Collections for use of long[] data to store + * bits. + * + * @since 1.2 + */ +final class BitIndexUpdatingInterval implements UpdatingInterval { + /** All 64-bits bits set. */ + private static final long LONG_MASK = -1L; + /** A bit shift to apply to an integer to divided by 64 (2^6). */ + private static final int DIVIDE_BY_64 = 6; + + /** Bit indexes. */ + private final long[] data; + + /** Index offset. */ + private final int offset; + /** Left bound of the support. */ + private int left; + /** Right bound of the support. */ + private int right; + + /** + * Create an instance to store indices within the range {@code [left, right]}. + * The range is not validated. + * + * @param left Lower bound (inclusive). + * @param right Upper bound (inclusive). + */ + BitIndexUpdatingInterval(int left, int right) { + this.offset = left; + this.left = left; + this.right = right; + // Allocate storage to store index==right + // Note: This may allow directly writing to index > right if there + // is extra capacity. + data = new long[getLongIndex(right - offset) + 1]; + } + + /** + * Create an instance with the range {@code [left, right]} and reusing the provided + * index {@code data}. + * + * @param data Data. + * @param offset Index offset. + * @param left Lower bound (inclusive). + * @param right Upper bound (inclusive). + */ + private BitIndexUpdatingInterval(long[] data, int offset, int left, int right) { + this.data = data; + this.offset = offset; + this.left = left; + this.right = right; + } + + /** + * Gets the filter index for the specified bit index assuming the filter is using + * 64-bit longs to store bits starting at index 0. + * + *
The index is assumed to be positive. For a positive index the result will match + * {@code bitIndex / 64}.
+ * + *The divide is performed using bit shifts. If the input is negative the + * behavior is not defined.
+ * + * @param bitIndex the bit index (assumed to be positive) + * @return the index of the bit map in an array of bit maps. + */ + private static int getLongIndex(final int bitIndex) { + // An integer divide by 64 is equivalent to a shift of 6 bits if the integer is + // positive. + // We do not explicitly check for a negative here. Instead we use a + // signed shift. Any negative index will produce a negative value + // by sign-extension and if used as an index into an array it will throw an + // exception. + return bitIndex >> DIVIDE_BY_64; + } + + /** + * Gets the filter bit mask for the specified bit index assuming the filter is using + * 64-bit longs to store bits starting at index 0. The returned value is a + * {@code long} with only 1 bit set. + * + *The index is assumed to be positive. For a positive index the result will match + * {@code 1L << (bitIndex % 64)}.
+ * + *If the input is negative the behavior is not defined.
+ * + * @param bitIndex the bit index (assumed to be positive) + * @return the filter bit + */ + private static long getLongBit(final int bitIndex) { + // Bit shifts only use the first 6 bits. Thus it is not necessary to mask this + // using 0x3f (63) or compute bitIndex % 64. + // Note: If the index is negative the shift will be (64 - (bitIndex & 0x3f)) and + // this will identify an incorrect bit. + return 1L << bitIndex; + } + + /** + * Sets the bit at the specified index to {@code true}. + * + *Warning: This has no range checks. + * + * @param bitIndex the bit index (assumed to be positive) + */ + void set(int bitIndex) { + // WARNING: No range checks !!! + final int index = bitIndex - offset; + final int i = getLongIndex(index); + final long m = getLongBit(index); + data[i] |= m; + } + + /** + * Returns the index of the first bit that is set to {@code true} that occurs on or + * after the specified starting index. + * + *
Warning: This has no range checks. It is assumed that {@code left <= k <= right}, + * that is there is a set bit on or after {@code k}. + * + * @param k Index to start checking from (inclusive). + * @return the index of the next set bit + */ + private int nextIndex(int k) { + // left <= k <= right + + final int index = k - offset; + int i = getLongIndex(index); + + // Mask bits after the bit index + // mask = 11111000 = -1L << (index % 64) + long bits = data[i] & (LONG_MASK << index); + for (;;) { + if (bits != 0) { + //(i+1) i + // | index | + // | | | + // 0 001010000 + return i * Long.SIZE + Long.numberOfTrailingZeros(bits) + offset; + } + // Unsupported: the interval should contain k + //if (++i == data.length) + // return right + 1 + bits = data[++i]; + } + } + + /** + * Returns the index of the first bit that is set to {@code true} that occurs on or + * before the specified starting index. + * + *
Warning: This has no range checks. It is assumed that {@code left <= k <= right}, + * that is there is a set bit on or before {@code k}. + * + * @param k Index to start checking from (inclusive). + * @return the index of the previous set bit + */ + private int previousIndex(int k) { + // left <= k <= right + + final int index = k - offset; + int i = getLongIndex(index); + + // Mask bits before the bit index + // mask = 00011111 = -1L >>> (64 - ((index + 1) % 64)) + long bits = data[i] & (LONG_MASK >>> -(index + 1)); + for (;;) { + if (bits != 0) { + //(i+1) i + // | index | + // | | | + // 0 001010000 + return (i + 1) * Long.SIZE - Long.numberOfLeadingZeros(bits) - 1 + offset; + } + // Unsupported: the interval should contain k + //if (i == 0) + // return left - 1 + bits = data[--i]; + } + } + + @Override + public int left() { + return left; + } + + @Override + public int right() { + return right; + } + + @Override + public int updateLeft(int k) { + // Assume left < k= < right + return left = nextIndex(k); + } + + @Override + public int updateRight(int k) { + // Assume left <= k < right + return right = previousIndex(k); + } + + @Override + public UpdatingInterval splitLeft(int ka, int kb) { + // Assume left < ka <= kb < right + final int lower = left; + left = nextIndex(kb + 1); + return new BitIndexUpdatingInterval(data, offset, lower, previousIndex(ka - 1)); + } +} diff --git a/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/HashIndexSet.java b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/HashIndexSet.java new file mode 100644 index 000000000..388e82294 --- /dev/null +++ b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/HashIndexSet.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.numbers.arrays; + +/** + * An index set backed by a open-addressed hash table using linear hashing. Table size is a power + * of 2 and has a maximum capacity of 2^29 with a fixed load factor of 0.5. If the functional + * capacity is exceeded then the set raises an {@link IllegalStateException}. + * + *
Values are stored using bit inversion. Any positive index will have a negative + * representation when stored. An empty slot is indicated by a zero. + * + *
This class has a minimal API. It can be used to ensure a collection of indices of + * a known size are unique: + * + *
{@code + * int[] keys = ... + * HashIndexSet set = new HashIndexSet(keys.length); + * for (int k : keys) { + * if (set.add(k)) { + * // first occurrence of k in keys + * } + * } + * }+ * + * @see Open addressing (Wikipedia) + * @since 1.2 + */ +final class HashIndexSet { + /** Message for an invalid index. */ + private static final String INVALID_INDEX = "Invalid index: "; + /** The maximum capacity of the set. */ + private static final int MAX_CAPACITY = 1 << 29; + /** The minimum size of the backing array. */ + private static final int MIN_SIZE = 16; + /** + * Unsigned 32-bit integer numerator of the golden ratio (0.618) with an assumed + * denominator of 2^32. + * + *
+ * 2654435769 = round(2^32 * (sqrt(5) - 1) / 2) + * Long.toHexString((long)(0x1p32 * (Math.sqrt(5.0) - 1) / 2)) + *+ */ + private static final int PHI = 0x9e3779b9; + + /** The set. */ + private final int[] set; + /** The size. */ + private int size; + + /** + * Create an instance with size to store up to the specified {@code capacity}. + * + *
The functional capacity (number of indices that can be stored) is the next power + * of 2 above {@code capacity}; or a minimum size if the requested {@code capacity} is + * small. + * + * @param capacity Capacity. + */ + private HashIndexSet(int capacity) { + // This will generate a load factor at capacity in the range (0.25, 0.5] + // The use of Math.max will ignore zero/negative capacity requests. + set = new int[nextPow2(Math.max(MIN_SIZE, capacity * 2))]; + } + + /** + * Create an instance with size to store up to the specified {@code capacity}. + * The maximum supported {@code capacity} is 229. + * + * @param capacity Capacity. + * @return the hash index set + * @throws IllegalArgumentException if the {@code capacity} is too large. + */ + static HashIndexSet create(int capacity) { + if (capacity > MAX_CAPACITY) { + throw new IllegalArgumentException("Unsupported capacity: " + capacity); + } + return new HashIndexSet(capacity); + } + + /** + * Returns the closest power-of-two number greater than or equal to {@code value}. + * + *
Warning: This will return {@link Integer#MIN_VALUE} for any {@code value} above + * {@code 1 << 30}. This is the next power of 2 as an unsigned integer. + * + *
See Bit + * Hacks: Rounding up to a power of 2 + * + * @param value Value. + * @return the closest power-of-two number greater than or equal to value + */ + private static int nextPow2(int value) { + int result = value - 1; + result |= result >>> 1; + result |= result >>> 2; + result |= result >>> 4; + result |= result >>> 8; + return (result | (result >>> 16)) + 1; + } + + /** + * Adds the {@code index} to the set. + * + * @param index Index. + * @return true if the set was modified by the operation + * @throws IndexOutOfBoundsException if the index is negative + */ + boolean add(int index) { + if (index < 0) { + throw new IndexOutOfBoundsException(INVALID_INDEX + index); + } + final int[] keys = set; + final int key = ~index; + final int mask = keys.length - 1; + int pos = mix(index) & mask; + int curr = keys[pos]; + if (curr < 0) { + if (curr == key) { + // Already present + return false; + } + // Probe + while ((curr = keys[pos = (pos + 1) & mask]) < 0) { + if (curr == key) { + // Already present + return false; + } + } + } + // Insert + keys[pos] = key; + // Here the load factor is 0.5: Test if size > keys.length * 0.5 + if (++size > (mask + 1) >>> 1) { + // This is where we should grow the size of the set and re-insert + // all current keys into the new key storage. Here we are using a + // fixed capacity so raise an exception. + throw new IllegalStateException("Functional capacity exceeded: " + (keys.length >>> 1)); + } + return true; + } + + /** + * Mix the bits of an integer. + * + *
This is the fast hash function used in the linear hash implementation in the Koloboke Collections. + * + * @param x Bits. + * @return the mixed bits + */ + private static int mix(int x) { + final int h = x * PHI; + return h ^ (h >>> 16); + } +} diff --git a/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/IndexSupport.java b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/IndexSupport.java new file mode 100644 index 000000000..35bfb2fce --- /dev/null +++ b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/IndexSupport.java @@ -0,0 +1,334 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.numbers.arrays; + +/** + * Support for creating {@link UpdatingInterval} implementations and validating indices. + * + * @since 1.2 + */ +final class IndexSupport { + /** The upper threshold to use a modified insertion sort to find unique indices. */ + private static final int INSERTION_SORT_SIZE = 20; + + /** No instances. */ + private IndexSupport() {} + + /** + * Returns an interval that covers the specified indices {@code k}. + * + * @param left Lower bound of data (inclusive). + * @param right Upper bound of data (inclusive). + * @param k Indices. + * @param n Count of indices (must be strictly positive). + * @throws IndexOutOfBoundsException if any index {@code k} is not within the + * sub-range {@code [left, right]} + * @return the interval + */ + static UpdatingInterval createUpdatingInterval(int left, int right, int[] k, int n) { + // Note: A typical use case is to have a few indices. Thus the heuristics + // in this method should be very fast when n is small. + // We have a choice between a KeyUpdatingInterval which requires + // sorted keys or a BitIndexUpdatingInterval which handles keys in any order. + // The purpose of the heuristics is to avoid a very bad choice of data structure, + // rather than choosing the best data structure in all situations. As long as the + // choice is reasonable the speed will not impact a partition algorithm. + + // Simple cases + if (n == 2) { + if (k[0] == k[1]) { + return newUpdatingInterval(left, right, k, 1); + } + if (k[1] < k[0]) { + final int v = k[0]; + k[0] = k[1]; + k[1] = v; + } + return newUpdatingInterval(left, right, k, 2); + } + + // Strategy: Must be fast on already ascending data. + // Note: The recommended way to generate a lot of partition indices is to + // generate in sequence. + + // n <= small: + // Modified insertion sort (naturally finds ascending data) + // n > small: + // Look for ascending sequence and compact + // else: + // Remove duplicates using an order(1) data structure and sort + + if (n <= INSERTION_SORT_SIZE) { + final int unique = Sorting.insertionSortIndices(k, n); + return newUpdatingInterval(left, right, k, unique); + } + + if (isAscending(k, n)) { + // For sorted keys the KeyUpdatingInterval is fast. It may be slower than the + // BitIndexUpdatingInterval depending on data length but not significantly + // slower and the difference is lost in the time taken for partitioning. + // So always use the keys. + final int unique = compressDuplicates(k, n); + return newUpdatingInterval(left, right, k, unique); + } + + // At least 20 indices that are partially unordered. + + // Find min/max to understand the range. + int min = k[n - 1]; + int max = min; + for (int i = n - 1; --i >= 0;) { + min = Math.min(min, k[i]); + max = Math.max(max, k[i]); + } + + // Here we use a simple test based on the number of comparisons required + // to perform the expected next/previous look-ups after a split. + // It is expected that we can cut n keys a maximum of n-1 times. + // Each cut requires a scan next/previous to divide the interval into two intervals: + // + // cut + // | + // k1--------k2---------k3---- ... ---------kn initial interval + // <--| find previous + // find next |--> + // k1 k2---------k3---- ... ---------kn divided intervals + // + // An BitSet will scan from the cut location and find a match in time proportional to + // the index density. Average density is (size / n) and the scanning covers 64 + // indices together: Order(2 * n * (size / n) / 64) = Order(size / 32) + + // Sorted keys: Sort time Order(n log(n)) : Splitting time Order(log(n)) (binary search approx) + // Bit keys : Sort time Order(1) : Splitting time Order(size / 32) + + // Transition when n * n ~ size / 32 + // Benchmarking shows this is a reasonable approximation when size < 2^20. + // The speed of the bit keys is approximately independent of n and proportional to size. + // Large size observes degrading performance of the bit keys vs sorted keys. + // We introduce a penalty for each 4x increase over size = 2^20. + // n * n = size/32 * 2^log4(size / 2^20) + // The transition point still favours the bit keys when sorted keys would be faster. + // However the difference is held within 4x and the BitSet type structure is still fast + // enough to be negligible against the speed of partitioning. + + // Transition point: n = sqrt(size/32) + // size n + // 2^10 5.66 + // 2^15 32.0 + // 2^20 181.0 + + // Transition point: n = sqrt(size/32 * 2^(log4(size/2^20)))) + // size n + // 2^22 512.0 + // 2^24 1448.2 + // 2^28 11585 + // 2^31 55108 + + final int size = max - min + 1; + + // Divide by 32 is a shift of 5. This is reduced for each 4-fold size above 2^20. + // At 2^31 the shift reduces to 0. + int shift = 5; + if (size > (1 << 20)) { + // log4(size/2^20) == (log2(size) - 20) / 2 + shift -= (ceilLog2(size) - 20) >>> 1; + } + + if ((long) n * n > (size >> shift)) { + final BitIndexUpdatingInterval interval = new BitIndexUpdatingInterval(min, max); + for (int i = n; --i >= 0;) { + interval.set(k[i]); + } + return interval; + } + + // Sort with a hash set to filter indices + final int unique = Sorting.sortIndices(k, n); + return new KeyUpdatingInterval(k, unique); + } + + /** + * Test the data is in ascending order: {@code data[i] <= data[i+1]} for all {@code i}. + * Data is assumed to be at least length 1. + * + * @param data Data. + * @param n Length of data. + * @return true if ascending + */ + private static boolean isAscending(int[] data, int n) { + for (int i = 0; ++i < n;) { + if (data[i] < data[i - 1]) { + // descending + return false; + } + } + return true; + } + + /** + * Compress duplicates in the ascending data. + * + *
Warning: Requires {@code n > 0}. + * + * @param data Indices. + * @param n Number of indices. + * @return the number of unique indices + */ + private static int compressDuplicates(int[] data, int n) { + // Compress to remove duplicates + int last = 0; + int top = data[0]; + for (int i = 0; ++i < n;) { + final int v = data[i]; + if (v == top) { + continue; + } + top = v; + data[++last] = v; + } + return last + 1; + } + + /** + * Compute {@code ceil(log2(x))}. This is valid for all strictly positive {@code x}. + * + *
Returns -1 for {@code x = 0} in place of -infinity. + * + * @param x Value. + * @return {@code ceil(log2(x))} + */ + private static int ceilLog2(int x) { + return 32 - Integer.numberOfLeadingZeros(x - 1); + } + + /** + * Returns an interval that covers the specified indices {@code k}. + * The indices must be sorted. + * + * @param left Lower bound of data (inclusive). + * @param right Upper bound of data (inclusive). + * @param k Indices. + * @param n Count of indices (must be strictly positive). + * @throws IndexOutOfBoundsException if any index {@code k} is not within the + * sub-range {@code [left, right]} + * @return the interval + */ + private static UpdatingInterval newUpdatingInterval(int left, int right, int[] k, int n) { + return new KeyUpdatingInterval(k, n); + } + + /** + * Count the number of indices. Returns a negative value if the indices are sorted. + * + * @param keys Keys. + * @param n Count of indices. + * @return the count of (sorted) indices + */ + static int countIndices(UpdatingInterval keys, int n) { + if (keys instanceof KeyUpdatingInterval) { + return -((KeyUpdatingInterval) keys).size(); + } + return n; + } + + /** + * Checks if the sub-range from fromIndex (inclusive) to toIndex (exclusive) is + * within the bounds of range from 0 (inclusive) to length (exclusive). + * + *
This function provides the functionality of + * {@code java.utils.Objects.checkFromToIndex} introduced in JDK 9. The Objects + * javadoc has been reproduced for reference. The return value has been changed + * to void. + * + *
The sub-range is defined to be out of bounds if any of the following + * inequalities is true: + *
Warning: Indices must be sorted and distinct. + * + * @param indices Indices. + * @param n Number of indices. + */ + KeyUpdatingInterval(int[] indices, int n) { + this(indices, 0, n - 1); + } + + /** + * @param indices Indices. + * @param l Index of left key. + * @param r Index of right key. + */ + private KeyUpdatingInterval(int[] indices, int l, int r) { + keys = indices; + this.l = l; + this.r = r; + } + + @Override + public int left() { + return keys[l]; + } + + @Override + public int right() { + return keys[r]; + } + + @Override + public int updateLeft(int k) { + // Assume left < k <= right (i.e. we must move left at least 1) + // Search using a scan on the assumption that k is close to the end + int i = l; + do { + ++i; + } while (keys[i] < k); + l = i; + return keys[i]; + } + + @Override + public int updateRight(int k) { + // Assume left <= k < right (i.e. we must move right at least 1) + // Search using a scan on the assumption that k is close to the end + int i = r; + do { + --i; + } while (keys[i] > k); + r = i; + return keys[i]; + } + + @Override + public UpdatingInterval splitLeft(int ka, int kb) { + // left < ka <= kb < right + + // Find the new left bound for the upper interval. + // Switch to a linear scan if length is small. + int i; + if (r - l < SCAN_SIZE) { + i = r; + do { + --i; + } while (keys[i] > kb); + } else { + // Binary search + i = searchLessOrEqual(keys, l, r, kb); + } + final int lowerLeft = l; + l = i + 1; + + // Find the new right bound for the lower interval using a scan since a + // typical use case has ka == kb and this is faster than a second binary search. + while (keys[i] >= ka) { + --i; + } + // return left + return new KeyUpdatingInterval(keys, lowerLeft, i); + } + + /** + * Return the current number of indices in the interval. + * + * @return the size + */ + int size() { + return r - l + 1; + } + + /** + * Search the data for the largest index {@code i} where {@code a[i]} is + * less-than-or-equal to the {@code key}; else return {@code left - 1}. + *
+ * a[i] <= k : left <= i <= right, or (left - 1) + *+ * + *
The data is assumed to be in ascending order, otherwise the behaviour is undefined. + * If the range contains multiple elements with the {@code key} value, the result index + * may be any that match. + * + *
This is similar to using {@link java.util.Arrays#binarySearch(int[], int, int, int) + * Arrays.binarySearch}. The method differs in: + *
An equivalent use of binary search is: + *
{@code + * int i = Arrays.binarySearch(a, left, right + 1, k); + * if (i < 0) { + * i = ~i - 1; + * } + * }+ * + *
This specialisation avoids the caller checking the binary search result for the use + * case when the presence or absence of a key is not important; only that the returned + * index for an absence of a key is the largest index. When used on unique keys this + * method can be used to update an upper index so all keys are known to be below a key: + * + *
{@code + * int[] keys = ... + * // [i0, i1] contains all keys + * int i0 = 0; + * int i1 = keys.length - 1; + * // Update: [i0, i1] contains all keys <= k + * i1 = searchLessOrEqual(keys, i0, i1, k); + * }+ * + * @param a Data. + * @param left Lower bound (inclusive). + * @param right Upper bound (inclusive). + * @param k Key. + * @return largest index {@code i} such that {@code a[i] <= k}, or {@code left - 1} if no + * such index exists + */ + static int searchLessOrEqual(int[] a, int left, int right, int k) { + int l = left; + int r = right; + while (l <= r) { + // Middle value + final int m = (l + r) >>> 1; + final int v = a[m]; + // Test: + // l------m------r + // v k update left + // k v update right + if (v < k) { + l = m + 1; + } else if (v > k) { + r = m - 1; + } else { + // Equal + return m; + } + } + // Return largest known value below: + // r is always moved downward when a middle index value is too high + return r; + } + + /** + * Search the data for the smallest index {@code i} where {@code a[i]} is + * greater-than-or-equal to the {@code key}; else return {@code right + 1}. + *
+ * a[i] >= k : left <= i <= right, or (right + 1) + *+ * + *
The data is assumed to be in ascending order, otherwise the behaviour is undefined. + * If the range contains multiple elements with the {@code key} value, the result index + * may be any that match. + * + *
This is similar to using {@link java.util.Arrays#binarySearch(int[], int, int, int) + * Arrays.binarySearch}. The method differs in: + *
An equivalent use of binary search is: + *
{@code + * int i = Arrays.binarySearch(a, left, right + 1, k); + * if (i < 0) { + * i = ~i; + * } + * }+ * + *
This specialisation avoids the caller checking the binary search result for the use + * case when the presence or absence of a key is not important; only that the returned + * index for an absence of a key is the smallest index. When used on unique keys this + * method can be used to update a lower index so all keys are known to be above a key: + * + *
{@code + * int[] keys = ... + * // [i0, i1] contains all keys + * int i0 = 0; + * int i1 = keys.length - 1; + * // Update: [i0, i1] contains all keys >= k + * i0 = searchGreaterOrEqual(keys, i0, i1, k); + * }+ * + * @param a Data. + * @param left Lower bound (inclusive). + * @param right Upper bound (inclusive). + * @param k Key. + * @return largest index {@code i} such that {@code a[i] >= k}, or {@code right + 1} if no + * such index exists + */ + static int searchGreaterOrEqual(int[] a, int left, int right, int k) { + int l = left; + int r = right; + while (l <= r) { + // Middle value + final int m = (l + r) >>> 1; + final int v = a[m]; + // Test: + // l------m------r + // v k update left + // k v update right + if (v < k) { + l = m + 1; + } else if (v > k) { + r = m - 1; + } else { + // Equal + return m; + } + } + // Smallest known value above + // l is always moved upward when a middle index value is too low + return l; + } +} diff --git a/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/QuickSelect.java b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/QuickSelect.java new file mode 100644 index 000000000..27bef7ef4 --- /dev/null +++ b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/QuickSelect.java @@ -0,0 +1,1633 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.numbers.arrays; + +/** + * Partition array data. + * + *
Note: Requires that the floating-point data contains no NaN values; sorting does not + * respect the order of signed zeros imposed by {@link Double#compare(double, double)}; + * mixed signed zeros may be destroyed (the mixture updated during partitioning). The + * caller is responsible for counting a mixture of signed zeros and restoring them if + * required. + * + * @see Selection + * @since 1.2 + */ +final class QuickSelect { + // Implementation Notes + // + // Selection is performed using a quickselect variant to recursively divide the range + // to select the target index, or indices. Partition sizes or recursion are monitored + // will fall-backs on poor convergence of a linearselect (single index) or heapselect. + // + // Many implementations were tested, each with strengths and weaknesses on different + // input data containing random elements, repeat elements, elements with repeat + // patterns, and constant elements. The final implementation performs well across data + // types for single and multiple indices with no obvious weakness. + // See: o.a.c.numbers.examples.jmh.arrays for benchmarking implementations. + // + // Single indices are selected using a quickselect adaptive method based on Alexandrescu. + // The algorithm is a quickselect around a pivot identified using a + // sample-of-sample-of-samples created from the entire range data. This pivot will + // have known lower and upper margins and ensures elimination of a minimum fraction of + // data at each step. To increase speed the pivot can be identified using less of the data + // but without margin guarantees (sampling mode). The algorithm monitors partition sizes + // against the known margins. If the reduction in the partition size is not large enough + // then the algorithm can disable sampling mode and ensure linear performance by removing + // a set fraction of the data each iteration. + // + // Modifications from Alexandrescu are: + // 1. Initialise sampling mode using the Floyd-Rivest (FR) SELECT algorithm. + // 2. Adaption is adjusted to force use of the lower margin in the far-step method when + // sampling is disabled. + // 3. Change the far-step method to a min-of-4 then median-of-3 into the 2nd 12th-tile. + // The original method uses a lower-median-of-4, min-of-3 into the 4th 12th-tile. + // 4. Position the sample around the target k when in sampling mode for the non-far-step + // methods. + // + // The far step method is used when target k is within 1/12 of the end of the data A. + // The differences in the far-step method are: + // - The upper margin when not sampling is 8/24 vs. 9/24; the lower margin remains at 1/12. + // - The position of the sample is closer to the expected location of k < |A|/12. + // - Sampling mode uses a median-of-3 with adaptive k, matching the other step methods. + // Note the original min-of-3 sample is more likely to create a pivot too small if used + // with adaption of k leaving k in the larger partition and a wasted iteration. + // + // The Floyd-Rivest (FR) SELECT algorithm is preferred for sampling over using quickselect + // adaptive sampling. It uses a smaller sample and has improved heuristics to place the sample + // pivot. However the FR sample is a small range of the data and pivot selection can be poor + // if the sample is not representative. This can be mitigated by creating a random sample + // of the entire range for the pivot selection. This implementation does not use random + // sampling for the FR mode. Performance is identical on random data (randomisation is a + // negligible overhead) and faster on sorted data. Any data not suitable for the FR algorithm + // are immediately switched to the quickselect adaptive algorithm with sampling. Performance + // across a range of data shows this strategy is approximately mid-way in performance between + // FR with random sampling, and quickselect adaptive in sampling mode. The benefit is that + // sorted or partially partitioned data are not intentionally unordered as the method will + // only move elements known to be incorrectly placed in the array. + // + // Multiple indices are selected using a dual-pivot partition method by + // Yaroslavskiy to divide the interval containing the indices. When indices are effectively + // a single index the method can switch to the single index selection to use the FR algorithm. + // Alternative schemes to partition multiple indices are to repeat call single index select + // with cached pivots, or without cached pivots if processing indices in order as the previous + // index brackets the range for the next search. Caching pivots is the most effective + // alternative. It requires storing all pivots during select, and using the cache to look-up + // the search bounds (sub-range) for each target index. This requires 2n searches for n indices. + // All pivots must be stored to avoid destroying previously partitioned data on repeat entry + // to the array. The current scheme inverts this by requiring at most n-1 divides of the + // indices during recursion and has the advantage of tracking recursion depth during selection + // for each sub-range. Division of indices is a small overhead for the common case where + // the number of indices is far smaller than the size of the data. + // + // Dual-pivot paritioning adapted from Yaroslavskiy + // http://codeblab.com/wp-content/uploads/2009/09/DualPivotQuicksort.pdf + // + // Modified to allow partial sorting (partitioning): + // - Ignore insertion sort for tiny array (handled by calling code). + // - Ignore recursive calls for a full sort (handled by calling code). + // - Change to fast-forward over initial ascending / descending runs. + // - Change to fast-forward great when v > v2 and either break the sorting + // loop, or move a[great] direct to the correct location. + // - Change to use the 2nd and 4th of 5 elements for the pivots. + // - Identify a large central region using ~5/8 of the length to trigger search for + // equal values. + // + // For some indices and data a full sort of the data will be faster; this is impossible to + // predict on unknown data input and attempts to analyse the indices and data impact + // performance for the majority of use cases where sorting is not a suitable choice. + // Use of the sortselect finisher allows the current multiple indices method to degrade + // to a (non-optimised) dual-pivot quicksort (see below). + // + // heapselect vs sortselect + // + // Quickselect can switch to an alternative when: the range is very small + // (e.g. insertion sort); or the target index is close to the end (e.g. heapselect). + // Small ranges and a target index close to the end are handled using a hybrid of insertion + // sort and selection (sortselect). This is faster than heapselect for small distance from + // the edge (m) for a single index and has the advantage of sorting all upstream values from + // the target index (heapselect requires push-down of each successive value to sort). This + // allows the dual-pivot quickselect on multiple indices that saturate the range to degrade + // to a (non-optimised) dual-pivot quicksort. However sortselect is worst case Order(m * (r-l)) + // for range [l, r] so cannot be used when quickselect fails to converge as m may be very large. + // Thus heapselect is used as the stopper algorithm when quickselect progress is slow on + // multiple indices. If heapselect is used for small range handling the performance on + // saturated indices is significantly slower. Hence the presence of two final selection + // methods for different purposes. + + /** Sampling mode using Floyd-Rivest sampling. */ + static final int MODE_FR_SAMPLING = -1; + /** Sampling mode. */ + static final int MODE_SAMPLING = 0; + /** No sampling but use adaption of the target k. */ + static final int MODE_ADAPTION = 1; + /** No sampling and no adaption of target k (strict margins). */ + static final int MODE_STRICT = 2; + + /** Minimum size for sortselect. + * Below this perform a sort rather than selection. This is used to avoid + * sort select on tiny data. */ + private static final int MIN_SORTSELECT_SIZE = 4; + /** Single-pivot sortselect size for quickselect adaptive. Note that quickselect adaptive + * recursively calls quickselect so very small lengths are included with an initial medium + * length. Using lengths of 1023-5 and 2043-53 indicate optimum performance around 20-30. + * Note: The expand partition function assumes a sample of at least length 2 as each end + * of the sample is used as a sentinel; this imposes a minimum length of 24 on the range + * to ensure it contains a 12-th tile of length 2. Thus the absolute minimum for the + * distance from the edge is 12. */ + private static final int LINEAR_SORTSELECT_SIZE = 24; + /** Dual-pivot sortselect size for the distance of a single k from the edge of the + * range length n. Benchmarking in range [81+81, 243+243] suggests a value of ~20 (or + * higher on some hardware). Ranges are chosen based on third interval spacing between + * powers of 3. + * + *
Sortselect is faster at this small size than heapselect. A second advantage is + * that all indices closer to the edge than the target index are also sorted. This + * allows selection of multiple close indices to be performed with effectively the + * same speed. High density indices will result in recursion to very short fragments + * which also trigger use of sort select. The threshold for sorting short lengths is + * configured in {@link #dualPivotSortSelectSize(int, int)}. */ + private static final int DP_SORTSELECT_SIZE = 20; + /** Threshold to use Floyd-Rivest sub-sampling. This partitions a sample of the data to + * identify a pivot so that the target element is in the smaller set after partitioning. + * The original FR paper used 600 otherwise reverted to the target index as the pivot. + * This implementation reverts to quickselect adaptive which increases robustness + * at small size on a variety of data and allows raising the original FR threshold. */ + private static final int FR_SAMPLING_SIZE = 1200; + + /** Increment used for the recursion counter. The counter will overflow to negative when + * recursion has exceeded the maximum level. The counter is maintained in the upper bits + * of the dual-pivot control flags. */ + private static final int RECURSION_INCREMENT = 1 << 20; + /** Mask to extract the sort select size from the dual-pivot control flags. Currently + * the bits below those used for the recursion counter are only used for the sort select size + * so this can use a mask with all bits below the increment. */ + private static final int SORTSELECT_MASK = RECURSION_INCREMENT - 1; + + /** Threshold to use repeated step left: 7 / 16. */ + private static final double STEP_LEFT = 0.4375; + /** Threshold to use repeated step right: 9 / 16. */ + private static final double STEP_RIGHT = 0.5625; + /** Threshold to use repeated step far-left: 1 / 12. */ + private static final double STEP_FAR_LEFT = 0.08333333333333333; + /** Threshold to use repeated step far-right: 11 / 12. */ + private static final double STEP_FAR_RIGHT = 0.9166666666666666; + + /** No instances. */ + private QuickSelect() {} + + /** + * Partition the elements between {@code ka} and {@code kb} using a heap select + * algorithm. It is assumed {@code left <= ka <= kb <= right}. + * + * @param a Data array to use to find out the Kth value. + * @param left Lower bound (inclusive). + * @param right Upper bound (inclusive). + * @param ka Lower index to select. + * @param kb Upper index to select. + */ + static void heapSelect(double[] a, int left, int right, int ka, int kb) { + if (right <= left) { + return; + } + // Use the smallest heap + if (kb - left < right - ka) { + heapSelectLeft(a, left, right, ka, kb); + } else { + heapSelectRight(a, left, right, ka, kb); + } + } + + /** + * Partition the elements between {@code ka} and {@code kb} using a heap select + * algorithm. It is assumed {@code left <= ka <= kb <= right}. + * + *
For best performance this should be called with {@code k} in the lower + * half of the range. + * + * @param a Data array to use to find out the Kth value. + * @param left Lower bound (inclusive). + * @param right Upper bound (inclusive). + * @param ka Lower index to select. + * @param kb Upper index to select. + */ + static void heapSelectLeft(double[] a, int left, int right, int ka, int kb) { + // Create a max heap in-place in [left, k], rooted at a[left] = max + // |l|-max-heap-|k|--------------| + // Build the heap using Floyd's heap-construction algorithm for heap size n. + // Start at parent of the last element in the heap (k), + // i.e. start = parent(n-1) : parent(c) = floor((c - 1) / 2) : c = k - left + int end = kb + 1; + for (int p = left + ((kb - left - 1) >> 1); p >= left; p--) { + maxHeapSiftDown(a, a[p], p, left, end); + } + // Scan the remaining data and insert + // Mitigate worst case performance on descending data by backward sweep + double max = a[left]; + for (int i = right + 1; --i > kb;) { + final double v = a[i]; + if (v < max) { + a[i] = max; + maxHeapSiftDown(a, v, left, left, end); + max = a[left]; + } + } + // Partition [ka, kb] + // |l|-max-heap-|k|--------------| + // | <-swap-> | then sift down reduced size heap + // Avoid sifting heap of size 1 + final int last = Math.max(left, ka - 1); + while (--end > last) { + maxHeapSiftDown(a, a[end], left, left, end); + a[end] = max; + max = a[left]; + } + } + + /** + * Sift the element down the max heap. + * + *
Assumes {@code root <= p < end}, i.e. the max heap is above root. + * + * @param a Heap data. + * @param v Value to sift. + * @param p Start position. + * @param root Root of the heap. + * @param end End of the heap (exclusive). + */ + private static void maxHeapSiftDown(double[] a, double v, int p, int root, int end) { + // child2 = root + 2 * (parent - root) + 2 + // = 2 * parent - root + 2 + while (true) { + // Right child + int c = (p << 1) - root + 2; + if (c > end) { + // No left child + break; + } + // Use the left child if right doesn't exist, or it is greater + if (c == end || a[c] < a[c - 1]) { + --c; + } + if (v >= a[c]) { + // Parent greater than largest child - done + break; + } + // Swap and descend + a[p] = a[c]; + p = c; + } + a[p] = v; + } + + /** + * Partition the elements between {@code ka} and {@code kb} using a heap select + * algorithm. It is assumed {@code left <= ka <= kb <= right}. + * + *
For best performance this should be called with {@code k} in the upper + * half of the range. + * + * @param a Data array to use to find out the Kth value. + * @param left Lower bound (inclusive). + * @param right Upper bound (inclusive). + * @param ka Lower index to select. + * @param kb Upper index to select. + */ + static void heapSelectRight(double[] a, int left, int right, int ka, int kb) { + // Create a min heap in-place in [k, right], rooted at a[right] = min + // |--------------|k|-min-heap-|r| + // Build the heap using Floyd's heap-construction algorithm for heap size n. + // Start at parent of the last element in the heap (k), + // i.e. start = parent(n-1) : parent(c) = floor((c - 1) / 2) : c = right - k + int end = ka - 1; + for (int p = right - ((right - ka - 1) >> 1); p <= right; p++) { + minHeapSiftDown(a, a[p], p, right, end); + } + // Scan the remaining data and insert + // Mitigate worst case performance on descending data by backward sweep + double min = a[right]; + for (int i = left - 1; ++i < ka;) { + final double v = a[i]; + if (v > min) { + a[i] = min; + minHeapSiftDown(a, v, right, right, end); + min = a[right]; + } + } + // Partition [ka, kb] + // |--------------|k|-min-heap-|r| + // | <-swap-> | then sift down reduced size heap + // Avoid sifting heap of size 1 + final int last = Math.min(right, kb + 1); + while (++end < last) { + minHeapSiftDown(a, a[end], right, right, end); + a[end] = min; + min = a[right]; + } + } + + /** + * Sift the element down the min heap. + * + *
Assumes {@code root >= p > end}, i.e. the max heap is below root. + * + * @param a Heap data. + * @param v Value to sift. + * @param p Start position. + * @param root Root of the heap. + * @param end End of the heap (exclusive). + */ + private static void minHeapSiftDown(double[] a, double v, int p, int root, int end) { + // child2 = root - 2 * (root - parent) - 2 + // = 2 * parent - root - 2 + while (true) { + // Right child + int c = (p << 1) - root - 2; + if (c < end) { + // No left child + break; + } + // Use the left child if right doesn't exist, or it is less + if (c == end || a[c] > a[c + 1]) { + ++c; + } + if (v <= a[c]) { + // Parent less than smallest child - done + break; + } + // Swap and descend + a[p] = a[c]; + p = c; + } + a[p] = v; + } + + /** + * Partition the elements between {@code ka} and {@code kb} using a sort select + * algorithm. It is assumed {@code left <= ka <= kb <= right}. + * + * @param a Data array to use to find out the Kth value. + * @param left Lower bound (inclusive). + * @param right Upper bound (inclusive). + * @param ka Lower index to select. + * @param kb Upper index to select. + */ + static void sortSelect(double[] a, int left, int right, int ka, int kb) { + // Combine the test for right <= left with + // avoiding the overhead of sort select on tiny data. + if (right - left <= MIN_SORTSELECT_SIZE) { + Sorting.sort(a, left, right); + return; + } + // Sort the smallest side + if (kb - left < right - ka) { + sortSelectLeft(a, left, right, kb); + } else { + sortSelectRight(a, left, right, ka); + } + } + + /** + * Partition the minimum {@code n} elements below {@code k} where + * {@code n = k - left + 1}. Uses an insertion sort algorithm. + * + *
Works with any {@code k} in the range {@code left <= k <= right} + * and performs a full sort of the range below {@code k}. + * + *
For best performance this should be called with + * {@code k - left < right - k}, i.e. + * to partition a value in the lower half of the range. + * + * @param a Data array to use to find out the Kth value. + * @param left Lower bound (inclusive). + * @param right Upper bound (inclusive). + * @param k Index to select. + */ + static void sortSelectLeft(double[] a, int left, int right, int k) { + // Sort + for (int i = left; ++i <= k;) { + final double v = a[i]; + // Move preceding higher elements above (if required) + if (v < a[i - 1]) { + int j = i; + while (--j >= left && v < a[j]) { + a[j + 1] = a[j]; + } + a[j + 1] = v; + } + } + // Scan the remaining data and insert + // Mitigate worst case performance on descending data by backward sweep + double m = a[k]; + for (int i = right + 1; --i > k;) { + final double v = a[i]; + if (v < m) { + a[i] = m; + int j = k; + while (--j >= left && v < a[j]) { + a[j + 1] = a[j]; + } + a[j + 1] = v; + m = a[k]; + } + } + } + + /** + * Partition the maximum {@code n} elements above {@code k} where + * {@code n = right - k + 1}. Uses an insertion sort algorithm. + * + *
Works with any {@code k} in the range {@code left <= k <= right} + * and can be used to perform a full sort of the range above {@code k}. + * + *
For best performance this should be called with + * {@code k - left > right - k}, i.e. + * to partition a value in the upper half of the range. + * + * @param a Data array to use to find out the Kth value. + * @param left Lower bound (inclusive). + * @param right Upper bound (inclusive). + * @param k Index to select. + */ + static void sortSelectRight(double[] a, int left, int right, int k) { + // Sort + for (int i = right; --i >= k;) { + final double v = a[i]; + // Move succeeding lower elements below (if required) + if (v > a[i + 1]) { + int j = i; + while (++j <= right && v > a[j]) { + a[j - 1] = a[j]; + } + a[j - 1] = v; + } + } + // Scan the remaining data and insert + // Mitigate worst case performance on descending data by backward sweep + double m = a[k]; + for (int i = left - 1; ++i < k;) { + final double v = a[i]; + if (v > m) { + a[i] = m; + int j = k; + while (++j <= right && v > a[j]) { + a[j - 1] = a[j]; + } + a[j - 1] = v; + m = a[k]; + } + } + } + + /** + * Partition the array such that index {@code k} corresponds to its correctly + * sorted value in the equivalent fully sorted array. + * + *
Assumes {@code k} is a valid index into [left, right]. + * + * @param a Values. + * @param left Lower bound of data (inclusive). + * @param right Upper bound of data (inclusive). + * @param k Index. + */ + static void select(double[] a, int left, int right, int k) { + quickSelectAdaptive(a, left, right, k, k, new int[1], MODE_FR_SAMPLING); + } + + /** + * Partition the array such that indices {@code k} correspond to their correctly + * sorted value in the equivalent fully sorted array. + * + *
The count of the number of used indices is returned. If the keys are sorted in-place, + * the count is returned as a negative. + * + * @param a Values. + * @param left Lower bound of data (inclusive). + * @param right Upper bound of data (inclusive). + * @param k Indices (may be destructively modified). + * @param n Count of indices. + * @return the count of used indices + * @throws IndexOutOfBoundsException if any index {@code k} is not within the + * sub-range {@code [left, right]} + */ + static int select(double[] a, int left, int right, int[] k, int n) { + if (n < 1) { + return 0; + } + if (n == 1) { + quickSelectAdaptive(a, left, right, k[0], k[0], new int[1], MODE_FR_SAMPLING); + return -1; + } + + // Interval creation validates the indices are in [left, right] + final UpdatingInterval keys = IndexSupport.createUpdatingInterval(left, right, k, n); + + // Save number of used indices + final int count = IndexSupport.countIndices(keys, n); + + // Note: If the keys are not separated then they are effectively a single key. + // Any split of keys separated by the sort select size + // will be finished on the next iteration. + final int k1 = keys.left(); + final int kn = keys.right(); + if (kn - k1 < DP_SORTSELECT_SIZE) { + quickSelectAdaptive(a, left, right, k1, kn, new int[1], MODE_FR_SAMPLING); + } else { + // Dual-pivot mode with small range sort length configured using index density + dualPivotQuickSelect(a, left, right, keys, dualPivotFlags(left, right, k1, kn)); + } + return count; + } + + /** + * Partition the array such that indices {@code k} correspond to their + * correctly sorted value in the equivalent fully sorted array. + * + *
For all indices {@code [ka, kb]} and any index {@code i}: + * + *
{@code + * data[i < ka] <= data[ka] <= data[kb] <= data[kb < i] + * }+ * + *
This function accepts indices {@code [ka, kb]} that define the + * range of indices to partition. It is expected that the range is small. + * + *
The {@code flags} are used to control the sampling mode and adaption of + * the index within the sample. + * + *
Returns the bounds containing {@code [ka, kb]}. These may be lower/higher + * than the keys if equal values are present in the data. + * + * @param a Values. + * @param left Lower bound of data (inclusive, assumed to be strictly positive). + * @param right Upper bound of data (inclusive, assumed to be strictly positive). + * @param ka First key of interest. + * @param kb Last key of interest. + * @param bounds Upper bound of the range containing {@code [ka, kb]} (inclusive). + * @param flags Adaption flags. + * @return Lower bound of the range containing {@code [ka, kb]} (inclusive). + */ + static int quickSelectAdaptive(double[] a, int left, int right, int ka, int kb, + int[] bounds, int flags) { + int l = left; + int r = right; + int m = flags; + while (true) { + // Select when ka and kb are close to the same end + // |l|-----|ka|kkkkkkkk|kb|------|r| + if (Math.min(kb - l, r - ka) < LINEAR_SORTSELECT_SIZE) { + sortSelect(a, l, r, ka, kb); + bounds[0] = kb; + return ka; + } + + // Only target ka; kb is assumed to be close + int p0; + final int n = r - l; + // f in [0, 1] + final double f = (double) (ka - l) / n; + // Record the larger margin (start at 1/4) to create the estimated size. + // step L R + // far left 1/12 1/3 (use 1/4 + 1/32 + 1/64 ~ 0.328) + // left 1/6 1/4 + // middle 2/9 2/9 (use 1/4 - 1/32 ~ 0.219) + int margin = n >> 2; + if (m < MODE_SAMPLING && r - l > FR_SAMPLING_SIZE) { + // Floyd-Rivest sample step uses the same margins + p0 = sampleStep(a, l, r, ka, bounds); + if (f <= STEP_FAR_LEFT || f >= STEP_FAR_RIGHT) { + margin += (n >> 5) + (n >> 6); + } else if (f > STEP_LEFT && f < STEP_RIGHT) { + margin -= n >> 5; + } + } else if (f <= STEP_LEFT) { + if (f <= STEP_FAR_LEFT) { + margin += (n >> 5) + (n >> 6); + p0 = repeatedStepFarLeft(a, l, r, ka, bounds, m); + } else { + p0 = repeatedStepLeft(a, l, r, ka, bounds, m); + } + } else if (f >= STEP_RIGHT) { + if (f >= STEP_FAR_RIGHT) { + margin += (n >> 5) + (n >> 6); + p0 = repeatedStepFarRight(a, l, r, ka, bounds, m); + } else { + p0 = repeatedStepRight(a, l, r, ka, bounds, m); + } + } else { + margin -= n >> 5; + p0 = repeatedStep(a, l, r, ka, bounds, m); + } + + // Note: Here we expect [ka, kb] to be small and splitting is unlikely. + // p0 p1 + // |l|--|ka|kkkk|kb|--|P|-------------------|r| + // |l|----------------|P|--|ka|kkk|kb|------|r| + // |l|-----------|ka|k|P|k|kb|--------------|r| + final int p1 = bounds[0]; + if (kb < p0) { + // Entirely on left side + r = p0 - 1; + } else if (ka > p1) { + // Entirely on right side + l = p1 + 1; + } else { + // Pivot splits [ka, kb]. Expect ends to be close to the pivot and finish. + // Here we set the bounds for use after median-of-medians pivot selection. + // In the event there are many equal values this allows collecting those + // known to be equal together when moving around the medians sample. + if (kb > p1) { + sortSelectLeft(a, p1 + 1, r, kb); + bounds[0] = kb; + } + if (ka < p0) { + sortSelectRight(a, l, p0 - 1, ka); + p0 = ka; + } + return p0; + } + // Update mode based on target partition size + if (r - l > n - margin) { + m++; + } + } + } + + /** + * Partition an array slice around a pivot. Partitioning exchanges array elements such + * that all elements smaller than pivot are before it and all elements larger than + * pivot are after it. + * + *
Partitions a Floyd-Rivest sample around a pivot offset so that the input {@code k} will + * fall in the smaller partition when the entire range is partitioned. + * + *
Assumes the range {@code r - l} is large. + * + * @param a Data array. + * @param l Lower bound (inclusive). + * @param r Upper bound (inclusive). + * @param k Target index. + * @param upper Upper bound (inclusive) of the pivot range. + * @return Lower bound (inclusive) of the pivot range. + */ + private static int sampleStep(double[] a, int l, int r, int k, int[] upper) { + // Floyd-Rivest: use SELECT recursively on a sample of size S to get an estimate + // for the (k-l+1)-th smallest element into a[k], biased slightly so that the + // (k-l+1)-th element is expected to lie in the smaller set after partitioning. + final int n = r - l + 1; + final int ith = k - l + 1; + final double z = Math.log(n); + // sample size = 0.5 * n^(2/3) + final double s = 0.5 * Math.exp(0.6666666666666666 * z); + final double sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * Integer.signum(ith - (n >> 1)); + final int ll = Math.max(l, (int) (k - ith * s / n + sd)); + final int rr = Math.min(r, (int) (k + (n - ith) * s / n + sd)); + // Sample recursion restarts from [ll, rr] + final int p = quickSelectAdaptive(a, ll, rr, k, k, upper, MODE_FR_SAMPLING); + return expandPartition(a, l, r, ll, rr, p, upper[0], upper); + } + + /** + * Partition an array slice around a pivot. Partitioning exchanges array elements such + * that all elements smaller than pivot are before it and all elements larger than + * pivot are after it. + * + *
Assumes the range {@code r - l >= 8}; the caller is responsible for selection on a smaller + * range. If using a 12th-tile for sampling then assumes {@code r - l >= 11}. + * + *
Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm + * with the median of 3 then median of 3; the final sample is placed in the + * 5th 9th-tile; the pivot chosen from the sample is adaptive using the input {@code k}. + * + *
Given a pivot in the middle of the sample this has margins of 2/9 and 2/9. + * + * @param a Data array. + * @param l Lower bound (inclusive). + * @param r Upper bound (inclusive). + * @param k Target index. + * @param upper Upper bound (inclusive) of the pivot range. + * @param flags Control flags. + * @return Lower bound (inclusive) of the pivot range. + */ + private static int repeatedStep(double[] a, int l, int r, int k, int[] upper, int flags) { + // Adapted from Alexandrescu (2016), algorithm 8. + int fp; + int s; + int p; + if (flags <= MODE_SAMPLING) { + // Median into a 12th-tile + fp = (r - l + 1) / 12; + // Position the sample around the target k + s = k - mapDistance(k - l, l, r, fp); + p = k; + } else { + // i in tertile [3f':6f') + fp = (r - l + 1) / 9; + final int f3 = 3 * fp; + final int end = l + (f3 << 1); + for (int i = l + f3; i < end; i++) { + Sorting.sort3(a, i - f3, i, i + f3); + } + // 5th 9th-tile: [4f':5f') + s = l + (fp << 2); + // No adaption uses the middle to enforce strict margins + p = s + (flags == MODE_ADAPTION ? mapDistance(k - l, l, r, fp) : (fp >>> 1)); + } + final int e = s + fp - 1; + for (int i = s; i <= e; i++) { + Sorting.sort3(a, i - fp, i, i + fp); + } + p = quickSelectAdaptive(a, s, e, p, p, upper, MODE_FR_SAMPLING); + return expandPartition(a, l, r, s, e, p, upper[0], upper); + } + + /** + * Partition an array slice around a pivot. Partitioning exchanges array elements such + * that all elements smaller than pivot are before it and all elements larger than + * pivot are after it. + * + *
Assumes the range {@code r - l >= 11}; the caller is responsible for selection on a smaller + * range. + * + *
Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm + * with the lower median of 4 then either median of 3 with the final sample placed in the + * 5th 12th-tile, or min of 3 with the final sample in the 4th 12th-tile; + * the pivot chosen from the sample is adaptive using the input {@code k}. + * + *
Given a pivot in the middle of the sample this has margins of 1/6 and 1/4. + * + * @param a Data array. + * @param l Lower bound (inclusive). + * @param r Upper bound (inclusive). + * @param k Target index. + * @param upper Upper bound (inclusive) of the pivot range. + * @param flags Control flags. + * @return Lower bound (inclusive) of the pivot range. + */ + private static int repeatedStepLeft(double[] a, int l, int r, int k, int[] upper, int flags) { + // Adapted from Alexandrescu (2016), algorithm 9. + int fp; + int s; + int p; + if (flags <= MODE_SAMPLING) { + // Median into a 12th-tile + fp = (r - l + 1) / 12; + // Position the sample around the target k + // Avoid bounds error due to rounding as (k-l)/(r-l) -> 1/12 + s = Math.max(k - mapDistance(k - l, l, r, fp), l + fp); + p = k; + } else { + // i in 2nd quartile + final int f = (r - l + 1) >> 2; + final int f2 = f + f; + final int end = l + f2; + for (int i = l + f; i < end; i++) { + Sorting.lowerMedian4(a, i - f, i, i + f, i + f2); + } + // i in 5th 12th-tile + fp = f / 3; + s = l + f + fp; + // No adaption uses the middle to enforce strict margins + p = s + (flags == MODE_ADAPTION ? mapDistance(k - l, l, r, fp) : (fp >>> 1)); + } + final int e = s + fp - 1; + for (int i = s; i <= e; i++) { + Sorting.sort3(a, i - fp, i, i + fp); + } + p = quickSelectAdaptive(a, s, e, p, p, upper, MODE_FR_SAMPLING); + return expandPartition(a, l, r, s, e, p, upper[0], upper); + } + + /** + * Partition an array slice around a pivot. Partitioning exchanges array elements such + * that all elements smaller than pivot are before it and all elements larger than + * pivot are after it. + * + *
Assumes the range {@code r - l >= 11}; the caller is responsible for selection on a smaller + * range. + * + *
Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm + * with the upper median of 4 then either median of 3 with the final sample placed in the + * 8th 12th-tile, or max of 3 with the final sample in the 9th 12th-tile; + * the pivot chosen from the sample is adaptive using the input {@code k}. + * + *
Given a pivot in the middle of the sample this has margins of 1/4 and 1/6. + * + * @param a Data array. + * @param l Lower bound (inclusive). + * @param r Upper bound (inclusive). + * @param k Target index. + * @param upper Upper bound (inclusive) of the pivot range. + * @param flags Control flags. + * @return Lower bound (inclusive) of the pivot range. + */ + private static int repeatedStepRight(double[] a, int l, int r, int k, int[] upper, int flags) { + // Mirror image repeatedStepLeft using upper median into 3rd quartile + int fp; + int e; + int p; + if (flags <= MODE_SAMPLING) { + // Median into a 12th-tile + fp = (r - l + 1) / 12; + // Position the sample around the target k + // Avoid bounds error due to rounding as (r-k)/(r-l) -> 11/12 + e = Math.min(k + mapDistance(r - k, l, r, fp), r - fp); + p = k; + } else { + // i in 3rd quartile + final int f = (r - l + 1) >> 2; + final int f2 = f + f; + final int end = r - f2; + for (int i = r - f; i > end; i--) { + Sorting.upperMedian4(a, i - f2, i - f, i, i + f); + } + // i in 8th 12th-tile + fp = f / 3; + e = r - f - fp; + // No adaption uses the middle to enforce strict margins + p = e - (flags == MODE_ADAPTION ? mapDistance(r - k, l, r, fp) : (fp >>> 1)); + } + final int s = e - fp + 1; + for (int i = s; i <= e; i++) { + Sorting.sort3(a, i - fp, i, i + fp); + } + p = quickSelectAdaptive(a, s, e, p, p, upper, MODE_FR_SAMPLING); + return expandPartition(a, l, r, s, e, p, upper[0], upper); + } + + /** + * Partition an array slice around a pivot. Partitioning exchanges array elements such + * that all elements smaller than pivot are before it and all elements larger than + * pivot are after it. + * + *
Assumes the range {@code r - l >= 11}; the caller is responsible for selection on a smaller + * range. + * + *
Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm + * with the minimum of 4 then median of 3; the final sample is placed in the + * 2nd 12th-tile; the pivot chosen from the sample is adaptive using the input {@code k}. + * + *
Given a pivot in the middle of the sample this has margins of 1/12 and 1/3. + * + * @param a Data array. + * @param l Lower bound (inclusive). + * @param r Upper bound (inclusive). + * @param k Target index. + * @param upper Upper bound (inclusive) of the pivot range. + * @param flags Control flags. + * @return Lower bound (inclusive) of the pivot range. + */ + private static int repeatedStepFarLeft(double[] a, int l, int r, int k, int[] upper, int flags) { + // Far step has been changed from the Alexandrescu (2016) step of lower-median-of-4, min-of-3 + // into the 4th 12th-tile to a min-of-4, median-of-3 into the 2nd 12th-tile. + // The differences are: + // - The upper margin when not sampling is 8/24 vs. 9/24; the lower margin remains at 1/12. + // - The position of the sample is closer to the expected location of k < |A| / 12. + // - Sampling mode uses a median-of-3 with adaptive k, matching the other step methods. + // A min-of-3 sample can create a pivot too small if used with adaption of k leaving + // k in the larger parition and a wasted iteration. + // - Adaption is adjusted to force use of the lower margin when not sampling. + int fp; + int s; + int p; + if (flags <= MODE_SAMPLING) { + // 2nd 12th-tile + fp = (r - l + 1) / 12; + s = l + fp; + // Use adaption + p = s + mapDistance(k - l, l, r, fp); + } else { + // i in 2nd quartile; min into i-f (1st quartile) + final int f = (r - l + 1) >> 2; + final int f2 = f + f; + final int end = l + f2; + for (int i = l + f; i < end; i++) { + if (a[i + f] < a[i - f]) { + final double u = a[i + f]; + a[i + f] = a[i - f]; + a[i - f] = u; + } + if (a[i + f2] < a[i]) { + final double v = a[i + f2]; + a[i + f2] = a[i]; + a[i] = v; + } + if (a[i] < a[i - f]) { + final double u = a[i]; + a[i] = a[i - f]; + a[i - f] = u; + } + } + // 2nd 12th-tile + fp = f / 3; + s = l + fp; + // Lower margin has 2(d+1) elements; d == (position in sample) - s + // Force k into the lower margin + p = s + ((k - l) >>> 1); + } + final int e = s + fp - 1; + for (int i = s; i <= e; i++) { + Sorting.sort3(a, i - fp, i, i + fp); + } + p = quickSelectAdaptive(a, s, e, p, p, upper, MODE_FR_SAMPLING); + return expandPartition(a, l, r, s, e, p, upper[0], upper); + } + + /** + * Partition an array slice around a pivot. Partitioning exchanges array elements such + * that all elements smaller than pivot are before it and all elements larger than + * pivot are after it. + * + *
Assumes the range {@code r - l >= 11}; the caller is responsible for selection on a smaller + * range. + * + *
Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm + * with the maximum of 4 then median of 3; the final sample is placed in the + * 11th 12th-tile; the pivot chosen from the sample is adaptive using the input {@code k}. + * + *
Given a pivot in the middle of the sample this has margins of 1/3 and 1/12. + * + * @param a Data array. + * @param l Lower bound (inclusive). + * @param r Upper bound (inclusive). + * @param k Target index. + * @param upper Upper bound (inclusive) of the pivot range. + * @param flags Control flags. + * @return Lower bound (inclusive) of the pivot range. + */ + private static int repeatedStepFarRight(double[] a, int l, int r, int k, int[] upper, int flags) { + // Mirror image repeatedStepFarLeft + int fp; + int e; + int p; + if (flags <= MODE_SAMPLING) { + // 11th 12th-tile + fp = (r - l + 1) / 12; + e = r - fp; + // Use adaption + p = e - mapDistance(r - k, l, r, fp); + } else { + // i in 3rd quartile; max into i+f (4th quartile) + final int f = (r - l + 1) >> 2; + final int f2 = f + f; + final int end = r - f2; + for (int i = r - f; i > end; i--) { + if (a[i - f] > a[i + f]) { + final double u = a[i - f]; + a[i - f] = a[i + f]; + a[i + f] = u; + } + if (a[i - f2] > a[i]) { + final double v = a[i - f2]; + a[i - f2] = a[i]; + a[i] = v; + } + if (a[i] > a[i + f]) { + final double u = a[i]; + a[i] = a[i + f]; + a[i + f] = u; + } + } + // 11th 12th-tile + fp = f / 3; + e = r - fp; + // Upper margin has 2(d+1) elements; d == e - (position in sample) + // Force k into the upper margin + p = e - ((r - k) >>> 1); + } + final int s = e - fp + 1; + for (int i = s; i <= e; i++) { + Sorting.sort3(a, i - fp, i, i + fp); + } + p = quickSelectAdaptive(a, s, e, p, p, upper, MODE_FR_SAMPLING); + return expandPartition(a, l, r, s, e, p, upper[0], upper); + } + + /** + * Expand a partition around a single pivot. Partitioning exchanges array + * elements such that all elements smaller than pivot are before it and all + * elements larger than pivot are after it. The central region is already + * partitioned. + * + *
{@code + * |l |s |p0 p1| e| r| + * | ??? |+ * + *P | ??? | + * }
This requires that {@code start != end}. However it handles + * {@code left == start} and/or {@code end == right}. + * + * @param a Data array. + * @param left Lower bound (inclusive). + * @param right Upper bound (inclusive). + * @param start Start of the partition range (inclusive). + * @param end End of the partitioned range (inclusive). + * @param pivot0 Lower pivot location (inclusive). + * @param pivot1 Upper pivot location (inclusive). + * @param upper Upper bound (inclusive) of the pivot range [k1]. + * @return Lower bound (inclusive) of the pivot range [k0]. + */ + // package-private for testing + static int expandPartition(double[] a, int left, int right, int start, int end, + int pivot0, int pivot1, int[] upper) { + // 3-way partition of the data using a pivot value into + // less-than, equal or greater-than. + // Based on Sedgewick's Bentley-McIroy partitioning: always swap i<->j then + // check for equal to the pivot and move again. + // + // Move sentinels from start and end to left and right. Scan towards the + // sentinels until >=,<=. Swap then move == to the pivot region. + // <-i j-> + // |l | | |p0 p1| | | r| + // |>=| ??? | < | == | > | ??? |<=| + // + // When either i or j reach the edge perform finishing loop. + // Finish loop for a[j] <= v replaces j with p1+1, optionally moves value + // to p0 for < and updates the pivot range p1 (and optionally p0): + // j-> + // |l |p0 p1| | | r| + // | < | == | > | ??? |<=| + + final double v = a[pivot0]; + // Use start/end as sentinels (requires start != end) + double vi = a[start]; + double vj = a[end]; + a[start] = a[left]; + a[end] = a[right]; + a[left] = vj; + a[right] = vi; + + int i = start + 1; + int j = end - 1; + + // Positioned for pre-in/decrement to write to pivot region + int p0 = pivot0 == start ? i : pivot0; + int p1 = pivot1 == end ? j : pivot1; + + while (true) { + do { + --i; + } while (a[i] < v); + do { + ++j; + } while (a[j] > v); + vj = a[i]; + vi = a[j]; + a[i] = vi; + a[j] = vj; + // Move the equal values to pivot region + if (vi == v) { + a[i] = a[--p0]; + a[p0] = v; + } + if (vj == v) { + a[j] = a[++p1]; + a[p1] = v; + } + // Termination check and finishing loops. + // Note: This works even if pivot region is zero length (p1 == p0-1 due to + // length 1 pivot region at either start/end) because we pre-inc/decrement + // one side and post-inc/decrement the other side. + if (i == left) { + while (j < right) { + do { + ++j; + } while (a[j] > v); + final double w = a[j]; + // Move upper bound of pivot region + a[j] = a[++p1]; + a[p1] = v; + // Move lower bound of pivot region + if (w != v) { + a[p0] = w; + p0++; + } + } + break; + } + if (j == right) { + while (i > left) { + do { + --i; + } while (a[i] < v); + final double w = a[i]; + // Move lower bound of pivot region + a[i] = a[--p0]; + a[p0] = v; + // Move upper bound of pivot region + if (w != v) { + a[p1] = w; + p1--; + } + } + break; + } + } + + upper[0] = p1; + return p0; + } + + /** + * Partition the array such that indices {@code k} correspond to their + * correctly sorted value in the equivalent fully sorted array. + * + *
For all indices {@code k} and any index {@code i}: + * + *
{@code + * data[i < k] <= data[k] <= data[k < i] + * }+ * + *
This function accepts a {@link UpdatingInterval} of indices {@code k} that define the + * range of indices to partition. The {@link UpdatingInterval} can be narrowed or split as + * partitioning divides the range. + * + *
Uses an introselect variant. The quickselect is a dual-pivot quicksort + * partition method by Vladimir Yaroslavskiy; the fall-back on poor convergence of + * the quickselect is a heapselect. + * + *
The {@code flags} contain the the current recursion count and the configured + * length threshold for {@code r - l} to perform sort select. The count is in the upper + * bits and the threshold is in the lower bits. + * + * @param a Values. + * @param left Lower bound of data (inclusive, assumed to be strictly positive). + * @param right Upper bound of data (inclusive, assumed to be strictly positive). + * @param k Interval of indices to partition (ordered). + * @param flags Control flags. + */ + // package-private for testing + static void dualPivotQuickSelect(double[] a, int left, int right, UpdatingInterval k, int flags) { + // If partitioning splits the interval then recursion is used for the left-most side(s) + // and the right-most side remains within this function. If partitioning does + // not split the interval then it remains within this function. + int l = left; + int r = right; + int f = flags; + int ka = k.left(); + int kb = k.right(); + final int[] upper = {0, 0, 0}; + while (true) { + // Select when ka and kb are close to the same end, + // or the entire range is small + // |l|-----|ka|--------|kb|------|r| + final int n = r - l; + if (Math.min(kb - l, r - ka) < DP_SORTSELECT_SIZE || + n < (f & SORTSELECT_MASK)) { + sortSelect(a, l, r, ka, kb); + return; + } + if (kb - ka < DP_SORTSELECT_SIZE) { + // Switch to single-pivot mode with Floyd-Rivest sub-sampling + quickSelectAdaptive(a, l, r, ka, kb, upper, MODE_FR_SAMPLING); + return; + } + if (f < 0) { + // Excess recursion, switch to heap select + heapSelect(a, l, r, ka, kb); + return; + } + + // Dual-pivot partitioning + final int p0 = partition(a, l, r, upper); + final int p1 = upper[0]; + + // Recursion to max depth + // Note: Here we possibly branch left, middle and right with multiple keys. + // It is possible that the partition has split the keys + // and the recursion proceeds with a reduced set in each region. + // p0 p1 p2 p3 + // |l|--|ka|--k----k--|P|------k--|kb|----|P|----|r| + // kb | ka + f += RECURSION_INCREMENT; + // Recurse left side if required + if (ka < p0) { + if (kb <= p1) { + // Entirely on left side + r = p0 - 1; + if (r < kb) { + kb = k.updateRight(r); + } + continue; + } + dualPivotQuickSelect(a, l, p0 - 1, k.splitLeft(p0, p1), f); + // Here we must process middle and/or right + ka = k.left(); + } else if (kb <= p1) { + // No middle/right side + return; + } else if (ka <= p1) { + // Advance lower bound + ka = k.updateLeft(p1 + 1); + } + // Recurse middle if required + final int p2 = upper[1]; + final int p3 = upper[2]; + if (ka < p2) { + l = p1 + 1; + if (kb <= p3) { + // Entirely in middle + r = p2 - 1; + if (r < kb) { + kb = k.updateRight(r); + } + continue; + } + dualPivotQuickSelect(a, l, p2 - 1, k.splitLeft(p2, p3), f); + ka = k.left(); + } else if (kb <= p3) { + // No right side + return; + } else if (ka <= p3) { + ka = k.updateLeft(p3 + 1); + } + // Continue right + l = p3 + 1; + } + } + + /** + * Partition an array slice around 2 pivots. Partitioning exchanges array elements + * such that all elements smaller than pivot are before it and all elements larger + * than pivot are after it. + * + *
This method returns 4 points describing the pivot ranges of equal values. + * + *
{@code + * |k0 k1| |k2 k3| + * |+ * + *P | + * }
Bounds are set so {@code i < k0}, {@code i > k3} and {@code k1 < i < k2} are
+ * unsorted. When the range {@code [k0, k3]} contains fully sorted elements the result
+ * is set to {@code k1 = k3; k2 == k0}. This can occur if
+ * {@code P1 == P2} or there are zero or one value between the pivots
+ * {@code P1 < v < P2}. Any sort/partition of ranges [left, k0-1], [k1+1, k2-1] and
+ * [k3+1, right] must check the length is {@code > 1}.
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param bounds Points [k1, k2, k3].
+ * @return Lower bound (inclusive) of the pivot range [k0].
+ */
+ private static int partition(double[] a, int left, int right, int[] bounds) {
+ // Pick 2 pivots from 5 approximately uniform through the range.
+ // Spacing is ~ 1/7 made using shifts. Other strategies are equal or much
+ // worse. 1/7 = 5/35 ~ 1/8 + 1/64 : 0.1429 ~ 0.1406
+ // Ensure the value is above zero to choose different points!
+ final int n = right - left;
+ final int step = 1 + (n >>> 3) + (n >>> 6);
+ final int i3 = left + (n >>> 1);
+ final int i2 = i3 - step;
+ final int i1 = i2 - step;
+ final int i4 = i3 + step;
+ final int i5 = i4 + step;
+ Sorting.sort5(a, i1, i2, i3, i4, i5);
+
+ // Partition data using pivots P1 and P2 into less-than, greater-than or between.
+ // Pivot values P1 & P2 are placed at the end. If P1 < P2, P2 acts as a sentinel.
+ // k traverses the unknown region ??? and values moved if less-than or
+ // greater-than:
+ //
+ // left less k great right
+ // |P1| The provides the adaption {@code kf'/|A|} from Alexandrescu (2016) where
+ * {@code k == d}, {@code f' == n} and {@code |A| == r-l+1}.
+ *
+ * For convenience this accepts the input range {@code [l, r]}.
+ *
+ * @param d Distance from the edge in {@code [0, r - l]}.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param n Size of the new range.
+ * @return the mapped distance in [0, n)
+ */
+ private static int mapDistance(int d, int l, int r, int n) {
+ return (int) (d * (n - 1.0) / (r - l));
+ }
+
+ /**
+ * Configure the dual-pivot control flags. This packs the maximum recursion depth and
+ * sort select size into a single integer.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param k1 First key of interest.
+ * @param kn Last key of interest.
+ * @return the flags
+ */
+ private static int dualPivotFlags(int left, int right, int k1, int kn) {
+ final int maxDepth = dualPivotMaxDepth(right - left);
+ final int ss = dualPivotSortSelectSize(k1, kn);
+ return dualPivotFlags(maxDepth, ss);
+ }
+
+ /**
+ * Configure the dual-pivot control flags. This packs the maximum recursion depth and
+ * sort select size into a single integer.
+ *
+ * @param maxDepth Maximum recursion depth.
+ * @param ss Sort select size.
+ * @return the flags
+ */
+ static int dualPivotFlags(int maxDepth, int ss) {
+ // The flags are packed using the upper bits to count back from -1 in
+ // step sizes. The lower bits pack the sort select size.
+ int flags = Integer.MIN_VALUE - maxDepth * RECURSION_INCREMENT;
+ flags &= ~SORTSELECT_MASK;
+ return flags | ss;
+ }
+
+ /**
+ * Compute the maximum recursion depth for dual pivot recursion.
+ * This is an approximation to {@code 2 * log3 (x)}.
+ *
+ * The result is between {@code 2*floor(log3(x))} and {@code 2*ceil(log3(x))}.
+ * The result is correctly rounded when {@code x +/- 1} is a power of 3.
+ *
+ * @param x Value.
+ * @return maximum recursion depth
+ */
+ static int dualPivotMaxDepth(int x) {
+ // log3(2) ~ 1.5849625
+ // log3(x) ~ log2(x) * 0.630929753... ~ log2(x) * 323 / 512 (0.630859375)
+ // Use (floor(log2(x))+1) * 323 / 256
+ return ((32 - Integer.numberOfLeadingZeros(x)) * 323) >>> 8;
+ }
+
+ /**
+ * Configure the sort select size for dual pivot partitioning.
+ *
+ * @param k1 First key of interest.
+ * @param kn Last key of interest.
+ * @return the sort select size.
+ */
+ private static int dualPivotSortSelectSize(int k1, int kn) {
+ // Configure the sort select size based on the index density
+ // l---k1---k---k-----k--k------kn----r
+ //
+ // For a full sort the dual-pivot quicksort can switch to insertion sort
+ // when the length is small. The optimum value is dependent on the
+ // hardware and the insertion sort implementation. Benchmarks show that
+ // insertion sort can be used at length 80-120.
+ //
+ // During selection the SORTSELECT_SIZE specifies the distance from the edge
+ // to use sort select. When keys are not dense there may be a small length
+ // that is ignored by sort select due to the presence of another key.
+ // Diagram of k-l = SORTSELECT_SIZE and r-k < SORTSELECT_SIZE where a second
+ // key b is blocking the use of sort select. The key b is closest it can be to the right
+ // key to enable blocking; it could be further away (up to k = left).
+ //
+ // |--SORTSELECT_SIZE--|
+ // |--SORTSELECT_SIZE--|
+ // l--b----------------k--r
+ // l----b--------------k----r
+ // l------b------------k------r
+ // l--------b----------k--------r
+ // l----------b--------k----------r
+ // l------------b------k------------r
+ // l--------------b----k--------------r
+ // l----------------b--k----------------r
+ // l------------------bk------------------r
+ // |--SORTSELECT_SIZE--|
+ //
+ // For all these cases the partitioning method would have to run. Assuming ideal
+ // dual-pivot partitioning into thirds, and that the left key is randomly positioned
+ // in [left, k) it is more likely that after partitioning 2 partitions will have to
+ // be processed rather than 1 partition. In this case the options are:
+ // - split the range using partitioning; sort select next iteration
+ // - use sort select with a edge distance above the optimum length for single k selection
+ //
+ // Contrast with a longer length:
+ // |--SORTSELECT_SIZE--|
+ // l-------------------k-----k-------k-------------------r
+ // |--SORTSELECT_SIZE--|
+ // Here partitioning has to run and 1, 2, or 3 partitions processed. But all k can
+ // be found with a sort. In this case sort select could be used with a much higher
+ // length (e.g. 80 - 120).
+ //
+ // When keys are extremely sparse (never within SORTSELECT_SIZE) then no switch
+ // to sort select based on length is *required*. It may still be beneficial to avoid
+ // partitioning if the length is very small due to the overhead of partitioning.
+ //
+ // Benchmarking with different lengths for a switch to sort select show inconsistent
+ // behaviour across platforms due to the variable speed of insertion sort at longer
+ // lengths. Attempts to transition the length based on various ramps schemes can
+ // be incorrect and result is a slowdown rather than speed-up (if the correct threshold
+ // is not chosen).
+ //
+ // Here we use a much simpler scheme based on these observations:
+ // - If the average separation is very high then no length will collect extra indices
+ // from a sort select over the current trigger of using the distance from the end. But
+ // using a length dependence will not effect the work done by sort select as it only
+ // performs the minimum sorting required.
+ // - If the average separation is within the SORTSELECT_SIZE then a round of
+ // partitioning will create multiple regions that all require a sort selection.
+ // Thus a partitioning round can be avoided if the length is small.
+ // - If the keys are at the end with nothing in between then partitioning will be able
+ // to split them but a sort will have to sort the entire range:
+ // lk-------------------------------kr
+ // After partitioning starts the chance of keys being at the ends is low as keys
+ // should be random within the divided range.
+ // - Extremely high density keys is rare. It is only expected to saturate the range
+ // with short lengths, e.g. 100 quantiles for length 1000 = separation 10 (high density)
+ // but for length 10000 = separation 100 (low density).
+ // - The density of (non-uniform) keys is hard to predict without complex analysis.
+ //
+ // Benchmarking using random keys at various density show no performance loss from
+ // using a fixed size for the length dependence of sort select, if the size is small.
+ // A large length can impact performance with low density keys, and on machines
+ // where insertion sort is slower. Extreme performance gains occur when the average
+ // separation of random keys is below 8-16, or of uniform keys around 32, by using a
+ // sort at lengths up to 90. But this threshold shows performance loss greater than
+ // the gains with separation of 64-128 on random keys, and on machines with slow
+ // insertion sort. The transition to using an insertion sort of a longer length
+ // is difficult to predict for all situations.
+
+ // Let partitioning run if the initial length is small.
+ // Use kn - k1 as a proxy for the length. If length is actually very large then
+ // the final selection is insignificant. This avoids slowdown for small lengths
+ // where the keys may only be at the ends. Note ideal dual-pivot partitioning
+ // creates thirds so 1 iteration on SORTSELECT_SIZE * 3 should create
+ // SORTSELECT_SIZE partitions.
+ if (kn - k1 < DP_SORTSELECT_SIZE * 3) {
+ return 0;
+ }
+ // Here partitioning will run at least once.
+ // Stable performance across platforms using a modest length dependence.
+ return DP_SORTSELECT_SIZE * 2;
+ }
+}
diff --git a/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/Selection.java b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/Selection.java
new file mode 100644
index 000000000..60c7d73f7
--- /dev/null
+++ b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/Selection.java
@@ -0,0 +1,334 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.arrays;
+
+/**
+ * Select indices in array data.
+ *
+ * Arranges elements such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * Examples:
+ *
+ * This implementation can select on multiple indices and will handle duplicate and
+ * unordered indices. The method detects ordered indices (with or without duplicates) and
+ * uses this during processing. Passing ordered indices is recommended if the order is already
+ * known; for example using uniform spacing through the array data, or to select the top and
+ * bottom {@code n} values from the data.
+ *
+ * A quickselect adaptive method is used for single indices. This uses analysis of the
+ * partition sizes after each division to update the algorithm mode. If the partition
+ * containing the target does not sufficiently reduce in size then the algorithm is
+ * progressively changed to use partitions with guaranteed margins. This ensures a set fraction
+ * of data is eliminated each step and worse-case linear run time performance. This method can
+ * handle a range of indices {@code [ka, kb]} with a small separation by targeting the start of
+ * the range {@code ka} and then selecting the remaining elements {@code (ka, kb]} that are at
+ * the edge of the partition bounded by {@code ka}.
+ *
+ * Multiple keys are partitioned collectively using an introsort method which only recurses
+ * into partitions containing indices. Excess recursion will trigger use of a heapselect
+ * on the remaining range of indices ensuring non-quadratic worse case performance. Any
+ * partition containing a single index, adjacent pair of indices, or range of indices with a
+ * small separation will use the quickselect adaptive method for single keys. Note that the
+ * maximum number of times that {@code n} indices can be split is {@code n - 1} before all
+ * indices are handled as singles.
+ *
+ * Floating-point order
+ *
+ * The {@code <} relation does not impose a total order on all floating-point values.
+ * This class respects the ordering imposed by {@link Double#compare(double, double)}.
+ * {@code -0.0} is treated as less than value {@code 0.0}; {@code Double.NaN} is
+ * considered greater than any other value; and all {@code Double.NaN} values are
+ * considered equal.
+ *
+ * References
+ *
+ * Quickselect is introduced in Hoare [1]. This selects an element {@code k} from {@code n}
+ * using repeat division of the data around a partition element, recursing into the
+ * partition that contains {@code k}.
+ *
+ * Introsort/select is introduced in Musser [2]. This detects excess recursion in
+ * quicksort/select and reverts to a heapsort or linear select to achieve an improved worst
+ * case bound.
+ *
+ * Use of sampling to identify a pivot that places {@code k} in the smaller partition is
+ * performed in the SELECT algorithm of Floyd and Rivest [3, 4].
+ *
+ * A worst-case linear time algorithm PICK is described in Blum et al [5]. This uses
+ * the median of medians as a partition element for selection which ensures a minimum fraction of
+ * the elements are eliminated per iteration. This was extended to use an asymmetric pivot choice
+ * with efficient placement of the medians sample location in the QuickselectAdpative algorithm of
+ * Alexandrescu [6].
+ *
+ * This method pre/post-processes the data and indices to respect the ordering
+ * imposed by {@link Double#compare(double, double)}.
+ *
+ * @param fromIndex Index of the first element (inclusive).
+ * @param toIndex Index of the last element (exclusive).
+ * @param a Values.
+ * @param k Index.
+ */
+ private static void doSelect(double[] a, int fromIndex, int toIndex, int k) {
+ if (toIndex - fromIndex <= 1) {
+ return;
+ }
+ // Sort NaN / count signed zeros.
+ // Caution: This loop contributes significantly to the runtime.
+ int cn = 0;
+ int end = toIndex;
+ for (int i = toIndex; --i >= fromIndex;) {
+ final double v = a[i];
+ // Count negative zeros using a sign bit check
+ if (Double.doubleToRawLongBits(v) == Long.MIN_VALUE) {
+ cn++;
+ // Change to positive zero.
+ // Data must be repaired after selection.
+ a[i] = 0.0;
+ } else if (v != v) {
+ // Move NaN to end
+ a[i] = a[--end];
+ a[end] = v;
+ }
+ }
+
+ // Partition
+ if (end - fromIndex > 1 && k < end) {
+ QuickSelect.select(a, fromIndex, end - 1, k);
+ }
+
+ // Restore signed zeros
+ if (cn != 0) {
+ // Use partition index below zero to fast-forward to zero as much as possible
+ for (int j = a[k] < 0 ? k : -1;;) {
+ if (a[++j] == 0) {
+ a[j] = -0.0;
+ if (--cn == 0) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array.
+ *
+ * This method pre/post-processes the data and indices to respect the ordering
+ * imposed by {@link Double#compare(double, double)}.
+ *
+ * @param fromIndex Index of the first element (inclusive).
+ * @param toIndex Index of the last element (exclusive).
+ * @param a Values.
+ * @param k Indices (may be destructively modified).
+ */
+ private static void doSelect(double[] a, int fromIndex, int toIndex, int[] k) {
+ if (k.length == 0 || toIndex - fromIndex <= 1) {
+ return;
+ }
+ // Sort NaN / count signed zeros.
+ // Caution: This loop contributes significantly to the runtime for single indices.
+ int cn = 0;
+ int end = toIndex;
+ for (int i = toIndex; --i >= fromIndex;) {
+ final double v = a[i];
+ // Count negative zeros using a sign bit check
+ if (Double.doubleToRawLongBits(v) == Long.MIN_VALUE) {
+ cn++;
+ // Change to positive zero.
+ // Data must be repaired after selection.
+ a[i] = 0.0;
+ } else if (v != v) {
+ // Move NaN to end
+ a[i] = a[--end];
+ a[end] = v;
+ }
+ }
+
+ // Partition
+ int n = 0;
+ if (end - fromIndex > 1) {
+ n = k.length;
+ // Filter indices invalidated by NaN check
+ if (end < toIndex) {
+ for (int i = n; --i >= 0;) {
+ final int index = k[i];
+ if (index >= end) {
+ // Move to end
+ k[i] = k[--n];
+ k[n] = index;
+ }
+ }
+ }
+ // Return n, the count of used indices in k.
+ // Use this to post-process zeros.
+ n = QuickSelect.select(a, fromIndex, end - 1, k, n);
+ }
+
+ // Restore signed zeros
+ if (cn != 0) {
+ // Use partition indices below zero to fast-forward to zero as much as possible
+ int j = -1;
+ if (n < 0) {
+ // Binary search on -n sorted indices: hi = (-n) - 1
+ int lo = 0;
+ int hi = ~n;
+ while (lo <= hi) {
+ final int mid = (lo + hi) >>> 1;
+ if (a[k[mid]] < 0) {
+ j = mid;
+ lo = mid + 1;
+ } else {
+ hi = mid - 1;
+ }
+ }
+ } else {
+ // Unsorted, process all indices
+ for (int i = n; --i >= 0;) {
+ if (a[k[i]] < 0) {
+ j = k[i];
+ }
+ }
+ }
+ for (;;) {
+ if (a[++j] == 0) {
+ a[j] = -0.0;
+ if (--cn == 0) {
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/Sorting.java b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/Sorting.java
new file mode 100644
index 000000000..bfd06aaeb
--- /dev/null
+++ b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/Sorting.java
@@ -0,0 +1,341 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.arrays;
+
+import java.util.Arrays;
+
+/**
+ * Support class for sorting arrays.
+ *
+ * Optimal sorting networks are used for small fixed size array sorting.
+ *
+ * Note: Requires that the floating-point data contains no NaN values; sorting
+ * does not respect the order of signed zeros imposed by {@link Double#compare(double, double)}.
+ *
+ * @see Sorting network (Wikipedia)
+ * @see Sorting Networks (Bert Dobbelaere)
+ *
+ * @since 1.2
+ */
+final class Sorting {
+
+ /** No instances. */
+ private Sorting() {}
+
+ /**
+ * Sorts an array using an insertion sort.
+ *
+ * @param x Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void sort(double[] x, int left, int right) {
+ for (int i = left; ++i <= right;) {
+ final double v = x[i];
+ // Move preceding higher elements above (if required)
+ if (v < x[i - 1]) {
+ int j = i;
+ while (--j >= left && v < x[j]) {
+ x[j + 1] = x[j];
+ }
+ x[j + 1] = v;
+ }
+ }
+ }
+
+ /**
+ * Sorts the elements at the given distinct indices in an array.
+ *
+ * @param x Data array.
+ * @param a Index.
+ * @param b Index.
+ * @param c Index.
+ */
+ static void sort3(double[] x, int a, int b, int c) {
+ // Decision tree avoiding swaps:
+ // Order [(0,2)]
+ // Move point 1 above point 2 or below point 0
+ final double u = x[a];
+ final double v = x[b];
+ final double w = x[c];
+ if (w < u) {
+ if (v < w) {
+ x[a] = v;
+ x[b] = w;
+ x[c] = u;
+ return;
+ }
+ if (u < v) {
+ x[a] = w;
+ x[b] = u;
+ x[c] = v;
+ return;
+ }
+ // z < y < z
+ x[a] = w;
+ x[c] = u;
+ return;
+ }
+ if (v < u) {
+ // y < x < z
+ x[a] = v;
+ x[b] = u;
+ return;
+ }
+ if (w < v) {
+ // x < z < y
+ x[b] = w;
+ x[c] = v;
+ }
+ // x < y < z
+ }
+
+ /**
+ * Sorts the elements at the given distinct indices in an array.
+ *
+ * @param x Data array.
+ * @param a Index.
+ * @param b Index.
+ * @param c Index.
+ * @param d Index.
+ * @param e Index.
+ */
+ static void sort5(double[] x, int a, int b, int c, int d, int e) {
+ // Uses an optimal sorting network from Knuth's Art of Computer Programming.
+ // 9 comparisons.
+ // Order pairs:
+ // [(0,3),(1,4)]
+ // [(0,2),(1,3)]
+ // [(0,1),(2,4)]
+ // [(1,2),(3,4)]
+ // [(2,3)]
+ if (x[e] < x[b]) {
+ final double u = x[e];
+ x[e] = x[b];
+ x[b] = u;
+ }
+ if (x[d] < x[a]) {
+ final double v = x[d];
+ x[d] = x[a];
+ x[a] = v;
+ }
+
+ if (x[d] < x[b]) {
+ final double u = x[d];
+ x[d] = x[b];
+ x[b] = u;
+ }
+ if (x[c] < x[a]) {
+ final double v = x[c];
+ x[c] = x[a];
+ x[a] = v;
+ }
+
+ if (x[e] < x[c]) {
+ final double u = x[e];
+ x[e] = x[c];
+ x[c] = u;
+ }
+ if (x[b] < x[a]) {
+ final double v = x[b];
+ x[b] = x[a];
+ x[a] = v;
+ }
+
+ if (x[e] < x[d]) {
+ final double u = x[e];
+ x[e] = x[d];
+ x[d] = u;
+ }
+ if (x[c] < x[b]) {
+ final double v = x[c];
+ x[c] = x[b];
+ x[b] = v;
+ }
+
+ if (x[d] < x[c]) {
+ final double u = x[d];
+ x[d] = x[c];
+ x[c] = u;
+ }
+ }
+
+ /**
+ * Place the lower median of 4 elements in {@code b}; the smaller element in
+ * {@code a}; and the larger two elements in {@code c, d}.
+ *
+ * @param x Values
+ * @param a Index.
+ * @param b Index.
+ * @param c Index.
+ * @param d Index.
+ */
+ static void lowerMedian4(double[] x, int a, int b, int c, int d) {
+ // 3 to 5 comparisons
+ if (x[d] < x[b]) {
+ final double u = x[d];
+ x[d] = x[b];
+ x[b] = u;
+ }
+ if (x[c] < x[a]) {
+ final double v = x[c];
+ x[c] = x[a];
+ x[a] = v;
+ }
+ // a--c
+ // b--d
+ if (x[c] < x[b]) {
+ final double u = x[c];
+ x[c] = x[b];
+ x[b] = u;
+ } else if (x[b] < x[a]) {
+ // a--c
+ // b--d
+ final double xb = x[a];
+ x[a] = x[b];
+ x[b] = xb;
+ // b--c
+ // a--d
+ if (x[d] < xb) {
+ x[b] = x[d];
+ // Move a pair to maintain the sorted order
+ x[d] = x[c];
+ x[c] = xb;
+ }
+ }
+ }
+
+ /**
+ * Place the upper median of 4 elements in {@code c}; the smaller two elements in
+ * {@code a,b}; and the larger element in {@code d}.
+ *
+ * @param x Values
+ * @param a Index.
+ * @param b Index.
+ * @param c Index.
+ * @param d Index.
+ */
+ static void upperMedian4(double[] x, int a, int b, int c, int d) {
+ // 3 to 5 comparisons
+ if (x[d] < x[b]) {
+ final double u = x[d];
+ x[d] = x[b];
+ x[b] = u;
+ }
+ if (x[c] < x[a]) {
+ final double v = x[c];
+ x[c] = x[a];
+ x[a] = v;
+ }
+ // a--c
+ // b--d
+ if (x[b] > x[c]) {
+ final double u = x[c];
+ x[c] = x[b];
+ x[b] = u;
+ } else if (x[c] > x[d]) {
+ // a--c
+ // b--d
+ final double xc = x[d];
+ x[d] = x[c];
+ x[c] = xc;
+ // a--d
+ // b--c
+ if (x[a] > xc) {
+ x[c] = x[a];
+ // Move a pair to maintain the sorted order
+ x[a] = x[b];
+ x[b] = xc;
+ }
+ }
+ }
+
+ /**
+ * Sort the unique indices in-place to the start of the array. The number of
+ * unique indices is returned.
+ *
+ * Uses an insertion sort modified to ignore duplicates. Use on small {@code n}.
+ *
+ * Warning: Requires {@code n > 0}. The array contents after the count of unique
+ * indices {@code c} are unchanged (i.e. {@code [c, n)}. This may change the count of
+ * each unique index in the entire array.
+ *
+ * @param x Indices.
+ * @param n Number of indices.
+ * @return the number of indices
+ */
+ static int insertionSortIndices(int[] x, int n) {
+ int unique = 1;
+ // Do an insertion sort but only compare the current set of unique values.
+ for (int i = 0; ++i < n;) {
+ final int v = x[i];
+ int j = unique - 1;
+ if (v > x[j]) {
+ // Insert at end
+ x[unique] = v;
+ unique++;
+ } else if (v < x[j]) {
+ // Find insertion point in the unique indices
+ do {
+ --j;
+ } while (j >= 0 && v < x[j]);
+ // Insertion point = j + 1
+ // Insert if at start or non-duplicate
+ if (j < 0 || v != x[j]) {
+ // Move (j, unique) to (j+1, unique+1)
+ for (int k = unique; --k > j;) {
+ x[k + 1] = x[k];
+ }
+ x[j + 1] = v;
+ unique++;
+ }
+ }
+ }
+ return unique;
+ }
+
+ /**
+ * Sort the unique indices in-place to the start of the array. The number of
+ * unique indices is returned.
+ *
+ * Uses an Order(1) data structure to ignore duplicates.
+ *
+ * Warning: Requires {@code n > 0}. The array contents after the count of unique
+ * indices is unchanged. This may change the count of each unique index in the
+ * entire array.
+ *
+ * @param x Indices.
+ * @param n Number of indices.
+ * @return the number of indices
+ */
+ static int sortIndices(int[] x, int n) {
+ // Duplicates are checked using a primitive hash set.
+ // Storage (bytes) = 4 * next-power-of-2(n*2) => 2-4 times n
+ final HashIndexSet set = HashIndexSet.create(n);
+ int i = 0;
+ int last = 0;
+ set.add(x[0]);
+ while (++i < n) {
+ final int v = x[i];
+ if (set.add(v)) {
+ x[++last] = v;
+ }
+ }
+ Arrays.sort(x, 0, ++last);
+ return last;
+ }
+}
diff --git a/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/UpdatingInterval.java b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/UpdatingInterval.java
new file mode 100644
index 000000000..5b8e82ffa
--- /dev/null
+++ b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/UpdatingInterval.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.arrays;
+
+/**
+ * An interval that contains indices used for partitioning an array into multiple regions.
+ *
+ * The interval provides the following functionality:
+ *
+ * Note that the interval provides the supported bounds. If an search index {@code k} is
+ * outside the supported bounds the result is undefined.
+ *
+ * Implementations may assume indices are positive.
+ *
+ * @since 1.2
+ */
+interface UpdatingInterval {
+ /**
+ * The start (inclusive) of the interval.
+ *
+ * @return start of the interval
+ */
+ int left();
+
+ /**
+ * The end (inclusive) of the interval.
+ *
+ * @return end of the interval
+ */
+ int right();
+
+ /**
+ * Update the left bound of the interval so {@code k <= left}.
+ *
+ * Note: Requires {@code left < k <= right}, i.e. there exists a valid interval
+ * above the index.
+ *
+ * Note: Requires {@code left <= k < right}, i.e. there exists a valid interval
+ * below the index.
+ *
+ * Note: Requires {@code left < ka <= kb < right}, i.e. there exists a valid interval
+ * both above and below the splitting indices.
+ *
+ * If {@code ka <= left} or {@code kb >= right} the result is undefined.
+ *
+ * @param ka Split index.
+ * @param kb Split index.
+ * @return the left interval
+ */
+ UpdatingInterval splitLeft(int ka, int kb);
+}
diff --git a/commons-numbers-arrays/src/test/java/org/apache/commons/numbers/arrays/HashIndexSetTest.java b/commons-numbers-arrays/src/test/java/org/apache/commons/numbers/arrays/HashIndexSetTest.java
new file mode 100644
index 000000000..458d5bc0e
--- /dev/null
+++ b/commons-numbers-arrays/src/test/java/org/apache/commons/numbers/arrays/HashIndexSetTest.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.arrays;
+
+import java.util.BitSet;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Test for {@link HashIndexSet}.
+ */
+class HashIndexSetTest {
+
+ @Test
+ void testInvalidCapacityThrows() {
+ final int maxCapacity = 1 << 29;
+ Assertions.assertThrows(IllegalArgumentException.class, () -> HashIndexSet.create(maxCapacity + 1));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> HashIndexSet.create(Integer.MAX_VALUE));
+ }
+
+ @Test
+ void testInvalidIndexThrows() {
+ final HashIndexSet set = HashIndexSet.create(16);
+ Assertions.assertThrows(IndexOutOfBoundsException.class, () -> set.add(-1));
+ Assertions.assertThrows(IndexOutOfBoundsException.class, () -> set.add(Integer.MIN_VALUE));
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {10, 32})
+ void testCapacityExceededThrows(int capacity) {
+ final HashIndexSet set = HashIndexSet.create(capacity);
+ IntStream.range(0, capacity).forEach(set::add);
+ // Now add more and expect an exception.
+ // With a load factor of 0.5 we can only add twice the requested capacity
+ // before an exception occurs.
+ Assertions.assertThrows(IllegalStateException.class, () -> {
+ for (int i = capacity; i < capacity * 2; i++) {
+ set.add(i);
+ }
+ });
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testIndices"})
+ void testAdd(int[] indices, int capacity) {
+ final HashIndexSet set = HashIndexSet.create(capacity);
+ final BitSet ref = new BitSet(capacity);
+ for (final int i : indices) {
+ final boolean observed = ref.get(i);
+ // Add returns true if not already present
+ Assertions.assertEquals(!observed, set.add(i), () -> String.valueOf(i));
+ ref.set(i);
+ }
+ }
+
+ static Stream This method allows variable length indices using a count of the indices to
+ * process.
+ *
+ * @param a Values.
+ * @param k Indices.
+ * @param n Count of indices.
+ */
+ void partition(double[] a, int[] k, int n);
+ }
+
+ /**
+ * Return a sorted copy of the {@code values}.
+ *
+ * @param values Values.
+ * @return the copy
+ */
+ private static double[] sort(double[] values) {
+ final double[] sorted = values.clone();
+ Arrays.sort(sorted);
+ return sorted;
+ }
+
+ /**
+ * Return a copy of the {@code values} sorted in the range {@code [from, to]}.
+ *
+ * @param values Values.
+ * @param from From (inclusive).
+ * @param to To (inclusive).
+ * @return the copy
+ */
+ private static double[] sort(double[] values, int from, int to) {
+ final double[] sorted = values.clone();
+ Arrays.sort(sorted, from, to + 1);
+ return sorted;
+ }
+
+ /**
+ * Move NaN values to the end of the array.
+ * This allows all other values to be compared using {@code <, ==, >} operators (with
+ * the exception of signed zeros).
+ *
+ * @param data Values.
+ * @return index of last non-NaN value (or -1)
+ */
+ private static int sortNaN(double[] data) {
+ int end = data.length;
+ // Find first non-NaN
+ while (--end >= 0) {
+ if (!Double.isNaN(data[end])) {
+ break;
+ }
+ }
+ for (int i = end; --i >= 0;) {
+ final double v = data[i];
+ if (Double.isNaN(v)) {
+ // swap(data, i, end--)
+ data[i] = data[end];
+ data[end] = v;
+ end--;
+ }
+ }
+ return end;
+ }
+
+ /**
+ * Replace negative zeros with a proxy. Uses -{@link Double#MIN_VALUE} as the proxy.
+ *
+ * @param a Data.
+ * @param from Lower bound (inclusive).
+ * @param to Upper bound (inclusive).
+ */
+ private static void replaceNegativeZeros(double[] a, int from, int to) {
+ for (int i = from; i <= to; i++) {
+ if (Double.doubleToRawLongBits(a[i]) == Long.MIN_VALUE) {
+ a[i] = -Double.MIN_VALUE;
+ }
+ }
+ }
+
+ /**
+ * Restore proxy negative zeros.
+ *
+ * @param a Data.
+ * @param from Lower bound (inclusive).
+ * @param to Upper bound (inclusive).
+ */
+ private static void restoreNegativeZeros(double[] a, int from, int to) {
+ for (int i = from; i <= to; i++) {
+ if (a[i] == -Double.MIN_VALUE) {
+ a[i] = -0.0;
+ }
+ }
+ }
+
+ /**
+ * Shuffles the entries of the given array.
+ *
+ * @param rng Source of randomness.
+ * @param array Array whose entries will be shuffled (in-place).
+ * @return Shuffled input array.
+ */
+ // TODO - replace with Commons RNG 1.6: o.a.c.rng.sampling.ArraySampler
+ private static double[] shuffle(UniformRandomProvider rng, double[] array) {
+ for (int i = array.length; i > 1; i--) {
+ swap(array, i - 1, rng.nextInt(i));
+ }
+ return array;
+ }
+
+ /**
+ * Swaps the two specified elements in the array.
+ *
+ * @param array Array.
+ * @param i First index.
+ * @param j Second index.
+ */
+ private static void swap(double[] array, int i, int j) {
+ final double tmp = array[i];
+ array[i] = array[j];
+ array[j] = tmp;
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testDoubleHeapSelect", "testDoubleSelectMinMax", "testDoubleSelectMinMax2"})
+ void testDoubleHeapSelectLeft(double[] values, int from, int to) {
+ final double[] sorted = sort(values, from, to);
+
+ final double[] x = values.clone();
+ final DoubleRangePartitionFunction fun = QuickSelect::heapSelectLeft;
+
+ for (int k = from; k <= to; k++) {
+ assertPartitionRange(sorted, fun, x.clone(), from, to, k, k);
+ if (k > from) {
+ // Sort an extra 1
+ assertPartitionRange(sorted, fun, x.clone(), from, to, k - 1, k);
+ if (k > from + 1) {
+ // Sort all
+ // Test clipping with k < from
+ assertPartitionRange(sorted, fun, x.clone(), from, to, from - 23, k);
+ }
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testDoubleHeapSelect", "testDoubleSelectMinMax", "testDoubleSelectMinMax2"})
+ void testDoubleHeapSelectRight(double[] values, int from, int to) {
+ final double[] sorted = sort(values, from, to);
+
+ final double[] x = values.clone();
+ final DoubleRangePartitionFunction fun = QuickSelect::heapSelectRight;
+
+ for (int k = from; k <= to; k++) {
+ assertPartitionRange(sorted, fun, x.clone(), from, to, k, k);
+ if (k < to) {
+ // Sort an extra 1
+ assertPartitionRange(sorted, fun, x.clone(), from, to, k, k + 1);
+ if (k < to - 1) {
+ // Sort all
+ // Test clipping with k > to
+ assertPartitionRange(sorted, fun, x.clone(), from, to, k, to + 23);
+ }
+ }
+ }
+ }
+
+ static Stream Sorting tests for floating point values do not include NaN or signed zero (-0.0).
+ */
+class SortingTest {
+
+ /**
+ * Interface to test sorting of indices.
+§ */
+ interface IndexSort {
+ /**
+ * Sort the indices into unique ascending order.
+ *
+ * @param a Indices.
+ * @param n Number of indices.
+ * @return number of unique indices.
+ */
+ int insertionSortIndices(int[] a, int n);
+ }
+
+ // double[]
+
+ @ParameterizedTest
+ @MethodSource(value = {"testDoubleSort"})
+ void testDoubleInsertionSort(double[] values) {
+ assertSort(values, x -> Sorting.sort(x, 0, x.length - 1));
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testDoubleSort", "testDoubleSort3"})
+ void testDoubleSort3(double[] values) {
+ final double[] data = Arrays.copyOf(values, 3);
+ assertSort(data, x -> Sorting.sort3(x, 0, 1, 2));
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testDoubleSort", "testDoubleSort5"})
+ void testDoubleSort5(double[] values) {
+ final double[] data = Arrays.copyOf(values, 5);
+ assertSort(data, x -> Sorting.sort5(x, 0, 1, 2, 3, 4));
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testDoubleSort3Internal"})
+ void testDoubleSort3Internal(double[] values, int[] indices) {
+ final int a = indices[0];
+ final int b = indices[1];
+ final int c = indices[2];
+ assertSortInternal(values, x -> Sorting.sort3(x, a, b, c), indices);
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testDoubleSort5Internal"})
+ void testDoubleSort5Internal(double[] values, int[] indices) {
+ final int a = indices[0];
+ final int b = indices[1];
+ final int c = indices[2];
+ final int d = indices[3];
+ final int e = indices[4];
+ assertSortInternal(values, x -> Sorting.sort5(x, a, b, c, d, e), indices);
+ }
+
+ /**
+ * Assert that the sort {@code function} computes the same result as
+ * {@link Arrays#sort(double[])}.
+ *
+ * @param values Data.
+ * @param function Sort function.
+ */
+ private static void assertSort(double[] values, Consumer This is a specialised class to implement a reduced API similar to a
+ * {@link java.util.BitSet}. It uses no bounds range checks and supports only
+ * the methods required to implement the {@link UpdatingInterval} API.
+ *
+ * An offset is supported to allow the fixed size to cover a range of indices starting
+ * above 0 with the most efficient usage of storage.
+ *
+ * See the BloomFilter code in Commons Collections for use of long[] data to store
+ * bits.
+ *
+ * @since 1.2
+ */
+final class BitIndexUpdatingInterval implements UpdatingInterval, SplittingInterval, IntervalAnalysis {
+ /** All 64-bits bits set. */
+ private static final long LONG_MASK = -1L;
+ /** A bit shift to apply to an integer to divided by 64 (2^6). */
+ private static final int DIVIDE_BY_64 = 6;
+
+ /** Bit indexes. */
+ private final long[] data;
+
+ /** Index offset. */
+ private final int offset;
+ /** Left bound of the support. */
+ private int left;
+ /** Right bound of the support. */
+ private int right;
+
+ /**
+ * Create an instance to store indices within the range {@code [left, right]}.
+ * The range is not validated.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ BitIndexUpdatingInterval(int left, int right) {
+ this.offset = left;
+ this.left = left;
+ this.right = right;
+ // Allocate storage to store index==right
+ // Note: This may allow directly writing to index > right if there
+ // is extra capacity.
+ data = new long[getLongIndex(right - offset) + 1];
+ }
+
+ /**
+ * Create an instance with the range {@code [left, right]} and reusing the provided
+ * index {@code data}.
+ *
+ * @param data Data.
+ * @param offset Index offset.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ private BitIndexUpdatingInterval(long[] data, int offset, int left, int right) {
+ this.data = data;
+ this.offset = offset;
+ this.left = left;
+ this.right = right;
+ }
+
+ /**
+ * Create an instance to store indices within the range {@code [left, right]}.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @return the index set
+ * @throws IllegalArgumentException if {@code right < left}; {@code left < 0} or
+ * {@code right == Integer.MAX_VALUE}
+ */
+ static BitIndexUpdatingInterval ofRange(int left, int right) {
+ if (left < 0) {
+ throw new IllegalArgumentException("Invalid lower index: " + left);
+ }
+ if (right == Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("Invalid upper index: " + right);
+ }
+ if (right < left) {
+ throw new IllegalArgumentException(
+ String.format("Invalid range: [%d, %d]", left, right));
+ }
+ return new BitIndexUpdatingInterval(left, right);
+ }
+
+ /**
+ * Initialise an instance with the {@code indices}. The capacity is defined by the
+ * range required to store the minimum and maximum index.
+ *
+ * @param indices Indices.
+ * @param n Number of indices.
+ * @return the index set
+ * @throws IllegalArgumentException if {@code n == 0}
+ */
+ static BitIndexUpdatingInterval of(int[] indices, int n) {
+ if (n <= 0) {
+ throw new IllegalArgumentException("No indices to define the range");
+ }
+ int min = indices[0];
+ int max = min;
+ for (int i = 1; i < n; i++) {
+ min = Math.min(min, indices[i]);
+ max = Math.max(max, indices[i]);
+ }
+ final BitIndexUpdatingInterval set = BitIndexUpdatingInterval.ofRange(min, max);
+ for (int i = -1; ++i < n;) {
+ set.set(indices[i]);
+ }
+ return set;
+ }
+
+ /**
+ * Return the memory footprint in bytes. This is always a multiple of 64.
+ *
+ * The result is {@code 64 * ceil((right - offset + 1) / 64)}.
+ *
+ * This method is intended to provided information to choose if the data structure
+ * is memory efficient.
+ *
+ * Warning: It is assumed {@code 0 <= left <= right}. Use with the min/max index
+ * that is to be stored.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @return the memory footprint
+ */
+ static long memoryFootprint(int left, int right) {
+ return (getLongIndex(right - left) + 1L) * Long.BYTES;
+ }
+
+ /**
+ * Gets the filter index for the specified bit index assuming the filter is using
+ * 64-bit longs to store bits starting at index 0.
+ *
+ * The index is assumed to be positive. For a positive index the result will match
+ * {@code bitIndex / 64}. The divide is performed using bit shifts. If the input is negative the
+ * behavior is not defined. The index is assumed to be positive. For a positive index the result will match
+ * {@code 1L << (bitIndex % 64)}. If the input is negative the behavior is not defined. Warning: This has no range checks.
+ *
+ * @param bitIndex the bit index (assumed to be positive)
+ */
+ void set(int bitIndex) {
+ // WARNING: No range checks !!!
+ final int index = bitIndex - offset;
+ final int i = getLongIndex(index);
+ final long m = getLongBit(index);
+ data[i] |= m;
+ }
+
+ /**
+ * Returns the index of the first bit that is set to {@code true} that occurs on or
+ * after the specified starting index.
+ *
+ * Warning: This has no range checks. It is assumed that {@code left <= k <= right},
+ * that is there is a set bit on or after {@code k}.
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the index of the next set bit
+ */
+ private int nextIndex(int k) {
+ // left <= k <= right
+
+ final int index = k - offset;
+ int i = getLongIndex(index);
+
+ // Mask bits after the bit index
+ // mask = 11111000 = -1L << (index % 64)
+ long bits = data[i] & (LONG_MASK << index);
+ for (;;) {
+ if (bits != 0) {
+ //(i+1) i
+ // | index |
+ // | | |
+ // 0 001010000
+ return i * Long.SIZE + Long.numberOfTrailingZeros(bits) + offset;
+ }
+ // Unsupported: the interval should contain k
+ //if (++i == data.length)
+ // return right + 1
+ bits = data[++i];
+ }
+ }
+
+ /**
+ * Returns the index of the first bit that is set to {@code true} that occurs on or
+ * before the specified starting index.
+ *
+ * Warning: This has no range checks. It is assumed that {@code left <= k <= right},
+ * that is there is a set bit on or before {@code k}.
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the index of the previous set bit
+ */
+ private int previousIndex(int k) {
+ // left <= k <= right
+
+ final int index = k - offset;
+ int i = getLongIndex(index);
+
+ // Mask bits before the bit index
+ // mask = 00011111 = -1L >>> (64 - ((index + 1) % 64))
+ long bits = data[i] & (LONG_MASK >>> -(index + 1));
+ for (;;) {
+ if (bits != 0) {
+ //(i+1) i
+ // | index |
+ // | | |
+ // 0 001010000
+ return (i + 1) * Long.SIZE - Long.numberOfLeadingZeros(bits) - 1 + offset;
+ }
+ // Unsupported: the interval should contain k
+ //if (i == 0)
+ // return left - 1
+ bits = data[--i];
+ }
+ }
+
+ @Override
+ public int left() {
+ return left;
+ }
+
+ @Override
+ public int right() {
+ return right;
+ }
+
+ @Override
+ public int updateLeft(int k) {
+ // Assume left < k= < right
+ return left = nextIndex(k);
+ }
+
+ @Override
+ public int updateRight(int k) {
+ // Assume left <= k < right
+ return right = previousIndex(k);
+ }
+
+ @Override
+ public UpdatingInterval splitLeft(int ka, int kb) {
+ // Assume left < ka <= kb < right
+ final int lower = left;
+ left = nextIndex(kb + 1);
+ return new BitIndexUpdatingInterval(data, offset, lower, previousIndex(ka - 1));
+ }
+
+ @Override
+ public UpdatingInterval splitRight(int ka, int kb) {
+ // Assume left < ka <= kb < right
+ final int upper = right;
+ right = previousIndex(ka - 1);
+ return new BitIndexUpdatingInterval(data, offset, nextIndex(kb + 1), upper);
+ }
+
+ @Override
+ public boolean empty() {
+ // Empty when the interval is invalid. Signalled by a negative right bound.
+ return right < 0;
+ }
+
+ @Override
+ public SplittingInterval split(int ka, int kb) {
+ if (ka <= left) {
+ // No left interval
+ if (kb >= right) {
+ // No right interval
+ invalidate();
+ } else if (kb >= left) {
+ // Update the left bound
+ left = nextIndex(kb + 1);
+ }
+ return null;
+ }
+ if (kb >= right) {
+ // No right interval.
+ // Find new right bound for the left-side.
+ final int r = ka <= right ? previousIndex(ka - 1) : right;
+ invalidate();
+ return new BitIndexUpdatingInterval(data, offset, left, r);
+ }
+ // Split
+ return (SplittingInterval) splitLeft(ka, kb);
+ }
+
+ /**
+ * Invalidate the interval and mark as empty.
+ */
+ private void invalidate() {
+ right = -1;
+ }
+
+ @Override
+ public boolean saturated(int separation) {
+ // Support saturation analysis at separation relevant to the
+ // quickselect implementations
+ if (separation == 3) {
+ return saturated3();
+ }
+ if (separation == 4) {
+ return saturated4();
+ }
+ return false;
+ }
+
+ /**
+ * Test if saturated as a separation of {@code 2^3}.
+ *
+ * @return true if saturated
+ */
+ private boolean saturated3() {
+ int c = 0;
+ for (long x : data) {
+ // Shift powers of 2 and mask out the bits that were shifted
+ x = x | (x >>> 1);
+ x = x | (x >>> 2);
+ x = (x | (x >>> 4)) & 0b0000000100000001000000010000000100000001000000010000000100000001L;
+ // Expect a population count intrinsic method
+ // Add [0, 8]
+ c += Long.bitCount(x);
+ }
+ // Multiply by 8
+ return c << 3 >= right - left;
+ }
+
+ /**
+ * Test if saturated as a separation of {@code 2^4}.
+ *
+ * @return true if saturated
+ */
+ private boolean saturated4() {
+ int c = 0;
+ for (long x : data) {
+ // Shift powers of 2 and mask out the bits that were shifted
+ x = x | (x >>> 1);
+ x = x | (x >>> 2);
+ x = x | (x >>> 4);
+ x = (x | (x >>> 8)) & 0b0000000000000001000000000000000100000000000000010000000000000001L;
+ // Count the bits using folding
+ // x = mask:
+ // 0000000000000001000000000000001000000000000000100000000000000010 (x += (x >>> 16))
+ // 0000000100000001000000100000001000000011000000110000010000000100 (x += (x >>> 32))
+ x = x + (x >>> 16); // put count of each 32 bits into their lowest 2 bits
+ x = x + (x >>> 32); // put count of each 64 bits into their lowest 3 bits
+ // Add [0, 4]
+ c += (int) x & 0b111;
+ }
+ // Multiply by 16
+ return c << 4 >= right - left;
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/CompressedIndexSet.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/CompressedIndexSet.java
new file mode 100644
index 000000000..e256d4506
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/CompressedIndexSet.java
@@ -0,0 +1,698 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * A fixed size set of indices within an inclusive range {@code [left, right]}.
+ *
+ * This is a specialised class to implement a data structure similar to a
+ * {@link java.util.BitSet}. It supports a fixed size and contains the methods required to
+ * store and look-up intervals of indices.
+ *
+ * An offset is supported to allow the fixed size to cover a range of indices starting
+ * above 0 with the most efficient usage of storage.
+ *
+ * In contrast to a {@link java.util.BitSet}, the data structure does not store all
+ * indices in the range. Indices are compressed by a power of 2. The structure can return
+ * with 100% accuracy if a query index is not within the range. It cannot return with 100%
+ * accuracy if a query index is contained within the range. The presence of a query index
+ * is a probabilistic statement that there is an index within a range of the query index.
+ * The range is defined by the compression level {@code c}.
+ *
+ * Indices are stored offset from {@code left} and compressed. A compressed index
+ * represents 2c real indices:
+ *
+ * When scanning for the next index the identified compressed index is decompressed and
+ * the lower bound of the range represented by the index is returned.
+ *
+ * When scanning for the previous index the identified compressed index is decompressed
+ * and the upper bound of the range represented by the index is returned.
+ *
+ * When scanning in either direction, if the search index is inside a compressed index
+ * the search index is returned.
+ *
+ * Note: Search for the {@link SearchableInterval} interface outside the supported bounds
+ * {@code [left, right]} is not supported and will result in an {@link IndexOutOfBoundsException}.
+ *
+ * See the BloomFilter code in Commons Collections for use of long[] data to store
+ * bits.
+ *
+ * @since 1.2
+ */
+final class CompressedIndexSet implements SearchableInterval, SearchableInterval2 {
+ /** All 64-bits bits set. */
+ private static final long LONG_MASK = -1L;
+ /** A bit shift to apply to an integer to divided by 64 (2^6). */
+ private static final int DIVIDE_BY_64 = 6;
+
+ /** Bit indexes. */
+ private final long[] data;
+
+ /** Left bound of the support. */
+ private final int left;
+ /** Right bound of the support. */
+ private final int right;
+ /** Compression level. */
+ private final int compression;
+
+ /**
+ * Create an instance to store indices within the range {@code [left, right]}.
+ *
+ * @param compression Compression level (in {@code [1, 31])}
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ */
+ private CompressedIndexSet(int compression, int l, int r) {
+ this.compression = compression;
+ this.left = l;
+ // Note: The functional upper bound may be higher but the next/previous functionality
+ // support scanning in the original [left, right] bound.
+ this.right = r;
+ // Note: This may allow directly writing to index > right if there
+ // is extra capacity.
+ data = new long[getLongIndex((r - l) >>> 1) + 1];
+ }
+
+ /**
+ * Create an instance to store indices within the range {@code [left, right]}.
+ * The instance is initially empty.
+ *
+ * Warning: To use this object as an {@link SearchableInterval} the left and right
+ * indices should be added to the set.
+ *
+ * @param compression Compression level (in {@code [1, 31])}
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @return the index set
+ * @throws IllegalArgumentException if {@code compression} is not in {@code [1, 31]};
+ * or if {@code right < left}; or if {@code left < 0}
+ */
+ static CompressedIndexSet ofRange(int compression, int left, int right) {
+ checkCompression(compression);
+ checkLeft(left);
+ checkRange(left, right);
+ return new CompressedIndexSet(compression, left, right);
+ }
+
+ /**
+ * Initialise an instance with the {@code indices}. The capacity is defined by the
+ * range required to store the minimum and maximum index at the specified
+ * {@code compression} level.
+ *
+ * This object can be used as an {@link SearchableInterval} as the left and right
+ * indices will be set.
+ *
+ * @param compression Compression level (in {@code [1, 31])}
+ * @param indices Indices.
+ * @return the index set
+ * @throws IllegalArgumentException if {@code compression} is not in {@code [1, 31]};
+ * or if {@code indices.length == 0}; or if {@code left < 0}
+ */
+ static CompressedIndexSet of(int compression, int[] indices) {
+ return of(compression, indices, indices.length);
+ }
+
+ /**
+ * Initialise an instance with the {@code indices}. The capacity is defined by the
+ * range required to store the minimum and maximum index at the specified
+ * {@code compression} level.
+ *
+ * This object can be used as an {@link SearchableInterval} as the left and right
+ * indices will be set.
+ *
+ * @param compression Compression level (in {@code [1, 31])}
+ * @param indices Indices.
+ * @param n Number of indices.
+ * @return the index set
+ * @throws IllegalArgumentException if {@code compression} is not in {@code [1, 31]};
+ * or if {@code n == 0}; or if {@code left < 0}
+ */
+ static CompressedIndexSet of(int compression, int[] indices, int n) {
+ if (n <= 0) {
+ throw new IllegalArgumentException("No indices to define the range");
+ }
+ checkCompression(compression);
+ int min = indices[0];
+ int max = min;
+ for (int i = 0; ++i < n;) {
+ min = Math.min(min, indices[i]);
+ max = Math.max(max, indices[i]);
+ }
+ checkLeft(min);
+ final CompressedIndexSet set = new CompressedIndexSet(compression, min, max);
+ for (int i = -1; ++i < n;) {
+ set.set(indices[i]);
+ }
+ return set;
+ }
+
+ /**
+ * Create an {@link IndexIterator} with the {@code indices}.
+ *
+ * @param compression Compression level (in {@code [1, 31])}
+ * @param indices Indices.
+ * @param n Number of indices.
+ * @return the index set
+ * @throws IllegalArgumentException if {@code compression} is not in {@code [1, 31]};
+ * or if {@code n == 0}; or if {@code left < 0}
+ */
+ static IndexIterator iterator(int compression, int[] indices, int n) {
+ return of(compression, indices, n).new Iterator();
+ }
+
+ /**
+ * Gets the compressed index for this instance using the left bound and the
+ * compression level.
+ *
+ * @param index Index.
+ * @return the compressed index
+ */
+ private int compressIndex(int index) {
+ return (index - left) >>> compression;
+ }
+
+ /**
+ * Gets the filter index for the specified bit index assuming the filter is using
+ * 64-bit longs to store bits starting at index 0.
+ *
+ * The index is assumed to be positive. For a positive index the result will match
+ * {@code bitIndex / 64}. The divide is performed using bit shifts. If the input is negative the
+ * behavior is not defined. The index is assumed to be positive. For a positive index the result will match
+ * {@code 1L << (bitIndex % 64)}. If the input is negative the behavior is not defined. Warning: This has no range checks.
+ *
+ * @param bitIndex the bit index (assumed to be positive)
+ * @return the value of the bit with the specified index
+ */
+ boolean get(int bitIndex) {
+ // WARNING: No range checks !!!
+ final int index = compressIndex(bitIndex);
+ final int i = getLongIndex(index);
+ final long m = getLongBit(index);
+ return (data[i] & m) != 0;
+ }
+
+ /**
+ * Sets the bit at the specified index to {@code true}.
+ *
+ * Warning: This has no range checks.
+ *
+ * @param bitIndex the bit index (assumed to be positive)
+ */
+ void set(int bitIndex) {
+ // WARNING: No range checks !!!
+ final int index = compressIndex(bitIndex);
+ final int i = getLongIndex(index);
+ final long m = getLongBit(index);
+ data[i] |= m;
+ }
+
+
+ @Override
+ public int left() {
+ return left;
+ }
+
+ @Override
+ public int right() {
+ return right;
+ }
+
+ /**
+ * Returns the nearest index that occurs on or before the specified starting
+ * index, or {@code left - 1} if no such index exists.
+ *
+ * This method exists for comparative testing to {@link #previousIndex(int)}.
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the previous index, or {@code left - 1}
+ */
+ int previousIndexOrLeftMinus1(int k) {
+ if (k < left) {
+ // index is in an unknown range
+ return left - 1;
+ }
+ // Support searching backward through the known range
+ final int index = compressIndex(k > right ? right : k);
+
+ int i = getLongIndex(index);
+ long bits = data[i];
+
+ // Check if this is within a compressed index. If so return the exact result.
+ if ((bits & getLongBit(index)) != 0) {
+ return Math.min(k, right);
+ }
+
+ // Mask bits before the bit index
+ // mask = 00011111 = -1L >>> (64 - ((index + 1) % 64))
+ bits &= LONG_MASK >>> -(index + 1);
+ for (;;) {
+ if (bits != 0) {
+ //(i+1) i
+ // | c |
+ // | | |
+ // 0 001010000
+ final int c = (i + 1) * Long.SIZE - Long.numberOfLeadingZeros(bits);
+ // Decompress the prior unset bit to an index. When inflated this is the
+ // next index above the upper bound of the compressed range so subtract 1.
+ return (c << compression) - 1 + left;
+ }
+ if (i == 0) {
+ return left - 1;
+ }
+ bits = data[--i];
+ }
+ }
+
+ /**
+ * Returns the nearest index that occurs on or after the specified starting
+ * index, or {@code right + 1} if no such index exists.
+ *
+ * This method exists for comparative testing to {@link #nextIndex(int)}.
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the next index, or {@code right + 1}
+ */
+ int nextIndexOrRightPlus1(int k) {
+ if (k > right) {
+ // index is in an unknown range
+ return right + 1;
+ }
+ // Support searching forward through the known range
+ final int index = compressIndex(k < left ? left : k);
+
+ int i = getLongIndex(index);
+ long bits = data[i];
+
+ // Check if this is within a compressed index. If so return the exact result.
+ if ((bits & getLongBit(index)) != 0) {
+ return Math.max(k, left);
+ }
+
+ // Mask bits after the bit index
+ // mask = 11111000 = -1L << (index % 64)
+ bits &= LONG_MASK << index;
+ for (;;) {
+ if (bits != 0) {
+ //(i+1) i
+ // | c |
+ // | | |
+ // 0 001010000
+ final int c = i * Long.SIZE + Long.numberOfTrailingZeros(bits);
+ // Decompress the set bit to an index. When inflated this is the lower bound of
+ // the compressed range and is OK for next scanning.
+ return (c << compression) + left;
+ }
+ if (++i == data.length) {
+ return right + 1;
+ }
+ bits = data[i];
+ }
+ }
+
+ @Override
+ public int previousIndex(int k) {
+ // WARNING: No range checks !!!
+ // Assume left <= k <= right and that left and right are set bits acting as sentinals.
+ final int index = compressIndex(k);
+
+ int i = getLongIndex(index);
+ long bits = data[i];
+
+ // Check if this is within a compressed index. If so return the exact result.
+ if ((bits & getLongBit(index)) != 0) {
+ return k;
+ }
+
+ // Mask bits before the bit index
+ // mask = 00011111 = -1L >>> (64 - ((index + 1) % 64))
+ bits &= LONG_MASK >>> -(index + 1);
+ for (;;) {
+ if (bits != 0) {
+ //(i+1) i
+ // | c |
+ // | | |
+ // 0 001010000
+ final int c = (i + 1) * Long.SIZE - Long.numberOfLeadingZeros(bits);
+ // Decompress the prior unset bit to an index. When inflated this is the
+ // next index above the upper bound of the compressed range so subtract 1.
+ return (c << compression) - 1 + left;
+ }
+ // Unsupported: the interval should contain k
+ //if (i == 0) {
+ // return left - 1;
+ //}
+ bits = data[--i];
+ }
+ }
+
+ @Override
+ public int nextIndex(int k) {
+ // WARNING: No range checks !!!
+ // Assume left <= k <= right and that left and right are set bits acting as sentinals.
+ final int index = compressIndex(k);
+
+ int i = getLongIndex(index);
+ long bits = data[i];
+
+ // Check if this is within a compressed index. If so return the exact result.
+ if ((bits & getLongBit(index)) != 0) {
+ return k;
+ }
+
+ // Mask bits after the bit index
+ // mask = 11111000 = -1L << (index % 64)
+ bits &= LONG_MASK << index;
+ for (;;) {
+ if (bits != 0) {
+ //(i+1) i
+ // | c |
+ // | | |
+ // 0 001010000
+ final int c = i * Long.SIZE + Long.numberOfTrailingZeros(bits);
+ // Decompress the set bit to an index. When inflated this is the lower bound of
+ // the compressed range and is OK for next scanning.
+ return (c << compression) + left;
+ }
+ // Unsupported: the interval should contain k
+ //if (++i == data.length) {
+ // return right + 1;
+ //}
+ bits = data[++i];
+ }
+ }
+
+ // SearchableInterval2
+ // This is exactly the same as SearchableInterval as the pointers i are the same as the keys k
+
+ @Override
+ public int start() {
+ return left();
+ }
+
+ @Override
+ public int end() {
+ return right();
+ }
+
+ @Override
+ public int index(int i) {
+ return i;
+ }
+
+ @Override
+ public int previous(int i, int k) {
+ return previousIndex(k);
+ }
+
+ @Override
+ public int next(int i, int k) {
+ return nextIndex(k);
+ }
+
+ /**
+ * Returns the index of the first bit that is set to {@code false} that occurs on or
+ * before the specified starting index within the supported range. If no such
+ * bit exists then {@code left - 1} is returned.
+ *
+ * Assumes {@code k} is within an enabled compressed index.
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the index of the previous unset bit, or {@code left - 1} if there is no such bit
+ */
+ int previousClearBit(int k) {
+ // WARNING: No range checks !!!
+ // Assume left <= k <= right and that left and right are set bits acting as sentinals.
+ final int index = compressIndex(k);
+
+ int i = getLongIndex(index);
+
+ // Note: This method is conceptually the same as previousIndex with the exception
+ // that: all the data is bit-flipped; a check is made when the scan reaches the end;
+ // and no check is made for k within an unset compressed index.
+
+ // Mask bits before the bit index
+ // mask = 00011111 = -1L >>> (64 - ((index + 1) % 64))
+ long bits = ~data[i] & (LONG_MASK >>> -(index + 1));
+ for (;;) {
+ if (bits != 0) {
+ final int c = (i + 1) * Long.SIZE - Long.numberOfLeadingZeros(bits);
+ return (c << compression) - 1 + left;
+ }
+ if (i == 0) {
+ return left - 1;
+ }
+ bits = ~data[--i];
+ }
+ }
+
+ /**
+ * Returns the index of the first bit that is set to {@code false} that occurs on or
+ * after the specified starting index within the supported range. If no such
+ * bit exists then the {@code capacity} is returned where {@code capacity = index + 1}
+ * with {@code index} the largest index that can be added to the set without an error.
+ *
+ * Assumes {@code k} is within an enabled compressed index.
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the index of the next unset bit, or the {@code capacity} if there is no such bit
+ */
+ int nextClearBit(int k) {
+ // WARNING: No range checks !!!
+ // Assume left <= k <= right
+ final int index = compressIndex(k);
+
+ int i = getLongIndex(index);
+
+ // Note: This method is conceptually the same as nextIndex with the exception
+ // that: all the data is bit-flipped; a check is made for the capacity when the
+ // scan reaches the end; and no check is made for k within an unset compressed index.
+
+ // Mask bits after the bit index
+ // mask = 11111000 = -1L << (fromIndex % 64)
+ long bits = ~data[i] & (LONG_MASK << index);
+ for (;;) {
+ if (bits != 0) {
+ final int c = i * Long.SIZE + Long.numberOfTrailingZeros(bits);
+ return (c << compression) + left;
+ }
+ if (++i == data.length) {
+ // Capacity
+ return right + 1;
+ }
+ bits = ~data[i];
+ }
+ }
+
+ /**
+ * Check the compression is valid.
+ *
+ * @param compression Compression level.
+ * @throws IllegalArgumentException if {@code compression} is not in {@code [1, 31]}
+ */
+ private static void checkCompression(int compression) {
+ if (!(compression > 0 && compression <= 31)) {
+ throw new IllegalArgumentException("Invalid compression: " + compression);
+ }
+ }
+
+ /**
+ * Check the lower bound to the range is valid.
+ *
+ * @param left Lower bound (inclusive).
+ * @throws IllegalArgumentException if {@code left < 0}
+ */
+ private static void checkLeft(int left) {
+ if (left < 0) {
+ throw new IllegalArgumentException("Invalid lower index: " + left);
+ }
+ }
+
+ /**
+ * Check the range is valid.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @throws IllegalArgumentException if {@code right < left}
+ */
+ private static void checkRange(int left, int right) {
+ if (right < left) {
+ throw new IllegalArgumentException(
+ String.format("Invalid range: [%d, %d]", left, right));
+ }
+ }
+
+ /**
+ * {@link IndexIterator} implementation.
+ *
+ * This iterator can efficiently iterate over high-density indices
+ * if the compression level is set to create spacing equal to or above the expected
+ * separation between indices.
+ */
+ private class Iterator implements IndexIterator {
+ /** Iterator left. l is a compressed index. */
+ private int l;
+ /** Iterator right. (r+1) is a clear bit. */
+ private int r;
+ /** Next iterator left. Cached for look ahead functionality. */
+ private int nextL;
+
+ /**
+ * Create an instance.
+ */
+ Iterator() {
+ l = CompressedIndexSet.this.left();
+ r = nextClearBit(l) - 1;
+ if (r < end()) {
+ nextL = nextIndex(r + 1);
+ } else {
+ // Entire range is saturated
+ r = end();
+ }
+ }
+
+ @Override
+ public int left() {
+ return l;
+ }
+
+ @Override
+ public int right() {
+ return r;
+ }
+
+ @Override
+ public int end() {
+ return CompressedIndexSet.this.right();
+ }
+
+ @Override
+ public boolean next() {
+ if (r < end()) {
+ // Reuse the cached next left and advance
+ l = nextL;
+ r = nextClearBit(l) - 1;
+ if (r < end()) {
+ nextL = nextIndex(r + 1);
+ } else {
+ r = end();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean positionAfter(int index) {
+ // Even though this can provide random access we only allow advancing
+ if (r > index) {
+ return true;
+ }
+ if (index < end()) {
+ // Note: Uses 3 scans as it maintains the next left.
+ // For low density indices scanning for next left will be expensive
+ // and it would be more efficient to only compute next left on demand.
+ // For high density indices the next left will be close to
+ // the new right and the cost is low.
+ // This iterator favours use on high density indices. A variant
+ // iterator could be created for comparison purposes.
+
+ if (get(index + 1)) {
+ // (index+1) is set.
+ // Find [left <= index+1 <= right]
+ r = nextClearBit(index + 1) - 1;
+ if (r < end()) {
+ nextL = nextIndex(r + 1);
+ } else {
+ r = end();
+ }
+ l = index + 1;
+ //l = previousClearBit(index) + 1;
+ } else {
+ // (index+1) is clear.
+ // Advance to the next [left, right] pair
+ l = nextIndex(index + 1);
+ r = nextClearBit(l) - 1;
+ if (r < end()) {
+ nextL = nextIndex(r + 1);
+ } else {
+ r = end();
+ }
+ }
+ return true;
+ }
+ // Advance to end. No next left. Not positioned after the target index.
+ l = r = end();
+ return false;
+ }
+
+ @Override
+ public boolean nextAfter(int index) {
+ if (r < end()) {
+ return nextL > index;
+ }
+ // no more indices
+ return true;
+ }
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/CompressedIndexSet2.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/CompressedIndexSet2.java
new file mode 100644
index 000000000..27f9f6e13
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/CompressedIndexSet2.java
@@ -0,0 +1,359 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * A fixed size set of indices within an inclusive range {@code [left, right]}.
+ *
+ * This is a specialised class to implement a data structure similar to a
+ * {@link java.util.BitSet}. It supports a fixed size and contains the methods required to
+ * store and look-up intervals of indices.
+ *
+ * An offset is supported to allow the fixed size to cover a range of indices starting
+ * above 0 with the most efficient usage of storage.
+ *
+ * In contrast to a {@link java.util.BitSet}, the data structure does not store all
+ * indices in the range. Indices are compressed 2-to-1. The structure can return
+ * with 100% accuracy if a query index is not within the range. It cannot return with 100%
+ * accuracy if a query index is contained within the range. The presence of a query index
+ * is a probabilistic statement that there is an index within 1 of the query index.
+ *
+ * Indices are stored offset from {@code left} and compressed. A compressed index
+ * represents 2 real indices:
+ *
+ * When scanning for the next index the identified compressed index is decompressed and
+ * the lower bound of the range represented by the index is returned.
+ *
+ * When scanning for the previous index the identified compressed index is decompressed
+ * and the upper bound of the range represented by the index is returned.
+ *
+ * When scanning in either direction, if the search index is inside a compressed index
+ * the search index is returned.
+ *
+ * Note: Search for the {@link SearchableInterval} interface outside the supported bounds
+ * {@code [left, right]} is not supported and will result in an {@link IndexOutOfBoundsException}.
+ *
+ * See the BloomFilter code in Commons Collections for use of long[] data to store
+ * bits.
+ *
+ * Note: This is a specialised version of {@link CompressedIndexSet} using a fixed
+ * compression of 1. This is used for performance testing. This is the most useful
+ * compression level for the partition algorithm as any compressed key that lies
+ * exactly on a partition index will only require a search for the min/max in the
+ * interval immediately below/above the partition index. A pair of indices (k, k+1)
+ * that is split into two compressed keys and lies exactly on a partition index will
+ * require a search for the min on one side and two maximum values on the other side; or
+ * max on one side and two minimum on the other. Each of these cases is handled by
+ * dedicated heapselect routines to find 1 or 2 values at the edge of a range.
+ *
+ * @since 1.2
+ */
+final class CompressedIndexSet2 implements SearchableInterval {
+ /** All 64-bits bits set. */
+ private static final long LONG_MASK = -1L;
+ /** A bit shift to apply to an integer to divided by 64 (2^6). */
+ private static final int DIVIDE_BY_64 = 6;
+
+ /** Bit indexes. */
+ private final long[] data;
+
+ /** Left bound of the support. */
+ private final int left;
+ /** Right bound of the support. */
+ private final int right;
+
+ /**
+ * Create an instance to store indices within the range {@code [left, right]}.
+ *
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ */
+ private CompressedIndexSet2(int l, int r) {
+ this.left = l;
+ // Note: The functional upper bound may be higher but the next/previous functionality
+ // support scanning in the original [left, right] bound.
+ this.right = r;
+ // Note: This may allow directly writing to index > right if there
+ // is extra capacity.
+ data = new long[getLongIndex((r - l) >>> 1) + 1];
+ }
+
+ /**
+ * Create an instance to store indices within the range {@code [left, right]}.
+ * The instance is initially empty.
+ *
+ * Warning: To use this object as an {@link SearchableInterval} the left and right
+ * indices should be added to the set.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @return the index set
+ * @throws IllegalArgumentException if {@code compression} is not in {@code [1, 31]};
+ * or if {@code right < left}; or if {@code left < 0}
+ */
+ static CompressedIndexSet2 ofRange(int left, int right) {
+ checkLeft(left);
+ checkRange(left, right);
+ return new CompressedIndexSet2(left, right);
+ }
+
+ /**
+ * Initialise an instance with the {@code indices}. The capacity is defined by the
+ * range required to store the minimum and maximum index at the specified
+ * {@code compression} level.
+ *
+ * This object can be used as an {@link SearchableInterval} as the left and right
+ * indices will be set.
+ *
+ * @param indices Indices.
+ * @return the index set
+ * @throws IllegalArgumentException if {@code compression} is not in {@code [1, 31]};
+ * or if {@code indices.length == 0}; or if {@code left < 0}
+ */
+ static CompressedIndexSet2 of(int[] indices) {
+ return of(indices, indices.length);
+ }
+
+ /**
+ * Initialise an instance with the {@code indices}. The capacity is defined by the
+ * range required to store the minimum and maximum index at the specified
+ * {@code compression} level.
+ *
+ * @param indices Indices.
+ * @param n Number of indices.
+ * @return the index set
+ * @throws IllegalArgumentException if {@code compression} is not in {@code [1, 31]};
+ * or if {@code n == 0}; or if {@code left < 0}
+ */
+ static CompressedIndexSet2 of(int[] indices, int n) {
+ if (n <= 0) {
+ throw new IllegalArgumentException("No indices to define the range");
+ }
+ int min = indices[0];
+ int max = min;
+ for (int i = 0; ++i < n;) {
+ min = Math.min(min, indices[i]);
+ max = Math.max(max, indices[i]);
+ }
+ checkLeft(min);
+ final CompressedIndexSet2 set = new CompressedIndexSet2(min, max);
+ for (int i = -1; ++i < n;) {
+ set.set(indices[i]);
+ }
+ return set;
+ }
+
+ /**
+ * Gets the compressed index for this instance using the left bound and the
+ * compression level.
+ *
+ * @param index Index.
+ * @return the compressed index
+ */
+ private int compressIndex(int index) {
+ return (index - left) >>> 1;
+ }
+
+ /**
+ * Gets the filter index for the specified bit index assuming the filter is using
+ * 64-bit longs to store bits starting at index 0.
+ *
+ * The index is assumed to be positive. For a positive index the result will match
+ * {@code bitIndex / 64}. The divide is performed using bit shifts. If the input is negative the
+ * behavior is not defined. The index is assumed to be positive. For a positive index the result will match
+ * {@code 1L << (bitIndex % 64)}. If the input is negative the behavior is not defined. Warning: This has no range checks.
+ *
+ * @param bitIndex the bit index (assumed to be positive)
+ * @return the value of the bit with the specified index
+ */
+ boolean get(int bitIndex) {
+ // WARNING: No range checks !!!
+ final int index = compressIndex(bitIndex);
+ final int i = getLongIndex(index);
+ final long m = getLongBit(index);
+ return (data[i] & m) != 0;
+ }
+
+ /**
+ * Sets the bit at the specified index to {@code true}.
+ *
+ * Warning: This has no range checks.
+ *
+ * @param bitIndex the bit index (assumed to be positive)
+ */
+ void set(int bitIndex) {
+ // WARNING: No range checks !!!
+ final int index = compressIndex(bitIndex);
+ final int i = getLongIndex(index);
+ final long m = getLongBit(index);
+ data[i] |= m;
+ }
+
+
+ @Override
+ public int left() {
+ return left;
+ }
+
+ @Override
+ public int right() {
+ return right;
+ }
+
+ @Override
+ public int previousIndex(int k) {
+ // WARNING: No range checks !!!
+ // Assume left <= k <= right and that left and right are set bits acting as sentinels.
+ final int index = compressIndex(k);
+
+ int i = getLongIndex(index);
+ long bits = data[i];
+
+ // Check if this is within a compressed index. If so return the exact result.
+ if ((bits & getLongBit(index)) != 0) {
+ return k;
+ }
+
+ // Mask bits before the bit index
+ // mask = 00011111 = -1L >>> (64 - ((index + 1) % 64))
+ bits &= LONG_MASK >>> -(index + 1);
+ for (;;) {
+ if (bits != 0) {
+ //(i+1) i
+ // | c |
+ // | | |
+ // 0 001010000
+ final int c = (i + 1) * Long.SIZE - Long.numberOfLeadingZeros(bits);
+ // Decompress the prior unset bit to an index. When inflated this is the
+ // next index above the upper bound of the compressed range so subtract 1.
+ return (c << 1) - 1 + left;
+ }
+ // Unsupported: the interval should contain k
+ //if (i == 0) {
+ // return left - 1;
+ //}
+ bits = data[--i];
+ }
+ }
+
+ @Override
+ public int nextIndex(int k) {
+ // WARNING: No range checks !!!
+ // Assume left <= k <= right and that left and right are set bits acting as sentinels.
+ final int index = compressIndex(k);
+
+ int i = getLongIndex(index);
+ long bits = data[i];
+
+ // Check if this is within a compressed index. If so return the exact result.
+ if ((bits & getLongBit(index)) != 0) {
+ return k;
+ }
+
+ // Mask bits after the bit index
+ // mask = 11111000 = -1L << (index % 64)
+ bits &= LONG_MASK << index;
+ for (;;) {
+ if (bits != 0) {
+ //(i+1) i
+ // | c |
+ // | | |
+ // 0 001010000
+ final int c = i * Long.SIZE + Long.numberOfTrailingZeros(bits);
+ // Decompress the set bit to an index. When inflated this is the lower bound of
+ // the compressed range and is OK for next scanning.
+ return (c << 1) + left;
+ }
+ // Unsupported: the interval should contain k
+ //if (++i == data.length) {
+ // return right + 1;
+ //}
+ bits = data[++i];
+ }
+ }
+
+ /**
+ * Check the lower bound to the range is valid.
+ *
+ * @param left Lower bound (inclusive).
+ * @throws IllegalArgumentException if {@code left < 0}
+ */
+ private static void checkLeft(int left) {
+ if (left < 0) {
+ throw new IllegalArgumentException("Invalid lower index: " + left);
+ }
+ }
+
+ /**
+ * Check the range is valid.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @throws IllegalArgumentException if {@code right < left}
+ */
+ private static void checkRange(int left, int right) {
+ if (right < left) {
+ throw new IllegalArgumentException(
+ String.format("Invalid range: [%d, %d]", left, right));
+ }
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/DoubleDataTransformer.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/DoubleDataTransformer.java
new file mode 100644
index 000000000..8852bedae
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/DoubleDataTransformer.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * Defines a transformer for {@code double[]} arrays.
+ *
+ * This interface is not intended for a public API. It provides a consistent method
+ * to handle partial sorting of {@code double[]} data.
+ *
+ * The transformer allows pre-processing data before applying a sort algorithm.
+ * This is required to handle {@code NaN} and signed-zeros {@code -0.0}.
+ *
+ * Note: The {@code <} relation does not provide a total order on all double
+ * values: {@code -0.0 == 0.0} is {@code true} and a {@code NaN}
+ * value compares neither less than, greater than, nor equal to any value,
+ * even itself.
+ *
+ * The {@link java.util.Arrays#sort(double[])} method respects the order imposed by
+ * {@link Double#compare(double, double)}: {@code -0.0} is treated as less than value
+ * {@code 0.0} and {@code Double.NaN} is considered greater than any
+ * other value and all {@code Double.NaN} values are considered equal.
+ *
+ * This interface allows implementations to respect the behaviour
+ * {@link Double#compare(double, double)}, or implement different behaviour.
+ *
+ * @see java.util.Arrays#sort(double[])
+ * @since 1.2
+ */
+interface DoubleDataTransformer {
+ /**
+ * Pre-process the data for partitioning.
+ *
+ * This method will scan all the data and apply
+ * processing to {@code NaN} and signed-zeros {@code -0.0}.
+ *
+ * A method matching {@link java.util.Arrays#sort(double[])} would move
+ * all {@code NaN} to the end of the array and order zeros. However ordering
+ * zeros is not useful if the data is to be fully or partially reordered
+ * by the caller. Possible solutions are to count signed zeros, or ignore them since
+ * they will not interfere with comparison operators {@code <, ==, >}.
+ *
+ * The length of the data that must be processed by partitioning can be
+ * accessed using {@link #length()}. For example if {@code NaN} values are moved
+ * to the end of the data they are already partitioned. A partition algorithm
+ * can then avoid processing {@code NaN} during partitioning.
+ *
+ * @param data Data.
+ * @return pre-processed data (may be a copy)
+ */
+ double[] preProcess(double[] data);
+
+ /**
+ * Get the size of the data.
+ *
+ * Note: Although the pre-processed data array may be longer than this length some
+ * values may have been excluded from the data (e.g. removal of NaNs). This is the
+ * effective size of the data.
+ *
+ * @return the size
+ */
+ int size();
+
+ /**
+ * Get the length of the pre-processed data that must be partitioned.
+ *
+ * Note: Although the pre-processed data array may be longer than this length it is
+ * only required to partition indices below this length. For example the end of the
+ * array may contain values to ignore from partitioning such as {@code NaN}.
+ *
+ * @return the length
+ */
+ int length();
+
+ /**
+ * Post-process the data after partitioning. This method can restore values that
+ * may have been removed from the pre-processed data, for example signed zeros
+ * or revert any special {@code NaN} value processing.
+ *
+ * If no partition indices are available use {@code null} and {@code n = 0}.
+ *
+ * @param data Data.
+ * @param k Partition indices.
+ * @param n Count of partition indices.
+ */
+ void postProcess(double[] data, int[] k, int n);
+
+ /**
+ * Post-process the data after sorting. This method can restore values that
+ * may have been removed from the pre-processed data, for example signed zeros
+ * or revert any special {@code NaN} value processing.
+ *
+ * Warning: Assumes data is fully sorted in {@code [0, length)} (see {@link #length()}).
+ *
+ * @param data Data.
+ */
+ void postProcess(double[] data);
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/DoubleDataTransformers.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/DoubleDataTransformers.java
new file mode 100644
index 000000000..b5351be76
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/DoubleDataTransformers.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+import java.util.Arrays;
+import java.util.function.Supplier;
+
+/**
+ * Support for creating {@link DoubleDataTransformer} implementations.
+ *
+ * @since 1.2
+ */
+final class DoubleDataTransformers {
+
+ /** No instances. */
+ private DoubleDataTransformers() {}
+
+ /**
+ * Creates a factory to supply a {@link DoubleDataTransformer} based on the
+ * {@code nanPolicy} and data {@code copy} policy.
+ *
+ * The factory will supply instances that may be reused on the same thread.
+ * Multi-threaded usage should create an instance per thread.
+ *
+ * @param nanPolicy NaN policy.
+ * @param copy Set to {@code true} to use a copy of the data.
+ * @return the factory
+ */
+ static Supplier Respects the sort ordering of {@link Double#compare(double, double)}:
+ *
+ * Respects the sort ordering of {@link Double#compare(double, double)}:
+ *
+ * An ideal strategy will pick the tertiles across a variety of data so
+ * to divide the data into [1/3, 1/3, 1/3].
+ *
+ * @see Tertile (Wiktionary)
+ * @since 1.2
+ */
+enum DualPivotingStrategy {
+ /**
+ * Pivot around the medians at 1/3 and 2/3 of the range.
+ *
+ * Requires {@code right - left >= 2}.
+ *
+ * On sorted data the tertiles are: 0.3340 0.6670
+ * On random data the tertiles are:
+ * Requires {@code right - left >= 4}.
+ *
+ * Warning: This has the side effect that the 5 values are also sorted.
+ *
+ * On sorted data the tertiles are: 0.3290 0.6710
+ * On random data the tertiles are:
+ * Requires {@code right - left >= 4}.
+ *
+ * Warning: This has the side effect that the 5 values are also sorted.
+ *
+ * On sorted data the tertiles are: 0.3600 0.6400
+ * On random data the tertiles are:
+ * This allows testing switching to a single pivot strategy against using
+ * a dual pivot partitioning with effectively only 1 pivot. This requires
+ * the dual pivot partition function to check pivot1 == pivot2. If the
+ * dual pivot partition function checks data[pivot1] == data[pivot2] then
+ * the switching choice cannot be enabled/disabled by changing pivoting strategy
+ * and must use another mechanism.
+ *
+ * This specific strategy has been selected for single-pivot switching as
+ * {@link #SORT_5B} benchmarks as consistently fast across all data input.
+ */
+ SORT_5B_SP {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int[] pivot2) {
+ final int pivot1 = SORT_5B.pivotIndex(data, left, right, pivot2);
+ if (data[pivot1] == data[pivot2[0]]) {
+ // Here 3 of 5 middle values are the same.
+ // Present single-pivot pivot methods would not
+ // have an advantage pivoting on p2, p3, or p4; just use 'p2'
+ pivot2[0] = pivot1;
+ }
+ return pivot1;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right) {
+ return SORT_5B.getSampledIndices(left, right);
+ }
+
+ @Override
+ int samplingEffect() {
+ return SORT;
+ }
+ },
+ /**
+ * Pivot around the 2nd and 4th values from 5 approximately uniformly spaced within the range.
+ * Uses points +/- eights from the median: 1/4, 3/8, 1/2, 5/8, 3/4.
+ *
+ * Requires {@code right - left >= 4}.
+ *
+ * Warning: This has the side effect that the 5 values are also sorted.
+ *
+ * On sorted data the tertiles are: 0.3750 0.6250
+ * On random data the tertiles are:
+ * The pivots are points 2 and 4. The other points are either known to be below or
+ * above the pivots; or potentially below or above the pivots.
+ *
+ * Pivot 1: below {@code 1,a,b}; potentially below {@code v,c,d,e}. This ranks
+ * pivot 1 from 4/15 to 8/15 and exactly 5/15 if the input data is sorted/reverse sorted.
+ *
+ * Pivot 2: above {@code 5,y,z}; potentially above {@code e,v,w,x}. This ranks
+ * pivot 2 from 7/15 to 11/15 and exactly 10/15 if the input data is sorted/reverse sorted.
+ *
+ * Warning: This has the side effect that the 15 samples values are partially sorted.
+ *
+ * On sorted data the tertiles are: 0.3140 0.6860
+ * On random data the tertiles are:
+ * Note the bias towards the outer regions.
+ */
+ SORT_5_OF_3 {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int[] pivot2) {
+ // Step size of 1/16 of the length
+ final int len = right - left;
+ final int step = Math.max(1, len >>> 4);
+ final int step3 = step * 3;
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - step3;
+ final int p1 = p2 - step3;
+ final int p4 = p3 + step3;
+ final int p5 = p4 + step3;
+ // 5 medians of 3
+ Sorting.sort3(data, p1 - step, p1, p1 + step);
+ Sorting.sort3(data, p2 - step, p2, p2 + step);
+ Sorting.sort3(data, p3 - step, p3, p3 + step);
+ Sorting.sort3(data, p4 - step, p4, p4 + step);
+ Sorting.sort3(data, p5 - step, p5, p5 + step);
+ // Sort the medians
+ Sorting.sort5(data, p1, p2, p3, p4, p5);
+ pivot2[0] = p4;
+ return p2;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right) {
+ final int len = right - left;
+ final int step = Math.max(1, len >>> 4);
+ final int step3 = step * 3;
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - step3;
+ final int p1 = p2 - step3;
+ final int p4 = p3 + step3;
+ final int p5 = p4 + step3;
+ return new int[] {
+ p1 - step, p1, p1 + step,
+ p2 - step, p2, p2 + step,
+ p3 - step, p3, p3 + step,
+ p4 - step, p4, p4 + step,
+ p5 - step, p5, p5 + step,
+ };
+ }
+
+ @Override
+ int samplingEffect() {
+ return PARTIAL_SORT;
+ }
+ },
+ /**
+ * Pivot around the 2nd and 3rd values from 4 medians approximately uniformly spaced within
+ * the range. The medians are from 3 samples. The 4 samples of 3 do not overlap thus this
+ * method requires {@code right - left >= 11}. The samples can be visualised as 4 sorted
+ * columns:
+ *
+ * The pivots are points 2 and 3. The other points are either known to be below or
+ * above the pivots; or potentially below or above the pivots.
+ *
+ * Pivot 1: below {@code 1,a,b}; potentially below {@code w,c,d}. This ranks
+ * pivot 1 from 4/12 to 7/12 and exactly 5/12 if the input data is sorted/reverse sorted.
+ *
+ * Pivot 2: above {@code 4,y,z}; potentially above {@code d,w,x}. This ranks
+ * pivot 2 from 5/15 to 8/12 and exactly 7/12 if the input data is sorted/reverse sorted.
+ *
+ * Warning: This has the side effect that the 12 samples values are partially sorted.
+ *
+ * On sorted data the tertiles are: 0.3850 0.6160
+ * On random data the tertiles are:
+ * Note the large bias towards the outer regions.
+ */
+ SORT_4_OF_3 {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int[] pivot2) {
+ // Step size of 1/13 of the length: 1/13 ~ 1/16 + 1/64 : 0.0769 ~ 0.0781
+ final int len = right - left;
+ final int step = Math.max(1, (len >>> 4) + (len >>> 6));
+ final int step3 = step * 3;
+ final int p1 = left + (step << 1) - 1;
+ final int p2 = p1 + step3;
+ final int p3 = p2 + step3;
+ final int p4 = p3 + step3;
+ // 5 medians of 3
+ Sorting.sort3(data, p1 - step, p1, p1 + step);
+ Sorting.sort3(data, p2 - step, p2, p2 + step);
+ Sorting.sort3(data, p3 - step, p3, p3 + step);
+ Sorting.sort3(data, p4 - step, p4, p4 + step);
+ // Sort the medians
+ Sorting.sort4(data, p1, p2, p3, p4);
+ pivot2[0] = p3;
+ return p2;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right) {
+ final int len = right - left;
+ final int step = Math.max(1, (len >>> 4) + (len >>> 6));
+ final int step3 = step * 3;
+ final int p1 = left + (step << 1) - 1;
+ final int p2 = p1 + step3;
+ final int p3 = p2 + step3;
+ final int p4 = p3 + step3;
+ return new int[] {
+ p1 - step, p1, p1 + step,
+ p2 - step, p2, p2 + step,
+ p3 - step, p3, p3 + step,
+ p4 - step, p4, p4 + step,
+ };
+ }
+
+ @Override
+ int samplingEffect() {
+ return PARTIAL_SORT;
+ }
+ },
+ /**
+ * Pivot around the 1st and 3rd values from 3 medians approximately uniformly spaced within
+ * the range. The medians are from 3 samples. The 3 samples of 3 do not overlap thus this
+ * method requires {@code right - left >= 8}. The samples can be visualised as 3 sorted
+ * columns:
+ *
+ * The pivots are points 1 and 3. The other points are either known to be below or
+ * above the pivots; or potentially below or above the pivots.
+ *
+ * Pivot 1: below {@code a}; potentially below {@code b, c}. This ranks
+ * pivot 1 from 2/9 to 4/9 and exactly 2/9 if the input data is sorted/reverse sorted.
+ *
+ * Pivot 2: above {@code z}; potentially above {@code x,y}. This ranks
+ * pivot 2 from 6/9 to 8/9 and exactly 8/9 if the input data is sorted/reverse sorted.
+ *
+ * Warning: This has the side effect that the 9 samples values are partially sorted.
+ *
+ * On sorted data the tertiles are: 0.1280 0.8720
+ * On random data the tertiles are:
+ * Note the bias towards the central region.
+ */
+ SORT_3_OF_3 {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int[] pivot2) {
+ // Step size of 1/8 of the length
+ final int len = right - left;
+ final int step = Math.max(1, len >>> 3);
+ final int step3 = step * 3;
+ final int p2 = left + (len >>> 1);
+ final int p1 = p2 - step3;
+ final int p3 = p2 + step3;
+ // 3 medians of 3
+ Sorting.sort3(data, p1 - step, p1, p1 + step);
+ Sorting.sort3(data, p2 - step, p2, p2 + step);
+ Sorting.sort3(data, p3 - step, p3, p3 + step);
+ // Sort the medians
+ Sorting.sort3(data, p1, p2, p3);
+ pivot2[0] = p3;
+ return p1;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right) {
+ final int len = right - left;
+ final int step = Math.max(1, len >>> 3);
+ final int step3 = step * 3;
+ final int p2 = left + (len >>> 1);
+ final int p1 = p2 - step3;
+ final int p3 = p2 + step3;
+ return new int[] {
+ p1 - step, p1, p1 + step,
+ p2 - step, p2, p2 + step,
+ p3 - step, p3, p3 + step,
+ };
+ }
+
+ @Override
+ int samplingEffect() {
+ return PARTIAL_SORT;
+ }
+ },
+ /**
+ * Pivot around the 2nd and 4th values from 5 medians approximately uniformly spaced within
+ * the range. The medians are from 5 samples. The 5 samples of 5 do not overlap thus this
+ * method requires {@code right - left >= 24}. The samples can be visualised as 5 sorted
+ * columns:
+ *
+ * The pivots are points 2 and 4. The other points are either known to be below or
+ * above the pivots; or potentially below or above the pivots.
+ *
+ * Pivot 1: below {@code 1,a,b,f,g}; potentially below {@code q,v,c,d,e,h,i,j}. This ranks
+ * pivot 1 from 6/25 to 14/25 and exactly 8/25 if the input data is sorted/reverse sorted.
+ *
+ * Pivot 2 by symmetry from 12/25 to 20/25 and exactly 18/25 for sorted data.
+ *
+ * Warning: This has the side effect that the 25 samples values are partially sorted.
+ *
+ * On sorted data the tertiles are: 0.3050 0.6950
+ * On random data the tertiles are:
+ * Note the bias towards the outer regions on random data but the inner region on
+ * sorted data.
+ */
+ SORT_5_OF_5 {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int[] pivot2) {
+ // Step size of 1/25 of the length
+ final int len = right - left;
+ final int step = Math.max(1, len / 25);
+ final int step2 = step << 1;
+ final int step5 = step * 5;
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - step5;
+ final int p1 = p2 - step5;
+ final int p4 = p3 + step5;
+ final int p5 = p4 + step5;
+ // 5 medians of 3
+ Sorting.sort5(data, p1 - step2, p1 - step, p1, p1 + step, p1 + step2);
+ Sorting.sort5(data, p2 - step2, p2 - step, p2, p2 + step, p2 + step2);
+ Sorting.sort5(data, p3 - step2, p3 - step, p3, p3 + step, p3 + step2);
+ Sorting.sort5(data, p4 - step2, p4 - step, p4, p4 + step, p4 + step2);
+ Sorting.sort5(data, p5 - step2, p5 - step, p5, p5 + step, p5 + step2);
+ // Sort the medians
+ Sorting.sort5(data, p1, p2, p3, p4, p5);
+ pivot2[0] = p4;
+ return p2;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right) {
+ // Step size of 1/25 of the length
+ final int len = right - left;
+ final int step = Math.max(1, len / 25);
+ final int step2 = step << 1;
+ final int step5 = step * 5;
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - step5;
+ final int p1 = p2 - step5;
+ final int p4 = p3 + step5;
+ final int p5 = p4 + step5;
+ return new int[] {
+ p1 - step2, p1 - step, p1, p1 + step, p1 + step2,
+ p2 - step2, p2 - step, p2, p2 + step, p2 + step2,
+ p3 - step2, p3 - step, p3, p3 + step, p3 + step2,
+ p4 - step2, p4 - step, p4, p4 + step, p4 + step2,
+ p5 - step2, p5 - step, p5, p5 + step, p5 + step2,
+ };
+ }
+
+ @Override
+ int samplingEffect() {
+ return PARTIAL_SORT;
+ }
+ },
+ /**
+ * Pivot around the 3rd and 5th values from 7 approximately uniformly spaced within the range.
+ * Uses points +/- eights from the median: 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8.
+ *
+ * Requires {@code right - left >= 6}.
+ *
+ * Warning: This has the side effect that the 7 values are also sorted.
+ *
+ * On sorted data the tertiles are: 0.3760 0.6240
+ * On random data the tertiles are:
+ * Note the bias towards the outer regions.
+ */
+ SORT_7 {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int[] pivot2) {
+ // Ensure the value is above zero to choose different points!
+ // This is safe if len >= 4.
+ final int len = right - left;
+ final int eighth = Math.max(1, len >>> 3);
+ final int p4 = left + (len >>> 1);
+ final int p3 = p4 - eighth;
+ final int p2 = p3 - eighth;
+ final int p1 = p2 - eighth;
+ final int p5 = p4 + eighth;
+ final int p6 = p5 + eighth;
+ final int p7 = p6 + eighth;
+ Sorting.sort7(data, p1, p2, p3, p4, p5, p6, p7);
+ pivot2[0] = p5;
+ return p3;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right) {
+ final int len = right - left;
+ final int eighth = Math.max(1, len >>> 3);
+ final int p4 = left + (len >>> 1);
+ final int p3 = p4 - eighth;
+ final int p2 = p3 - eighth;
+ final int p1 = p2 - eighth;
+ final int p5 = p4 + eighth;
+ final int p6 = p5 + eighth;
+ final int p7 = p6 + eighth;
+ return new int[] {p1, p2, p3, p4, p5, p6, p7};
+ }
+
+ @Override
+ int samplingEffect() {
+ return SORT;
+ }
+ },
+ /**
+ * Pivot around the 3rd and 6th values from 8 approximately uniformly spaced within the range.
+ * Uses points +/- ninths from the median: m - 4/9, m - 3/9, m - 2/9, m - 1/9; m + 1 + 1/9,
+ * m + 1 + 2/9, m + 1 + 3/9, m + 1 + 4/9.
+ *
+ * Requires {@code right - left >= 7}.
+ *
+ * Warning: This has the side effect that the 8 values are also sorted.
+ *
+ * On sorted data the tertiles are: 0.3380 0.6630
+ * On random data the tertiles are:
+ * Requires {@code right - left >= 10}.
+ *
+ * Warning: This has the side effect that the 11 values are also sorted.
+ *
+ * On sorted data the tertiles are: 0.3460 0.6540
+ * On random data the tertiles are:
+ * Returns two pivots so that {@code data[p1] <= data[p2]}.
+ *
+ * @param data Array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param pivot2 Second pivot.
+ * @return first pivot
+ */
+ abstract int pivotIndex(double[] data, int left, int right, int[] pivot2);
+
+ // The following methods allow the strategy and side effects to be tested
+
+ /**
+ * Get the indices of points that will be sampled.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @return the indices
+ */
+ abstract int[] getSampledIndices(int left, int right);
+
+ /**
+ * Get the effect on the sampled points.
+ * Values are stored using bit inversion. Any positive index will have a negative
+ * representation when stored. An empty slot is indicated by a zero.
+ *
+ * This class has a minimal API. It can be used to ensure a collection of indices of
+ * a known size are unique:
+ *
+ * The functional capacity (number of indices that can be stored) is the next power
+ * of 2 above {@code capacity}; or a minimum size if the requested {@code capacity} is
+ * small.
+ *
+ * @param capacity Capacity (assumed to be positive).
+ */
+ HashIndexSet(int capacity) {
+ if (capacity > MAX_CAPACITY) {
+ throw new IllegalArgumentException("Unsupported capacity: " + capacity);
+ }
+ // This will generate a load factor at capacity in the range (0.25, 0.5]
+ // The use of Math.max will ignore zero/negative capacity requests.
+ set = new int[nextPow2(Math.max(MIN_SIZE, capacity * 2))];
+ }
+
+ /**
+ * Return the memory footprint in bytes. This is always a power of 2.
+ *
+ * This will return the size as if not limited to a capacity of 229.
+ * In this case the size will exceed the maximum size of an {@code int[]} array.
+ *
+ * This method is intended to provide information to choose if the data structure
+ * is memory efficient.
+ *
+ * @param capacity Capacity.
+ * @return the memory footprint
+ */
+ static long memoryFootprint(int capacity) {
+ if (capacity <= (MIN_SIZE >> 1)) {
+ // 4 bytes/int
+ return MIN_SIZE << 2;
+ }
+ // Double the next power of 2, then convert integer count to bytes (4 bytes/int)
+ // * 2 * 4 == * 2^3
+ return Integer.toUnsignedLong(nextPow2(capacity)) << 3;
+ }
+
+ /**
+ * Returns the closest power-of-two number greater than or equal to {@code value}.
+ *
+ * Warning: This will return {@link Integer#MIN_VALUE} for any {@code value} above
+ * {@code 1 << 30}. This is the next power of 2 as an unsigned integer.
+ *
+ * See Bit
+ * Hacks: Rounding up to a power of 2
+ *
+ * @param value Value.
+ * @return the closest power-of-two number greater than or equal to value
+ */
+ private static int nextPow2(int value) {
+ int result = value - 1;
+ result |= result >>> 1;
+ result |= result >>> 2;
+ result |= result >>> 4;
+ result |= result >>> 8;
+ return (result | (result >>> 16)) + 1;
+ }
+
+ /**
+ * Adds the {@code index} to the set.
+ *
+ * @param index Index.
+ * @return true if the set was modified by the operation
+ * @throws IndexOutOfBoundsException if the index is negative
+ */
+ boolean add(int index) {
+ if (index < 0) {
+ throw new IndexOutOfBoundsException(INVALID_INDEX + index);
+ }
+ final int[] keys = set;
+ final int key = ~index;
+ final int mask = keys.length - 1;
+ int pos = mix(index) & mask;
+ int curr = keys[pos];
+ if (curr < 0) {
+ if (curr == key) {
+ // Already present
+ return false;
+ }
+ // Probe
+ while ((curr = keys[pos = (pos + 1) & mask]) < 0) {
+ if (curr == key) {
+ // Already present
+ return false;
+ }
+ }
+ }
+ // Insert
+ keys[pos] = key;
+ // Here the load factor is 0.5: Test if size > keys.length * 0.5
+ if (++size > (mask + 1) >>> 1) {
+ // This is where we should grow the size of the set and re-insert
+ // all current keys into the new key storage. Here we are using a
+ // fixed capacity so raise an exception.
+ throw new IllegalStateException("Functional capacity exceeded: " + (keys.length >>> 1));
+ }
+ return true;
+ }
+
+ /**
+ * Test if the {@code index} is in the set.
+ *
+ * This method is present for testing. It is not required when filtering a collection
+ * of indices with duplicates to a unique set of indices.
+ *
+ * @param index Index.
+ * @return true if the set contains the index
+ * @throws IndexOutOfBoundsException if the index is negative
+ */
+ boolean contains(int index) {
+ if (index < 0) {
+ throw new IndexOutOfBoundsException(INVALID_INDEX + index);
+ }
+ final int[] keys = set;
+ final int mask = keys.length - 1;
+ int pos = mix(index) & mask;
+ int curr = keys[pos];
+ if (curr == 0) {
+ return false;
+ }
+ final int key = ~index;
+ if (curr == key) {
+ return true;
+ }
+ // Probe
+ while (true) {
+ pos = (pos + 1) & mask;
+ curr = keys[pos];
+ if (curr == 0) {
+ // No more entries
+ return false;
+ }
+ if (curr == key) {
+ return true;
+ }
+ }
+ }
+
+ /**
+ * Mix the bits of an integer.
+ *
+ * This is the fast hash function used in the linear hash implementation in the Koloboke Collections.
+ *
+ * @param x Bits.
+ * @return the mixed bits
+ */
+ private static int mix(int x) {
+ final int h = x * PHI;
+ return h ^ (h >>> 16);
+ }
+
+ /**
+ * Returns the number of distinct indices in the set.
+ *
+ * @return the size
+ */
+ int size() {
+ return size;
+ }
+
+ /**
+ * Write each index in the set into the provided array. Returns the number of indices.
+ *
+ * The caller must ensure the output array has sufficient capacity.
+ *
+ * Warning: The indices are not ordered.
+ *
+ * This method is present for testing. It is not required when filtering a collection
+ * of indices with duplicates to a unique set of indices. It can be used to write
+ * out the unique indices, but they are not in the encounter order of indices
+ * {@link #add(int) added} to the set.
+ *
+ * @param a Output array.
+ * @return count of indices
+ * @see #size()
+ */
+ int toArray(int[] a) {
+ final int[] keys = set;
+ int c = 0;
+ for (final int key : keys) {
+ if (key < 0) {
+ a[c++] = ~key;
+ }
+ }
+ // assert c == size
+ return c;
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexIntervals.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexIntervals.java
new file mode 100644
index 000000000..2a94d293a
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexIntervals.java
@@ -0,0 +1,560 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * Support for creating {@link SearchableInterval}, {@link SearchableInterval2} and
+ * {@link UpdatingInterval} implementations.
+ *
+ * @since 1.2
+ */
+final class IndexIntervals {
+ /** Size to perform key analysis. This avoids key analysis for a small number of keys. */
+ private static final int KEY_ANALYSIS_SIZE = 10;
+ /** The upper threshold to use a modified insertion sort to find unique indices. */
+ private static final int INDICES_INSERTION_SORT_SIZE = 20;
+
+ /** Size to use a {@link BinarySearchKeyInterval}. Note that the
+ * {@link ScanningKeyInterval} uses points within the range to fast-forward
+ * scanning which improves performance significantly for a few hundred indices.
+ * Performance is similar when indices are in the thousands. Binary search is
+ * much faster when there are multiple thousands of indices. */
+ private static final int BINARY_SEARCH_SIZE = 2048;
+
+ /** No instances. */
+ private IndexIntervals() {}
+
+ /**
+ * Returns an interval that covers all indices ({@code [0, MAX_VALUE)}).
+ *
+ * When used with a partition algorithm will cause a full sort
+ * of the range between the bounds {@code [ka, kb]}.
+ *
+ * @return the interval
+ */
+ static SearchableInterval anyIndex() {
+ return AnyIndex.INSTANCE;
+ }
+
+ /**
+ * Returns an interval that covers all indices ({@code [0, MAX_VALUE)}).
+ *
+ * When used with a partition algorithm will cause a full sort
+ * of the range between the bounds {@code [ka, kb]}.
+ *
+ * @return the interval
+ */
+ static SearchableInterval2 anyIndex2() {
+ return AnyIndex.INSTANCE;
+ }
+
+ /**
+ * Returns an interval that covers a single index {@code k}. The interval cannot
+ * be split or the bounds updated.
+ *
+ * @param k Index.
+ * @return the interval
+ */
+ static UpdatingInterval interval(int k) {
+ return new PointInterval(k);
+ }
+
+ /**
+ * Returns an interval that covers all indices {@code [left, right]}.
+ * This method will sort the input bound to ensure {@code left <= right}.
+ *
+ * When used with a partition algorithm will cause a full sort
+ * of the range between the bounds {@code [left, right]}.
+ *
+ * @param left Left bound (inclusive).
+ * @param right Right bound (inclusive).
+ * @return the interval
+ */
+ static UpdatingInterval interval(int left, int right) {
+ // Sort the bound
+ final int l = left < right ? left : right;
+ final int r = left < right ? right : left;
+ return new RangeInterval(l, r);
+ }
+
+ /**
+ * Returns an interval that covers the specified indices {@code k}.
+ *
+ * @param k Indices.
+ * @param n Count of indices (must be strictly positive).
+ * @return the interval
+ */
+ static SearchableInterval createSearchableInterval(int[] k, int n) {
+ // Note: A typical use case is to have a few indices. Thus the heuristics
+ // in this method should be very fast when n is small. Here we skip them
+ // completely when the number of keys is tiny.
+
+ if (n > KEY_ANALYSIS_SIZE) {
+ // Here we use a simple test based on the number of comparisons required
+ // to perform the expected next/previous look-ups.
+ // It is expected that we can cut n keys a maximum of n-1 times.
+ // Each cut requires a scan next/previous to divide the interval into two intervals:
+ //
+ // cut
+ // |
+ // k1--------k2---------k3---- ... ---------kn initial interval
+ // <--| find previous
+ // find next |-->
+ // k1 k2---------k3---- ... ---------kn divided intervals
+ //
+ // A ScanningKeyIndexInterval will scan n keys in both directions using n comparisons
+ // (if next takes m comparisons then previous will take n - m comparisons): Order(n^2)
+ // An IndexSet will scan from the cut location and find a match in time proportional to
+ // the index density. Average density is (size / n) and the scanning covers 64
+ // indices together: Order(2 * n * (size / n) / 64) = Order(size / 32)
+
+ // Get the range. This will throw an exception if there are no indices.
+ int min = k[n - 1];
+ int max = min;
+ for (int i = n - 1; --i >= 0;) {
+ min = Math.min(min, k[i]);
+ max = Math.max(max, k[i]);
+ }
+
+ // Transition when n * n ~ size / 32
+ // Benchmarking shows this is a reasonable approximation when size is small.
+ // Speed of the IndexSet is approximately independent of n and proportional to size.
+ // Large size observes degrading performance more than expected from a linear relationship.
+ // Note the memory required is approximately (size / 8) bytes.
+ // We introduce a penalty for each 4x increase over size = 2^20 (== 128KiB).
+ // n * n = size/32 * 2^log4(size / 2^20)
+
+ // Transition point: n = sqrt(size/32)
+ // size n
+ // 2^10 5.66
+ // 2^15 32.0
+ // 2^20 181.0
+
+ // Transition point: n = sqrt(size/32 * 2^(log4(size/2^20))))
+ // size n
+ // 2^22 512.0
+ // 2^24 1448.2
+ // 2^28 11585
+ // 2^31 55108
+
+ final int size = max - min + 1;
+
+ // Divide by 32 is a shift of 5. This is reduced for each 4-fold size above 2^20.
+ // At 2^31 the shift reduces to 0.
+ int shift = 5;
+ if (size > (1 << 20)) {
+ // log4(size/2^20) == (log2(size) - 20) / 2
+ shift -= (ceilLog2(size) - 20) >>> 1;
+ }
+
+ if ((long) n * n > (size >> shift)) {
+ // Do not call IndexSet.of(k, n) which repeats the min/max search
+ // (especially given n is likely to be large).
+ final IndexSet interval = IndexSet.ofRange(min, max);
+ for (int i = n; --i >= 0;) {
+ interval.set(k[i]);
+ }
+ return interval;
+ }
+
+ // Switch to binary search above a threshold.
+ // Note this invalidates the speed assumptions based on the number of comparisons.
+ // Benchmarking shows this is useful when the keys are in the thousands so this
+ // would be used when data size is in the millions.
+ if (n > BINARY_SEARCH_SIZE) {
+ final int unique = Sorting.sortIndices2(k, n);
+ return BinarySearchKeyInterval.of(k, unique);
+ }
+
+ // Fall-though to the ScanningKeyIndexInterval...
+ }
+
+ // This is the typical use case.
+ // Here n is small, or small compared to the min/max range of indices.
+ // Use a special method to sort unique indices (detects already sorted indices).
+ final int unique = Sorting.sortIndices2(k, n);
+
+ return ScanningKeyInterval.of(k, unique);
+ }
+
+ /**
+ * Returns an interval that covers the specified indices {@code k}.
+ *
+ * @param k Indices.
+ * @param n Count of indices (must be strictly positive).
+ * @return the interval
+ */
+ static UpdatingInterval createUpdatingInterval(int[] k, int n) {
+ // Note: A typical use case is to have a few indices. Thus the heuristics
+ // in this method should be very fast when n is small.
+ // We have a choice between a KeyUpdatingInterval which requires
+ // sorted keys or a BitIndexUpdatingInterval which handles keys in any order.
+ // The purpose of the heuristics is to avoid a very bad choice of data structure,
+ // rather than choosing the best data structure in all situations. As long as the
+ // choice is reasonable the speed will not impact a partition algorithm.
+
+ // Simple cases
+ if (n < 3) {
+ if (n == 1 || k[0] == k[1]) {
+ // 1 unique value
+ return IndexIntervals.interval(k[0]);
+ }
+ // 2 unique values
+ if (Math.abs(k[0] - k[1]) == 1) {
+ // Small range
+ return IndexIntervals.interval(k[0], k[1]);
+ }
+ // 2 well separated values
+ if (k[1] < k[0]) {
+ final int v = k[0];
+ k[0] = k[1];
+ k[1] = v;
+ }
+ return KeyUpdatingInterval.of(k, 2);
+ }
+
+ // Strategy: Must be fast on already ascending data.
+ // Note: The recommended way to generate a lot of partition indices is to
+ // generate in sequence.
+
+ // n <= small:
+ // Modified insertion sort (naturally finds ascending data)
+ // n > small:
+ // Look for ascending sequence and compact
+ // else:
+ // Remove duplicates using an order(1) data structure and sort
+
+ if (n <= INDICES_INSERTION_SORT_SIZE) {
+ final int unique = Sorting.sortIndicesInsertionSort(k, n);
+ return KeyUpdatingInterval.of(k, unique);
+ }
+
+ if (isAscending(k, n)) {
+ // For sorted keys the KeyUpdatingInterval is fast. It may be slower than the
+ // BitIndexUpdatingInterval depending on data length but not significantly
+ // slower and the difference is lost in the time taken for partitioning.
+ // So always use the keys.
+ final int unique = compressDuplicates(k, n);
+ return KeyUpdatingInterval.of(k, unique);
+ }
+
+ // At least 20 indices that are partially unordered.
+
+ // Find min/max to understand the range.
+ int min = k[n - 1];
+ int max = min;
+ for (int i = n - 1; --i >= 0;) {
+ min = Math.min(min, k[i]);
+ max = Math.max(max, k[i]);
+ }
+
+ // Here we use a simple test based on the number of comparisons required
+ // to perform the expected next/previous look-ups after a split.
+ // It is expected that we can cut n keys a maximum of n-1 times.
+ // Each cut requires a scan next/previous to divide the interval into two intervals:
+ //
+ // cut
+ // |
+ // k1--------k2---------k3---- ... ---------kn initial interval
+ // <--| find previous
+ // find next |-->
+ // k1 k2---------k3---- ... ---------kn divided intervals
+ //
+ // An BitSet will scan from the cut location and find a match in time proportional to
+ // the index density. Average density is (size / n) and the scanning covers 64
+ // indices together: Order(2 * n * (size / n) / 64) = Order(size / 32)
+
+ // Sorted keys: Sort time Order(n log(n)) : Splitting time Order(log(n)) (binary search approx)
+ // Bit keys : Sort time Order(1) : Splitting time Order(size / 32)
+
+ // Transition when n * n ~ size / 32
+ // Benchmarking shows this is a reasonable approximation when size < 2^20.
+ // The speed of the bit keys is approximately independent of n and proportional to size.
+ // Large size observes degrading performance of the bit keys vs sorted keys.
+ // We introduce a penalty for each 4x increase over size = 2^20.
+ // n * n = size/32 * 2^log4(size / 2^20)
+ // The transition point still favours the bit keys when sorted keys would be faster.
+ // However the difference is held within 4x and the BitSet type structure is still fast
+ // enough to be negligible against the speed of partitioning.
+
+ // Transition point: n = sqrt(size/32)
+ // size n
+ // 2^10 5.66
+ // 2^15 32.0
+ // 2^20 181.0
+
+ // Transition point: n = sqrt(size/32 * 2^(log4(size/2^20))))
+ // size n
+ // 2^22 512.0
+ // 2^24 1448.2
+ // 2^28 11585
+ // 2^31 55108
+
+ final int size = max - min + 1;
+
+ // Divide by 32 is a shift of 5. This is reduced for each 4-fold size above 2^20.
+ // At 2^31 the shift reduces to 0.
+ int shift = 5;
+ if (size > (1 << 20)) {
+ // log4(size/2^20) == (log2(size) - 20) / 2
+ shift -= (ceilLog2(size) - 20) >>> 1;
+ }
+
+ if ((long) n * n > (size >> shift)) {
+ // Do not call BitIndexUpdatingInterval.of(k, n) which repeats the min/max search
+ // (especially given n is likely to be large).
+ final BitIndexUpdatingInterval interval = BitIndexUpdatingInterval.ofRange(min, max);
+ for (int i = n; --i >= 0;) {
+ interval.set(k[i]);
+ }
+ return interval;
+ }
+
+ // Sort with a hash set to filter indices
+ final int unique = Sorting.sortIndicesHashIndexSet(k, n);
+ return KeyUpdatingInterval.of(k, unique);
+ }
+
+ /**
+ * Test the data is in ascending order: {@code data[i] <= data[i+1]} for all {@code i}.
+ * Data is assumed to be at least length 1.
+ *
+ * @param data Data.
+ * @param n Length of data.
+ * @return true if ascending
+ */
+ private static boolean isAscending(int[] data, int n) {
+ for (int i = 0; ++i < n;) {
+ if (data[i] < data[i - 1]) {
+ // descending
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Compress duplicates in the ascending data.
+ *
+ * Warning: Requires {@code n > 0}.
+ *
+ * @param data Indices.
+ * @param n Number of indices.
+ * @return the number of unique indices
+ */
+ private static int compressDuplicates(int[] data, int n) {
+ // Compress to remove duplicates
+ int last = 0;
+ int top = data[0];
+ for (int i = 0; ++i < n;) {
+ final int v = data[i];
+ if (v == top) {
+ continue;
+ }
+ top = v;
+ data[++last] = v;
+ }
+ return last + 1;
+ }
+
+ /**
+ * Compute {@code ceil(log2(x))}. This is valid for all strictly positive {@code x}.
+ *
+ * Returns -1 for {@code x = 0} in place of -infinity.
+ *
+ * @param x Value.
+ * @return {@code ceil(log2(x))}
+ */
+ private static int ceilLog2(int x) {
+ return 32 - Integer.numberOfLeadingZeros(x - 1);
+ }
+
+ /**
+ * {@link SearchableInterval} for range {@code [0, MAX_VALUE)}.
+ */
+ private static final class AnyIndex implements SearchableInterval, SearchableInterval2 {
+ /** Singleton instance. */
+ static final AnyIndex INSTANCE = new AnyIndex();
+
+ @Override
+ public int left() {
+ return 0;
+ }
+
+ @Override
+ public int right() {
+ return Integer.MAX_VALUE - 1;
+ }
+
+ @Override
+ public int previousIndex(int k) {
+ return k;
+ }
+
+ @Override
+ public int nextIndex(int k) {
+ return k;
+ }
+
+ @Override
+ public int split(int ka, int kb, int[] upper) {
+ upper[0] = kb + 1;
+ return ka - 1;
+ }
+
+ // IndexInterval2
+ // This is exactly the same as IndexInterval as the pointers i are the same as the keys k
+
+ @Override
+ public int start() {
+ return 0;
+ }
+
+ @Override
+ public int end() {
+ return Integer.MAX_VALUE - 1;
+ }
+
+ @Override
+ public int index(int i) {
+ return i;
+ }
+
+ @Override
+ public int previous(int i, int k) {
+ return k;
+ }
+
+ @Override
+ public int next(int i, int k) {
+ return k;
+ }
+
+ @Override
+ public int split(int i1, int i2, int ka, int kb, int[] upper) {
+ upper[0] = kb + 1;
+ return ka - 1;
+ }
+ }
+
+ /**
+ * {@link UpdatingInterval} for a single {@code index}.
+ */
+ static final class PointInterval implements UpdatingInterval {
+ /** Left/right bound of the interval. */
+ private final int index;
+
+ /**
+ * @param k Left/right bound.
+ */
+ PointInterval(int k) {
+ this.index = k;
+ }
+
+ @Override
+ public int left() {
+ return index;
+ }
+
+ @Override
+ public int right() {
+ return index;
+ }
+
+ // Note: An UpdatingInterval is only required to update when a target index
+ // is within [left, right]. This is not possible for a single point.
+
+ @Override
+ public int updateLeft(int k) {
+ throw new UnsupportedOperationException("updateLeft should not be called");
+ }
+
+ @Override
+ public int updateRight(int k) {
+ throw new UnsupportedOperationException("updateRight should not be called");
+ }
+
+ @Override
+ public UpdatingInterval splitLeft(int ka, int kb) {
+ throw new UnsupportedOperationException("splitLeft should not be called");
+ }
+
+ @Override
+ public UpdatingInterval splitRight(int ka, int kb) {
+ throw new UnsupportedOperationException("splitRight should not be called");
+ }
+ }
+
+ /**
+ * {@link UpdatingInterval} for range {@code [left, right]}.
+ */
+ static final class RangeInterval implements UpdatingInterval {
+ /** Left bound of the interval. */
+ private int left;
+ /** Right bound of the interval. */
+ private int right;
+
+ /**
+ * @param left Left bound.
+ * @param right Right bound.
+ */
+ RangeInterval(int left, int right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ @Override
+ public int left() {
+ return left;
+ }
+
+ @Override
+ public int right() {
+ return right;
+ }
+
+ @Override
+ public int updateLeft(int k) {
+ // Assume left < k <= right
+ left = k;
+ return k;
+ }
+
+ @Override
+ public int updateRight(int k) {
+ // Assume left <= k < right
+ right = k;
+ return k;
+ }
+
+ @Override
+ public UpdatingInterval splitLeft(int ka, int kb) {
+ // Assume left < ka <= kb < right
+ final int lower = left;
+ left = kb + 1;
+ return new RangeInterval(lower, ka - 1);
+ }
+
+ @Override
+ public UpdatingInterval splitRight(int ka, int kb) {
+ // Assume left < ka <= kb < right
+ final int upper = right;
+ right = ka - 1;
+ return new RangeInterval(kb + 1, upper);
+ }
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexIterator.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexIterator.java
new file mode 100644
index 000000000..959717674
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexIterator.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * An iterator of indices used for partitioning an array into multiple regions.
+ *
+ * The iterator provides the functionality to iterate over blocks of indices
+ * defined by an inclusive interval {@code [left, right]}:
+ *
+ * If there are no more indices the result of {@link #left()} and
+ * {@link #right()} is undefined.
+ *
+ * @return true if the iterator was advanced
+ */
+ boolean next();
+
+ /**
+ * Advance the iterator so that {@code right > index}.
+ *
+ * If there are no more indices the result of {@link #left()} and
+ * {@link #right()} is undefined.
+ *
+ * The default implementation is:
+ * Implementations may choose to set {@code left = index + 1} if the iterator
+ * range spans the {@code index}; all indices before {@code index} are no
+ * longer available for iteration.
+ *
+ * @param index Index.
+ * @return true if {@code right > index}
+ */
+ default boolean positionAfter(int index) {
+ while (right() <= index) {
+ if (!next()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Return true if the start of the next block of indices is after the specified {@code index}.
+ * A partition algorithm can use this to decide how to process the current block.
+ *
+ * The default implementation is only true if there is no next index:
+ * This method handles duplicate indices; indices can be in any order.
+ *
+ * @param k1 Index.
+ * @param k2 Index.
+ * @return the iterator
+ */
+ static IndexIterator ofInterval(int k1, int k2) {
+ // Eliminate duplicates
+ if (k1 == k2) {
+ return new SingleIndex(k1);
+ }
+ // Sort
+ final int i1 = k1 < k2 ? k1 : k2;
+ final int i2 = k1 < k2 ? k2 : k1;
+ return new SingleInterval(i1, i2);
+ }
+
+ /**
+ * {@link IndexIterator} for a single index.
+ */
+ private static final class SingleIndex implements IndexIterator {
+ /** Index. */
+ private final int k;
+
+ /**
+ * @param k Index.
+ */
+ SingleIndex(int k) {
+ this.k = k;
+ }
+
+ @Override
+ public int left() {
+ return k;
+ }
+
+ @Override
+ public int right() {
+ return k;
+ }
+
+ @Override
+ public int end() {
+ return k;
+ }
+
+ @Override
+ public boolean next() {
+ return false;
+ }
+
+ @Override
+ public boolean positionAfter(int index) {
+ return k > index;
+ }
+
+ @Override
+ public boolean nextAfter(int index) {
+ // right >= end : no next index
+ return true;
+ }
+ }
+
+ /**
+ * {@link IndexIterator} for a single closed interval {@code [left, right]}.
+ */
+ private static final class SingleInterval implements IndexIterator {
+ /** Left index. */
+ private final int l;
+ /** Right index. */
+ private final int r;
+
+ /**
+ * @param l Left index.
+ * @param r Right index.
+ */
+ SingleInterval(int l, int r) {
+ this.l = l;
+ this.r = r;
+ }
+
+ @Override
+ public int left() {
+ return l;
+ }
+
+ @Override
+ public int right() {
+ return r;
+ }
+
+ @Override
+ public int end() {
+ return r;
+ }
+
+ @Override
+ public boolean next() {
+ return false;
+ }
+
+ @Override
+ public boolean positionAfter(int index) {
+ return r > index;
+ }
+
+ @Override
+ public boolean nextAfter(int index) {
+ // right >= end : no next index
+ return true;
+ }
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexSet.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexSet.java
new file mode 100644
index 000000000..6467b80e7
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexSet.java
@@ -0,0 +1,1214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+import java.util.Arrays;
+import java.util.function.IntConsumer;
+
+/**
+ * A fixed size set of indices within an inclusive range {@code [left, right]}.
+ *
+ * This is a specialised class to implement a reduced API similar to a
+ * {@link java.util.BitSet}. It uses no bounds range checks and supports only a
+ * fixed size. It contains the methods required to store and look-up intervals of indices.
+ *
+ * An offset is supported to allow the fixed size to cover a range of indices starting
+ * above 0 with the most efficient usage of storage.
+ *
+ * The class has methods to directly set and get bits in the range.
+ * Implementations of the {@link PivotCache} interface use range checks and maintain
+ * floating pivots flanking the range to allow bracketing any index within the range.
+ *
+ * Stores all pivots between the support {@code [left, right]}. Uses two
+ * floating pivots which are the closest known pivots surrounding this range.
+ *
+ * See the BloomFilter code in Commons Collections for use of long[] data to store
+ * bits.
+ *
+ * @since 1.2
+ */
+final class IndexSet implements PivotCache, SearchableInterval, SearchableInterval2 {
+ /** All 64-bits bits set. */
+ private static final long LONG_MASK = -1L;
+ /** A bit shift to apply to an integer to divided by 64 (2^6). */
+ private static final int DIVIDE_BY_64 = 6;
+ /** Default value for an unset upper floating pivot.
+ * Set as a value higher than any valid array index. */
+ private static final int UPPER_DEFAULT = Integer.MAX_VALUE;
+
+ /** Bit indexes. */
+ private final long[] data;
+
+ /** Left bound of the support. */
+ private final int left;
+ /** Right bound of the support. */
+ private final int right;
+ /** The upstream pivot closest to the left bound of the support.
+ * Provides a lower search bound for the range [left, right]. */
+ private int lowerPivot = -1;
+ /** The downstream pivot closest to the right bound of the support.
+ * Provides an upper search bound for the range [left, right]. */
+ private int upperPivot = UPPER_DEFAULT;
+
+ /**
+ * Create an instance to store indices within the range {@code [left, right]}.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ private IndexSet(int left, int right) {
+ this.left = left;
+ this.right = right;
+ // Allocate storage to store index==right
+ // Note: This may allow directly writing to index > right if there
+ // is extra capacity. Ranges checks to prevent this are provided by
+ // the PivotCache.add(int) method rather than using set(int).
+ data = new long[getLongIndex(right - left) + 1];
+ }
+
+ /**
+ * Create an instance to store indices within the range {@code [left, right]}.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @return the index set
+ * @throws IllegalArgumentException if {@code right < left}
+ */
+ static IndexSet ofRange(int left, int right) {
+ if (left < 0) {
+ throw new IllegalArgumentException("Invalid lower index: " + left);
+ }
+ checkRange(left, right);
+ return new IndexSet(left, right);
+ }
+
+ /**
+ * Initialise an instance with the {@code indices}. The capacity is defined by the
+ * range required to store the minimum and maximum index.
+ *
+ * @param indices Indices.
+ * @return the index set
+ * @throws IllegalArgumentException if {@code indices.length == 0}
+ */
+ static IndexSet of(int[] indices) {
+ return of(indices, indices.length);
+ }
+
+ /**
+ * Initialise an instance with the {@code indices}. The capacity is defined by the
+ * range required to store the minimum and maximum index.
+ *
+ * @param indices Indices.
+ * @param n Number of indices.
+ * @return the index set
+ * @throws IllegalArgumentException if {@code n == 0}
+ */
+ static IndexSet of(int[] indices, int n) {
+ if (n <= 0) {
+ throw new IllegalArgumentException("No indices to define the range");
+ }
+ int min = indices[0];
+ int max = min;
+ for (int i = 1; i < n; i++) {
+ min = Math.min(min, indices[i]);
+ max = Math.max(max, indices[i]);
+ }
+ final IndexSet set = IndexSet.ofRange(min, max);
+ for (int i = 0; i < n; i++) {
+ set.set(indices[i]);
+ }
+ return set;
+ }
+
+ /**
+ * Return the memory footprint in bytes. This is always a multiple of 64.
+ *
+ * The result is {@code 8 * ceil((right - left + 1) / 64)}.
+ *
+ * This method is intended to provide information to choose if the data structure
+ * is memory efficient.
+ *
+ * Warning: It is assumed {@code 0 <= left <= right}. Use with the min/max index
+ * that is to be stored.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @return the memory footprint
+ */
+ static long memoryFootprint(int left, int right) {
+ return (getLongIndex(right - left) + 1L) * Long.BYTES;
+ }
+
+ /**
+ * Gets the filter index for the specified bit index assuming the filter is using
+ * 64-bit longs to store bits starting at index 0.
+ *
+ * The index is assumed to be positive. For a positive index the result will match
+ * {@code bitIndex / 64}. The divide is performed using bit shifts. If the input is negative the
+ * behavior is not defined. The index is assumed to be positive. For a positive index the result will match
+ * {@code 1L << (bitIndex % 64)}. If the input is negative the behavior is not defined. This method can be used to assess the saturation of the indices in the range.
+ *
+ * @return the number of bits set to {@code true} in this {@code IndexSet} using a
+ * compression of 2 to 1
+ */
+ public int cardinality2() {
+ int c = 0;
+ for (long x : data) {
+ // Shift and mask out the bits that were shifted
+ x = (x | (x >>> 1)) & 0b0101010101010101010101010101010101010101010101010101010101010101L;
+ // Add [0, 32]
+ c += Long.bitCount(x);
+ }
+ // Multiply by 2
+ return c << 1;
+ }
+
+ /**
+ * Returns the number of bits set to {@code true} in this {@code IndexSet} using a
+ * compression of 4 to 1. This counts as enabled all bits of each consecutive
+ * 4 bits if any of the consecutive 4 bits are set to {@code true}.
+ * This method can be used to assess the saturation of the indices in the range.
+ *
+ * @return the number of bits set to {@code true} in this {@code IndexSet} using a compression
+ * of 8 to 1
+ */
+ public int cardinality4() {
+ int c = 0;
+ for (long x : data) {
+ // Shift powers of 2 and mask out the bits that were shifted
+ x = x | (x >>> 1);
+ x = (x | (x >>> 2)) & 0b0001000100010001000100010001000100010001000100010001000100010001L;
+ // Expect a population count intrinsic method
+ // Add [0, 16]
+ c += Long.bitCount(x);
+ }
+ // Multiply by 4
+ return c << 2;
+ }
+
+ /**
+ * Returns the number of bits set to {@code true} in this {@code IndexSet} using a
+ * compression of 8 to 1. This counts as enabled all bits of each consecutive
+ * 8 bits if any of the consecutive 8 bits are set to {@code true}.
+ * This method can be used to assess the saturation of the indices in the range.
+ *
+ * @return the number of bits set to {@code true} in this {@code IndexSet} using a compression
+ * of 8 to 1
+ */
+ public int cardinality8() {
+ int c = 0;
+ for (long x : data) {
+ // Shift powers of 2 and mask out the bits that were shifted
+ x = x | (x >>> 1);
+ x = x | (x >>> 2);
+ x = (x | (x >>> 4)) & 0b0000000100000001000000010000000100000001000000010000000100000001L;
+ // Expect a population count intrinsic method
+ // Add [0, 8]
+ c += Long.bitCount(x);
+ }
+ // Multiply by 8
+ return c << 3;
+ }
+
+ /**
+ * Returns the number of bits set to {@code true} in this {@code IndexSet} using a
+ * compression of 16 to 1. This counts as enabled all bits of each consecutive
+ * 16 bits if any of the consecutive 16 bits are set to {@code true}.
+ * This method can be used to assess the saturation of the indices in the range.
+ *
+ * @return the number of bits set to {@code true} in this {@code IndexSet} using a compression
+ * of 16 to 1
+ */
+ public int cardinality16() {
+ int c = 0;
+ for (long x : data) {
+ // Shift powers of 2 and mask out the bits that were shifted
+ x = x | (x >>> 1);
+ x = x | (x >>> 2);
+ x = x | (x >>> 4);
+ x = (x | (x >>> 8)) & 0b0000000000000001000000000000000100000000000000010000000000000001L;
+ // Count the bits using folding
+ // if x = mask:
+ // (x += (x >>> 16)) : 0000000000000001000000000000001000000000000000100000000000000010
+ // (x += (x >>> 32)) : 0000000100000001000000100000001000000011000000110000010000000100
+ x = x + (x >>> 16); // put count of each 32 bits into their lowest 2 bits
+ x = x + (x >>> 32); // put count of each 64 bits into their lowest 3 bits
+ // Add [0, 4]
+ c += (int) x & 0b111;
+ }
+ // Multiply by 16
+ return c << 4;
+ }
+
+ /**
+ * Returns the number of bits set to {@code true} in this {@code IndexSet} using a
+ * compression of 32 to 1. This counts as enabled all bits of each consecutive
+ * 32 bits if any of the consecutive 32 bits are set to {@code true}.
+ * This method can be used to assess the saturation of the indices in the range.
+ *
+ * @return the number of bits set to {@code true} in this {@code IndexSet} using a compression
+ * of 32 to 1
+ */
+ public int cardinality32() {
+ int c = 0;
+ for (final long x : data) {
+ // Are any lower 32-bits or upper 32-bits set?
+ c += (int) x != 0 ? 1 : 0;
+ c += (x >>> 32) != 0L ? 1 : 0;
+ }
+ // Multiply by 32
+ return c << 5;
+ }
+
+ /**
+ * Returns the number of bits set to {@code true} in this {@code IndexSet} using a
+ * compression of 64 to 1. This counts as enabled all bits of each consecutive
+ * 64 bits if any of the consecutive 64 bits are set to {@code true}.
+ * This method can be used to assess the saturation of the indices in the range.
+ *
+ * @return the number of bits set to {@code true} in this {@code IndexSet} using a compression
+ * of 64 to 1
+ */
+ public int cardinality64() {
+ int c = 0;
+ for (final long x : data) {
+ // Are any bits set?
+ c += x != 0L ? 1 : 0;
+ }
+ // Multiply by 64
+ return c << 6;
+ }
+
+ // Adapt method API from BitSet
+
+ /**
+ * Returns the number of bits set to {@code true} in this {@code IndexSet}.
+ *
+ * @return the number of bits set to {@code true} in this {@code IndexSet}
+ */
+ public int cardinality() {
+ int c = 0;
+ for (final long x : data) {
+ c += Long.bitCount(x);
+ }
+ return c;
+ }
+
+ /**
+ * Returns the value of the bit with the specified index.
+ *
+ * @param bitIndex the bit index (assumed to be positive)
+ * @return the value of the bit with the specified index
+ */
+ public boolean get(int bitIndex) {
+ // WARNING: No range checks !!!
+ final int index = bitIndex - left;
+ final int i = getLongIndex(index);
+ final long m = getLongBit(index);
+ return (data[i] & m) != 0;
+ }
+
+ /**
+ * Sets the bit at the specified index to {@code true}.
+ *
+ * Warning: This has no range checks. Use {@link #add(int)} to add an index that
+ * may be outside the support.
+ *
+ * @param bitIndex the bit index (assumed to be positive)
+ */
+ public void set(int bitIndex) {
+ // WARNING: No range checks !!!
+ final int index = bitIndex - left;
+ final int i = getLongIndex(index);
+ final long m = getLongBit(index);
+ data[i] |= m;
+ }
+
+ /**
+ * Sets the bits from the specified {@code leftIndex} (inclusive) to the specified
+ * {@code rightIndex} (inclusive) to {@code true}.
+ *
+ * If {@code rightIndex - leftIndex < 0} the behavior is not defined. Note: In contrast to the BitSet API, this uses an inclusive end as this
+ * is the main use case for the class.
+ *
+ * Warning: This has no range checks. Use {@link #add(int, int)} to range that
+ * may be outside the support.
+ *
+ * @param leftIndex the left index
+ * @param rightIndex the right index
+ */
+ public void set(int leftIndex, int rightIndex) {
+ final int l = leftIndex - left;
+ final int r = rightIndex - left;
+
+ // WARNING: No range checks !!!
+ int i = getLongIndex(l);
+ final int j = getLongIndex(r);
+
+ // Fill in bits using (big-endian mask):
+ // end middle start
+ // 00011111 11111111 11111100
+
+ // start = -1L << (left % 64)
+ // end = -1L >>> (64 - ((right+1) % 64))
+ final long start = LONG_MASK << l;
+ final long end = LONG_MASK >>> -(r + 1);
+ if (i == j) {
+ // Special case where the two masks overlap at the same long index
+ // 11111100 & 00011111 => 00011100
+ data[i] |= start & end;
+ } else {
+ // 11111100
+ data[i] |= start;
+ while (++i < j) {
+ // 11111111
+ // Note: -1L is all bits set
+ data[i] = -1L;
+ }
+ // 00011111
+ data[j] |= end;
+ }
+ }
+
+ /**
+ * Returns the index of the nearest bit that is set to {@code true} that occurs on or
+ * before the specified starting index. If no such bit exists, then {@code -1} is returned.
+ *
+ * @param fromIndex Index to start checking from (inclusive).
+ * @return the index of the previous set bit, or {@code -1} if there is no such bit
+ */
+ public int previousSetBit(int fromIndex) {
+ return previousSetBitOrElse(fromIndex, -1);
+ }
+
+ /**
+ * Returns the index of the nearest bit that is set to {@code true} that occurs on or
+ * before the specified starting index. If no such bit exists, then
+ * {@code defaultValue} is returned.
+ *
+ * @param fromIndex Index to start checking from (inclusive).
+ * @param defaultValue Default value.
+ * @return the index of the previous set bit, or {@code defaultValue} if there is no such bit
+ */
+ int previousSetBitOrElse(int fromIndex, int defaultValue) {
+ if (fromIndex < left) {
+ // index is in an unknown range
+ return defaultValue;
+ }
+ final int index = fromIndex - left;
+ int i = getLongIndex(index);
+
+ long bits = data[i];
+
+ // Repeat logic of get(int) to check the bit
+ if ((bits & getLongBit(index)) != 0) {
+ return fromIndex;
+ }
+
+ // Mask bits before the bit index
+ // mask = 00011111 = -1L >>> (64 - ((index + 1) % 64))
+ bits &= LONG_MASK >>> -(index + 1);
+ for (;;) {
+ if (bits != 0) {
+ //(i+1) i
+ // | index |
+ // | | |
+ // 0 001010000
+ return (i + 1) * Long.SIZE - Long.numberOfLeadingZeros(bits) - 1 + left;
+ }
+ if (i == 0) {
+ return defaultValue;
+ }
+ bits = data[--i];
+ }
+ }
+
+ /**
+ * Returns the index of the first bit that is set to {@code true} that occurs on or
+ * after the specified starting index. If no such bit exists then {@code -1} is
+ * returned.
+ *
+ * @param fromIndex Index to start checking from (inclusive).
+ * @return the index of the next set bit, or {@code -1} if there is no such bit
+ */
+ public int nextSetBit(int fromIndex) {
+ return nextSetBitOrElse(fromIndex, -1);
+ }
+
+ /**
+ * Returns the index of the first bit that is set to {@code true} that occurs on or
+ * after the specified starting index. If no such bit exists then {@code defaultValue} is
+ * returned.
+ *
+ * @param fromIndex Index to start checking from (inclusive).
+ * @param defaultValue Default value.
+ * @return the index of the next set bit, or {@code defaultValue} if there is no such bit
+ */
+ int nextSetBitOrElse(int fromIndex, int defaultValue) {
+ // Support searching forward through the known range
+ final int index = fromIndex < left ? 0 : fromIndex - left;
+
+ int i = getLongIndex(index);
+
+ // Mask bits after the bit index
+ // mask = 11111000 = -1L << (index % 64)
+ long bits = data[i] & (LONG_MASK << index);
+ for (;;) {
+ if (bits != 0) {
+ //(i+1) i
+ // | index |
+ // | | |
+ // 0 001010000
+ return i * Long.SIZE + Long.numberOfTrailingZeros(bits) + left;
+ }
+ if (++i == data.length) {
+ return defaultValue;
+ }
+ bits = data[i];
+ }
+ }
+
+ /**
+ * Returns the index of the first bit that is set to {@code false} that occurs on or
+ * after the specified starting index within the supported range. If no such
+ * bit exists then the {@code capacity} is returned where {@code capacity = index + 1}
+ * with {@code index} the largest index that can be added to the set without an error.
+ *
+ * If the starting index is less than the supported range the result is {@code fromIndex}.
+ *
+ * @param fromIndex Index to start checking from (inclusive).
+ * @return the index of the next unset bit, or the {@code capacity} if there is no such bit
+ */
+ public int nextClearBit(int fromIndex) {
+ if (fromIndex < left) {
+ return fromIndex;
+ }
+ // Support searching forward through the known range
+ final int index = fromIndex - left;
+
+ int i = getLongIndex(index);
+
+ // Note: This method is conceptually the same as nextSetBit with the exception
+ // that: all the data is bit-flipped; the capacity is returned when the
+ // scan reaches the end.
+
+ // Mask bits after the bit index
+ // mask = 11111000 = -1L << (fromIndex % 64)
+ long bits = ~data[i] & (LONG_MASK << index);
+ for (;;) {
+ if (bits != 0) {
+ return i * Long.SIZE + Long.numberOfTrailingZeros(bits) + left;
+ }
+ if (++i == data.length) {
+ // Capacity
+ return data.length * Long.SIZE + left;
+ }
+ bits = ~data[i];
+ }
+ }
+
+ /**
+ * Returns the index of the first bit that is set to {@code false} that occurs on or
+ * before the specified starting index within the supported range. If no such
+ * bit exists then {@code -1} is returned.
+ *
+ * If the starting index is less than the supported range the result is {@code fromIndex}.
+ * This can return {@code -1} only if the support begins at {@code index == 0}.
+ *
+ * @param fromIndex Index to start checking from (inclusive).
+ * @return the index of the previous unset bit, or {@code -1} if there is no such bit
+ */
+ public int previousClearBit(int fromIndex) {
+ if (fromIndex < left) {
+ // index is in an unknown range
+ return fromIndex;
+ }
+ final int index = fromIndex - left;
+ int i = getLongIndex(index);
+
+ // Note: This method is conceptually the same as previousSetBit with the exception
+ // that: all the data is bit-flipped; the offset - 1 is returned when the
+ // scan reaches the end.
+
+ // Mask bits before the bit index
+ // mask = 00011111 = -1L >>> (64 - ((index + 1) % 64))
+ long bits = ~data[i] & (LONG_MASK >>> -(index + 1));
+ for (;;) {
+ if (bits != 0) {
+ //(i+1) i
+ // | index |
+ // | | |
+ // 0 001010000
+ return (i + 1) * Long.SIZE - Long.numberOfLeadingZeros(bits) - 1 + left;
+ }
+ if (i == 0) {
+ return left - 1;
+ }
+ bits = ~data[--i];
+ }
+ }
+
+ /**
+ * Perform the {@code action} for each index in the set.
+ *
+ * @param action Action.
+ */
+ public void forEach(IntConsumer action) {
+ // Adapted from o.a.c.collections4.IndexProducer
+ int wordIdx = left;
+ for (int i = 0; i < data.length; i++) {
+ long bits = data[i];
+ int index = wordIdx;
+ while (bits != 0) {
+ if ((bits & 1L) == 1L) {
+ action.accept(index);
+ }
+ bits >>>= 1;
+ index++;
+ }
+ wordIdx += Long.SIZE;
+ }
+ }
+
+ /**
+ * Write each index in the set into the provided array.
+ * Returns the number of indices.
+ *
+ * The caller must ensure the output array has sufficient capacity.
+ * For example the array used to construct the IndexSet.
+ *
+ * @param a Output array.
+ * @return count of indices
+ * @see #of(int[])
+ * @see #of(int[], int)
+ */
+ public int toArray(int[] a) {
+ // This benchmarks as faster for index sorting than toArray2 even at
+ // high density (average separation of 2).
+ int n = -1;
+ int offset = left;
+ for (long bits : data) {
+ while (bits != 0) {
+ final int index = Long.numberOfTrailingZeros(bits);
+ a[++n] = index + offset;
+ bits &= ~(1L << index);
+ }
+ offset += Long.SIZE;
+ }
+ return n + 1;
+ }
+
+ /**
+ * Write each index in the set into the provided array.
+ * Returns the number of indices.
+ *
+ * The caller must ensure the output array has sufficient capacity.
+ * For example the array used to construct the IndexSet.
+ *
+ * @param a Output array.
+ * @return count of indices
+ * @see #of(int[])
+ * @see #of(int[], int)
+ */
+ public int toArray2(int[] a) {
+ // Adapted from o.a.c.collections4.IndexProducer
+ int n = -1;
+ for (int i = 0, offset = left; i < data.length; i++, offset += Long.SIZE) {
+ long bits = data[i];
+ int index = offset;
+ while (bits != 0) {
+ if ((bits & 1L) == 1L) {
+ a[++n] = index;
+ }
+ bits >>>= 1;
+ index++;
+ }
+ }
+ return n + 1;
+ }
+
+ // PivotCache interface
+
+ @Override
+ public int left() {
+ return left;
+ }
+
+ @Override
+ public int right() {
+ return right;
+ }
+
+ @Override
+ public boolean sparse() {
+ // Can store all pivots between [left, right]
+ return false;
+ }
+
+ @Override
+ public boolean contains(int k) {
+ // Assume [left <= k <= right]
+ return get(k);
+ }
+
+ @Override
+ public void add(int index) {
+ // Update the floating pivots if outside the support
+ if (index < left) {
+ lowerPivot = Math.max(index, lowerPivot);
+ } else if (index > right) {
+ upperPivot = Math.min(index, upperPivot);
+ } else {
+ set(index);
+ }
+ }
+
+ @Override
+ public void add(int fromIndex, int toIndex) {
+ // Optimisation for the main use case of the PivotCache
+ if (fromIndex == toIndex) {
+ add(fromIndex);
+ return;
+ }
+
+ // Note:
+ // Storing all pivots allows regions of identical values
+ // and sorted regions to be skipped in subsequent partitioning.
+ // Repeat sorting these regions is typically more expensive
+ // than caching them and moving over them during partitioning.
+ // An alternative is to: store fromIndex and only store
+ // toIndex if they are well separated, optionally storing
+ // regions between. If they are not well separated (e.g. < 10)
+ // then using a single pivot is an alternative to investigate
+ // with performance benchmarks on a range of input data.
+
+ // Pivots are required to bracket [L, R]:
+ // LP-----L--------------R------UP
+ // If the range [i, j] overlaps either L or R then
+ // the floating pivots are no longer required:
+ // i-j Set lower pivot
+ // i--------j Ignore lower pivot
+ // i---------------------j Ignore lower & upper pivots (no longer required)
+ // i-------j Ignore lower & upper pivots
+ // i---------------j Ignore upper pivot
+ // i-j Set upper pivot
+ if (fromIndex <= right && toIndex >= left) {
+ // Clip the range between [left, right]
+ final int i = Math.max(fromIndex, left);
+ final int j = Math.min(toIndex, right);
+ set(i, j);
+ } else if (toIndex < left) {
+ lowerPivot = Math.max(toIndex, lowerPivot);
+ } else {
+ // fromIndex > right
+ upperPivot = Math.min(fromIndex, upperPivot);
+ }
+ }
+
+ @Override
+ public int previousPivot(int k) {
+ // Assume scanning in [left <= k <= right]
+ return previousSetBitOrElse(k, lowerPivot);
+ }
+
+ @Override
+ public int nextPivotOrElse(int k, int other) {
+ // Assume scanning in [left <= k <= right]
+ final int p = upperPivot == UPPER_DEFAULT ? other : upperPivot;
+ return nextSetBitOrElse(k, p);
+ }
+
+ // IndexInterval
+
+ @Override
+ public int previousIndex(int k) {
+ // Re-implement previousSetBitOrElse without index checks
+ // as this supports left <= k <= right
+
+ final int index = k - left;
+ int i = getLongIndex(index);
+
+ // Mask bits before the bit index
+ // mask = 00011111 = -1L >>> (64 - ((index + 1) % 64))
+ long bits = data[i] & (LONG_MASK >>> -(index + 1));
+ for (;;) {
+ if (bits != 0) {
+ //(i+1) i
+ // | index |
+ // | | |
+ // 0 001010000
+ return (i + 1) * Long.SIZE - Long.numberOfLeadingZeros(bits) - 1 + left;
+ }
+ // Unsupported: the interval should contain k
+ //if (i == 0) {
+ // return left - 1;
+ //}
+ bits = data[--i];
+ }
+ }
+
+ @Override
+ public int nextIndex(int k) {
+ // Re-implement nextSetBitOrElse without index checks
+ // as this supports left <= k <= right
+
+ final int index = k - left;
+ int i = getLongIndex(index);
+
+ // Mask bits after the bit index
+ // mask = 11111000 = -1L << (index % 64)
+ long bits = data[i] & (LONG_MASK << index);
+ for (;;) {
+ if (bits != 0) {
+ //(i+1) i
+ // | index |
+ // | | |
+ // 0 001010000
+ return i * Long.SIZE + Long.numberOfTrailingZeros(bits) + left;
+ }
+ // Unsupported: the interval should contain k
+ //if (++i == data.length) {
+ // return right + 1;
+ //}
+ bits = data[++i];
+ }
+ }
+
+ // No override for split.
+ // This requires searching for previousIndex(k - 1) and nextIndex(k + 1).
+ // The only shared code is getLongIndex(x - left). Since argument indices are 2 apart
+ // these will map to a different long with a probability of 1/32.
+
+ // IndexInterval2
+ // This is exactly the same as IndexInterval as the pointers i are the same as the keys k
+
+ @Override
+ public int start() {
+ return left();
+ }
+
+ @Override
+ public int end() {
+ return right();
+ }
+
+ @Override
+ public int index(int i) {
+ return i;
+ }
+
+ @Override
+ public int previous(int i, int k) {
+ return previousIndex(k);
+ }
+
+ @Override
+ public int next(int i, int k) {
+ return nextIndex(k);
+ }
+
+ /**
+ * Return a {@link ScanningPivotCache} implementation re-using the same internal storage.
+ *
+ * Note that the range for the {@link ScanningPivotCache} must fit inside the current
+ * supported range of indices.
+ *
+ * Warning: This operation clears all set bits within the range.
+ *
+ * Support
+ *
+ * The returned {@link ScanningPivotCache} is suitable for storing all pivot points between
+ * {@code [left, right]} and the closest bounding pivots outside that range. It can be
+ * used for bracketing partition keys processed in a random order by storing pivots
+ * found during each successive partition search.
+ *
+ * The returned {@link ScanningPivotCache} is suitable for use when iterating over
+ * partition keys in ascending order.
+ *
+ * The cache allows incrementing the {@code left} support using
+ * {@link ScanningPivotCache#moveLeft(int)}. Any calls to decrement the {@code left} support
+ * at any time will result in an {@link UnsupportedOperationException}; this prevents reseting
+ * to within the original support used to create the cache. If the {@code left} is
+ * moved beyond the {@code right} then the move is rejected.
+ *
+ * @param lower Lower bound (inclusive).
+ * @param upper Upper bound (inclusive).
+ * @return the pivot cache
+ * @throws IllegalArgumentException if {@code right < left} or the range cannot be
+ * supported.
+ */
+ ScanningPivotCache asScanningPivotCache(int lower, int upper) {
+ return asScanningPivotCache(lower, upper, true);
+ }
+
+ /**
+ * Return a {@link ScanningPivotCache} implementation to support the range
+ * {@code [left, right]}.
+ *
+ * See {@link #asScanningPivotCache(int, int)} for the details of the cache implementation.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @return the pivot cache
+ * @throws IllegalArgumentException if {@code right < left}
+ */
+ static ScanningPivotCache createScanningPivotCache(int left, int right) {
+ final IndexSet set = ofRange(left, right);
+ return set.asScanningPivotCache(left, right, false);
+ }
+
+ /**
+ * Return a {@link ScanningPivotCache} implementation to support the range
+ * {@code [left, right]} re-using the same internal storage.
+ *
+ * @param lower Lower bound (inclusive).
+ * @param upper Upper bound (inclusive).
+ * @param initialize Perform validation checks and initialize the storage.
+ * @return the pivot cache
+ * @throws IllegalArgumentException if {@code right < left} or the range cannot be
+ * supported.
+ */
+ private ScanningPivotCache asScanningPivotCache(int lower, int upper, boolean initialize) {
+ if (initialize) {
+ checkRange(lower, upper);
+ final int capacity = data.length * Long.SIZE + lower;
+ if (lower < left || upper >= capacity) {
+ throw new IllegalArgumentException(
+ String.format("Unsupported range: [%d, %d] is not within [%d, %d]", lower, upper,
+ left, capacity - 1));
+ }
+ // Clear existing data
+ Arrays.fill(data, 0);
+ }
+ return new IndexPivotCache(lower, upper);
+ }
+
+ /**
+ * Return an {@link UpdatingInterval} implementation to support the range
+ * {@code [left, right]} re-using the same internal storage.
+ *
+ * @return the interval
+ */
+ UpdatingInterval interval() {
+ return new IndexSetUpdatingInterval(left, right);
+ }
+
+ /**
+ * Check the range is valid.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @throws IllegalArgumentException if {@code right < left}
+ */
+ private static void checkRange(int left, int right) {
+ if (right < left) {
+ throw new IllegalArgumentException(
+ String.format("Invalid range: [%d, %d]", left, right));
+ }
+ }
+
+ /**
+ * Implementation of the {@link ScanningPivotCache} using the {@link IndexSet}.
+ *
+ * Stores all pivots between the support {@code [left, right]}. Uses two
+ * floating pivots which are the closest known pivots surrounding this range.
+ *
+ * This class is bound to the enclosing {@link IndexSet} instance to provide
+ * the functionality to read, write and search indexes.
+ *
+ * Note: This duplicates functionality of the parent IndexSet. Differences
+ * are that it uses a movable left bound and implements the scanning functionality
+ * of the {@link ScanningPivotCache} interface. It can also be created for
+ * a smaller {@code [left, right]} range than the enclosing class.
+ *
+ * Creation of this class typically invalidates the use of the outer class.
+ * Creation will zero the underlying storage and the range may be different.
+ */
+ private class IndexPivotCache implements ScanningPivotCache {
+ /** Left bound of the support. */
+ private int left;
+ /** Right bound of the support. */
+ private final int right;
+ /** The upstream pivot closest to the left bound of the support.
+ * Provides a lower search bound for the range [left, right]. */
+ private int lowerPivot = -1;
+ /** The downstream pivot closest to the right bound of the support.
+ * Provides an upper search bound for the range [left, right]. */
+ private int upperPivot = UPPER_DEFAULT;
+
+ /**
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ IndexPivotCache(int left, int right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ @Override
+ public int left() {
+ return left;
+ }
+
+ @Override
+ public int right() {
+ return right;
+ }
+
+ @Override
+ public boolean sparse() {
+ // Can store all pivots between [left, right]
+ return false;
+ }
+
+ @Override
+ public boolean moveLeft(int newLeft) {
+ if (newLeft > right) {
+ // Signal that this cache can no longer be used in that range
+ return false;
+ }
+ if (newLeft < left) {
+ throw new UnsupportedOperationException(
+ String.format("New left is outside current support: %d < %d", newLeft, left));
+ }
+ // Here [left <= newLeft <= right]
+ // Move the upstream pivot
+ lowerPivot = previousPivot(newLeft);
+ left = newLeft;
+ return true;
+ }
+
+ @Override
+ public boolean contains(int k) {
+ // Assume [left <= k <= right]
+ return IndexSet.this.get(k);
+ }
+
+ @Override
+ public int previousPivot(int k) {
+ // Assume scanning in [left <= k <= right]
+ // Here left is moveable and lower pivot holds the last pivot below it.
+ // The cache will not store any bits below left so if it has moved
+ // searching may find stale bits below the current lower pivot.
+ // So we return the max of the found bit or the lower pivot.
+ if (k < left) {
+ return lowerPivot;
+ }
+ return Math.max(lowerPivot, IndexSet.this.previousSetBitOrElse(k, lowerPivot));
+ }
+
+ @Override
+ public int nextPivotOrElse(int k, int other) {
+ // Assume scanning in [left <= k <= right]
+ final int p = upperPivot == UPPER_DEFAULT ? other : upperPivot;
+ return IndexSet.this.nextSetBitOrElse(k, p);
+ }
+
+ @Override
+ public int nextNonPivot(int k) {
+ // Assume scanning in [left <= k <= right]
+ return IndexSet.this.nextClearBit(k);
+ }
+
+ @Override
+ public int previousNonPivot(int k) {
+ // Assume scanning in [left <= k <= right]
+ return IndexSet.this.previousClearBit(k);
+ }
+
+ @Override
+ public void add(int index) {
+ // Update the floating pivots if outside the support
+ if (index < left) {
+ lowerPivot = Math.max(index, lowerPivot);
+ } else if (index > right) {
+ upperPivot = Math.min(index, upperPivot);
+ } else {
+ IndexSet.this.set(index);
+ }
+ }
+
+ @Override
+ public void add(int fromIndex, int toIndex) {
+ if (fromIndex == toIndex) {
+ add(fromIndex);
+ return;
+ }
+ // Note:
+ // Storing all pivots allows regions of identical values
+ // and sorted regions to be skipped in subsequent partitioning.
+ // Repeat sorting these regions is typically more expensive
+ // than caching them and moving over them during partitioning.
+ // An alternative is to: store fromIndex and only store
+ // toIndex if they are well separated, optionally storing
+ // regions between. If they are not well separated (e.g. < 10)
+ // then using a single pivot is an alternative to investigate
+ // with performance benchmarks on a range of input data.
+
+ // Pivots are required to bracket [L, R]:
+ // LP-----L--------------R------UP
+ // If the range [i, j] overlaps either L or R then
+ // the floating pivots are no longer required:
+ // i-j Set lower pivot
+ // i--------j Ignore lower pivot
+ // i---------------------j Ignore lower & upper pivots (no longer required)
+ // i-------j Ignore lower & upper pivots
+ // i---------------j Ignore upper pivot
+ // i-j Set upper pivot
+ if (fromIndex <= right && toIndex >= left) {
+ // Clip the range between [left, right]
+ final int i = Math.max(fromIndex, left);
+ final int j = Math.min(toIndex, right);
+ IndexSet.this.set(i, j);
+ } else if (toIndex < left) {
+ lowerPivot = Math.max(toIndex, lowerPivot);
+ } else {
+ // fromIndex > right
+ upperPivot = Math.min(fromIndex, upperPivot);
+ }
+ }
+ }
+
+ /**
+ * Implementation of the {@link UpdatingInterval} using the {@link IndexSet}.
+ *
+ * This class is bound to the enclosing {@link IndexSet} instance to provide
+ * the functionality to search indexes.
+ */
+ private class IndexSetUpdatingInterval implements UpdatingInterval {
+ /** Left bound of the interval. */
+ private int left;
+ /** Right bound of the interval. */
+ private int right;
+
+ /**
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ IndexSetUpdatingInterval(int left, int right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ @Override
+ public int left() {
+ return left;
+ }
+
+ @Override
+ public int right() {
+ return right;
+ }
+
+ @Override
+ public int updateLeft(int k) {
+ // Assume left < k= < right
+ return left = nextIndex(k);
+ }
+
+ @Override
+ public int updateRight(int k) {
+ // Assume left <= k < right
+ return right = previousIndex(k);
+ }
+
+ @Override
+ public UpdatingInterval splitLeft(int ka, int kb) {
+ // Assume left < ka <= kb < right
+ final int lower = left;
+ left = nextIndex(kb + 1);
+ return new IndexSetUpdatingInterval(lower, previousIndex(ka - 1));
+ }
+
+ @Override
+ public UpdatingInterval splitRight(int ka, int kb) {
+ // Assume left < ka <= kb < right
+ final int upper = right;
+ right = previousIndex(ka - 1);
+ return new IndexSetUpdatingInterval(nextIndex(kb + 1), upper);
+ }
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexSortingPerformance.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexSortingPerformance.java
new file mode 100644
index 000000000..c7f9330af
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexSortingPerformance.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+/**
+ * Executes a benchmark of sorting array indices to a unique ascending sequence.
+ */
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@State(Scope.Benchmark)
+@Fork(value = 1, jvmArgs = {"-server", "-Xms512M", "-Xmx4096M"})
+public class IndexSortingPerformance {
+ /** Sort using a modified insertion sort that ignores duplicates. */
+ private static final String INSERTION = "Insertion";
+ /** Sort using a binary search into the unique indices. */
+ private static final String BINARY_SEARCH = "BinarySearch";
+ /** Sort using a modified heap sort that ignores duplicates. */
+ private static final String HEAP = "Heap";
+ /** Sort using a full sort and a second pass to ignore duplicates. */
+ private static final String SORT_UNIQUE = "SortUnique";
+ /** Sort using an {@link IndexSet} to ignore duplicates;
+ * sorted array extracted from the {@link IndexSet} storage. */
+ private static final String INDEX_SET = "IndexSet";
+ /** Sort using an {@link HashIndexSet} to ignore duplicates and full sort the unique values. */
+ private static final String HASH_INDEX_SET = "HashIndexSet";
+ /** Sort using a hybrid method using heuristics to choose the sort. */
+ private static final String HYBRID = "Hybrid";
+
+ /**
+ * Interface to test sorting unique indices.
+ */
+ interface IndexSort {
+ /**
+ * Sort the indices into unique ascending order.
+ *
+ * @param a Indices.
+ * @param n Number of indices.
+ * @return number of unique indices.
+ */
+ int sort(int[] a, int n);
+ }
+
+ /**
+ * Source of {@code int} index array data.
+ */
+ @State(Scope.Benchmark)
+ public static class IndexDataSource {
+ /** Number of indices. */
+ @Param({
+ "10",
+ "100",
+ //"1000"
+ })
+ private int n;
+ /** Range factor (spread of indices). */
+ @Param({
+ //"1",
+ "10",
+ //"100"
+ })
+ private double range;
+ /** Duplication factor. */
+ @Param({
+ //"0",
+ "1",
+ "2"
+ })
+ private double duplication;
+ /** Number of samples. */
+ @Param({"100"})
+ private int samples;
+ /** True if the indices should be sorted into ascending order.
+ * This would be the case if multiple quantiles are requested
+ * using an ascending sequence of p in [0, 1]. */
+ @Param({"false"})
+ private boolean ascending;
+
+
+ /** Data. */
+ private int[][] data;
+
+ /**
+ * @return the data
+ */
+ public int[][] getData() {
+ return data;
+ }
+
+ /**
+ * Create the data.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ // Data will be randomized per iteration
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+
+ // length of data: index in [0, length)
+ final int length = (int) Math.floor(n * range);
+ // extra duplicates
+ final int extra = (int) Math.floor(n * duplication);
+
+ data = new int[samples][];
+ for (int i = 0; i < samples; i++) {
+ final int[] indices = new int[n + extra];
+ // Randomly spread indices in the range (this may create duplicates anyway)
+ for (int j = 0; j < n; j++) {
+ indices[j] = rng.nextInt(length);
+ }
+ // Sample from the indices to create duplicates.
+ for (int j = 0; j < extra; j++) {
+ indices[j + n] = indices[rng.nextInt(n)];
+ }
+ // Ensure the full range is present. Otherwise it is hard to fairly assess
+ // the performance of the IndexSet when the data is so sparse that
+ // the min/max is far from the edge of the range and it can use less memory.
+ // Pick a random place to put the min.
+ final int i1 = rng.nextInt(indices.length);
+ // Put the max somewhere else.
+ final int i2 = (i1 + rng.nextInt(indices.length - 1)) % indices.length;
+ indices[i1] = 0;
+ indices[i2] = length - 1;
+ if (ascending) {
+ Arrays.sort(indices);
+ }
+ data[i] = indices;
+ }
+ }
+ }
+
+ /**
+ * Source of a {@link IndexSort}.
+ */
+ @State(Scope.Benchmark)
+ public static class IndexSortSource {
+ /** Name of the source. */
+ @Param({
+ // Fast when size is small (<10)
+ INSERTION,
+ // Slow (too many System.arraycopy calls)
+ //BINARY_SEARCH,
+ // Slow ~ n log(n)
+ //HEAP,
+ // Fast sort but does not scale well with duplicates
+ //SORT_UNIQUE,
+ // Scale well with duplicates.
+ // IndexSet has poor high memory requirements when keys are spread out.
+ // HashIndexSet has predictable memory usage.
+ INDEX_SET, HASH_INDEX_SET,
+ // Should pick the best option most of the time
+ HYBRID})
+ private String name;
+
+ /** The sort function. */
+ private IndexSort function;
+
+ /**
+ * @return the function
+ */
+ public IndexSort getFunction() {
+ return function;
+ }
+
+ /**
+ * Create the function.
+ */
+ @Setup
+ public void setup() {
+ // Note: Functions defensively copy the data by default
+ // Note: KeyStratgey does not matter for single / paired keys but
+ // we set it anyway for completeness.
+ Objects.requireNonNull(name);
+ if (INSERTION.equals(name)) {
+ function = Sorting::sortIndicesInsertionSort;
+ } else if (BINARY_SEARCH.equals(name)) {
+ function = Sorting::sortIndicesBinarySearch;
+ } else if (HEAP.equals(name)) {
+ function = Sorting::sortIndicesHeapSort;
+ } else if (SORT_UNIQUE.equals(name)) {
+ function = Sorting::sortIndicesSort;
+ } else if (INDEX_SET.equals(name)) {
+ function = Sorting::sortIndicesIndexSet;
+ } else if ((INDEX_SET + "2").equals(name)) {
+ function = Sorting::sortIndicesIndexSet2;
+ } else if (HASH_INDEX_SET.equals(name)) {
+ function = Sorting::sortIndicesHashIndexSet;
+ } else if (HYBRID.equals(name)) {
+ function = Sorting::sortIndices;
+ } else {
+ throw new IllegalStateException("Unknown sort function: " + name);
+ }
+ }
+ }
+
+ /**
+ * Sort the unique indices.
+ *
+ * @param function Source of the function.
+ * @param source Source of the data.
+ * @param bh Data sink.
+ */
+ @Benchmark
+ public void indexSort(IndexSortSource function, IndexDataSource source, Blackhole bh) {
+ for (final int[] a : source.getData()) {
+ bh.consume(function.getFunction().sort(a.clone(), a.length));
+ }
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IntervalAnalysis.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IntervalAnalysis.java
new file mode 100644
index 000000000..393fbbc1e
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IntervalAnalysis.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * An interval that provides analysis of indices within the range.
+ *
+ * @since 1.2
+ */
+interface IntervalAnalysis {
+ /**
+ * Test if the interval is saturated at the specified {@code separation}. The
+ * separation distance is provided as a power of 2.
+ *
+ * A saturated interval will have all neighbouring indices separated
+ * approximately within the maximum separation distance.
+ *
+ * Implementations may:
+ * Note: The search may use a cache of pivots. A pivot is a Kth index that
+ * corresponds to a value correctly positioned within the equivalent fully sorted data array.
+ * Each step of the algorithm partitions the array between a lower and upper bound into
+ * values above or below a value chosen from the interval. The index of this value after the
+ * partition is stored as a pivot. A subsequent search into the array can use the pivots to
+ * quickly bracket the interval for the next target index.
+ *
+ * The maximum supported cache size is 2^30 - 1. This ensures that any valid index i
+ * into the cache can be increased for the next level using 2 * i + 2 without overflow.
+ *
+ * The method for choosing the value within the interval to pivot around is specified by
+ * the {@link PivotingStrategy}. Ideally this should provide a guess of the middle (median)
+ * of the interval. The value must be within the interval, thus using for example the
+ * mean of the end values is not an option. The default uses a guess for the median from
+ * 3 values that are likely to be representative of the range of values.
+ *
+ * This implementation provides the option to return the (K+1) ordered element along with
+ * the Kth. This uses the position of K and the most recent upper bound on the
+ * bracket known to contain values greater than the Kth. This prevents using a
+ * second search into the array for the (K+1) element.
+ *
+ * @since 1.2
+ */
+class KthSelector {
+ /** Empty pivots array. */
+ static final int[] NO_PIVOTS = {};
+ /** Minimum selection size for insertion sort rather than selection.
+ * Dual-pivot quicksort used 27 in the original paper. */
+ private static final int MIN_SELECT_SIZE = 17;
+
+ /** A {@link PivotingStrategy} used for pivoting. */
+ private final PivotingStrategy pivotingStrategy;
+
+ /** Minimum selection size for insertion sort rather than selection. */
+ private final int minSelectSize;
+
+ /**
+ * Constructor with default {@link PivotingStrategy#MEDIAN_OF_3 median of 3} pivoting
+ * strategy.
+ */
+ KthSelector() {
+ this(PivotingStrategy.MEDIAN_OF_3);
+ }
+
+ /**
+ * Constructor with specified pivoting strategy.
+ *
+ * @param pivotingStrategy Pivoting strategy to use.
+ */
+ KthSelector(PivotingStrategy pivotingStrategy) {
+ this(pivotingStrategy, MIN_SELECT_SIZE);
+ }
+
+ /**
+ * Constructor with specified pivoting strategy and select size.
+ *
+ * @param pivotingStrategy Pivoting strategy to use.
+ * @param minSelectSize Minimum selection size for insertion sort rather than selection.
+ */
+ KthSelector(PivotingStrategy pivotingStrategy, int minSelectSize) {
+ this.pivotingStrategy = pivotingStrategy;
+ this.minSelectSize = minSelectSize;
+ }
+
+ /**
+ * Select Kth value in the array. Optionally select the next value after K.
+ *
+ * Note: If K+1 is requested this method assumes it is a valid index into the array.
+ *
+ * Uses a single-pivot partition method.
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param k Index whose value in the array is of interest.
+ * @param kp1 K+1th value (if not null)
+ * @return Kth value
+ */
+ double selectSP(double[] data, int k, double[] kp1) {
+ int begin = 0;
+ int end = data.length;
+ while (end - begin > minSelectSize) {
+ // Select a pivot and partition data array around it
+ final int pivot = partitionSP(data, begin, end,
+ pivotingStrategy.pivotIndex(data, begin, end - 1, k));
+ if (k == pivot) {
+ // The pivot was exactly the element we wanted
+ return finalSelection(data, k, kp1, end);
+ } else if (k < pivot) {
+ // The element is in the left partition
+ end = pivot;
+ } else {
+ // The element is in the right partition
+ begin = pivot + 1;
+ }
+ }
+ sortRange(data, begin, end);
+ if (kp1 != null) {
+ // Either end == data.length and k+1 is sorted; or
+ // end == pivot where data[k] <= data[pivot] <= data[pivot+j] for all j
+ kp1[0] = data[k + 1];
+ }
+ return data[k];
+ }
+
+ /**
+ * Select Kth value in the array. Optionally select the next value after K.
+ *
+ * Note: If K+1 is requested this method assumes it is a valid index into the array.
+ *
+ * Uses a single-pivot partition method with special handling of NaN and signed zeros.
+ * Correction of signed zeros requires a sweep across the entire range.
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param k Index whose value in the array is of interest.
+ * @param kp1 K+1th value (if not null)
+ * @return Kth value
+ */
+ double selectSPN(double[] data, int k, double[] kp1) {
+ // Handle NaN
+ final int length = sortNaN(data);
+ if (k >= length) {
+ if (kp1 != null) {
+ kp1[0] = Double.NaN;
+ }
+ return Double.NaN;
+ }
+
+ int begin = 0;
+ int end = length;
+ while (end - begin > minSelectSize) {
+ // Select a pivot and partition data array around it
+ final int pivot = partitionSPN(data, begin, end,
+ pivotingStrategy.pivotIndex(data, begin, end - 1, k));
+ if (k == pivot) {
+ // The pivot was exactly the element we wanted
+ if (data[k] == 0) {
+ orderSignedZeros(data, 0, length);
+ }
+ return finalSelection(data, k, kp1, end);
+ } else if (k < pivot) {
+ // The element is in the left partition
+ end = pivot;
+ } else {
+ // The element is in the right partition
+ begin = pivot + 1;
+ }
+ }
+ insertionSort(data, begin, end, begin != 0);
+ if (data[k] == 0) {
+ orderSignedZeros(data, 0, length);
+ }
+ if (kp1 != null) {
+ // Either end == data.length and k+1 is sorted; or
+ // end == pivot where data[k] <= data[pivot] <= data[pivot+j] for all j
+ kp1[0] = data[k + 1];
+ }
+ return data[k];
+ }
+
+ /**
+ * Select Kth value in the array. Optionally select the next value after K.
+ *
+ * Note: If K+1 is requested this method assumes it is a valid index into the array.
+ *
+ * Uses a single-pivot partition method with a heap cache.
+ *
+ * This method can be used for repeat calls to identify Kth values in
+ * the same array by caching locations of pivots. Maximum supported heap size is 2^30 - 1.
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param pivotsHeap Cached pivots heap that can be used for efficient estimation.
+ * @param k Index whose value in the array is of interest.
+ * @param kp1 K+1th value (if not null)
+ * @return Kth value
+ */
+ double selectSPH(double[] data, int[] pivotsHeap, int k, double[] kp1) {
+ final int heapLength = pivotsHeap.length;
+ if (heapLength == 0) {
+ // No pivots
+ return selectSP(data, k, kp1);
+ }
+ int begin = 0;
+ int end = data.length;
+ int node = 0;
+ while (end - begin > minSelectSize) {
+ int pivot;
+
+ if (node < heapLength && pivotsHeap[node] >= 0) {
+ // The pivot has already been found in a previous call
+ // and the array has already been partitioned around it
+ pivot = pivotsHeap[node];
+ } else {
+ // Select a pivot and partition data array around it
+ pivot = partitionSP(data, begin, end,
+ pivotingStrategy.pivotIndex(data, begin, end - 1, k));
+ if (node < heapLength) {
+ pivotsHeap[node] = pivot;
+ }
+ }
+
+ if (k == pivot) {
+ // The pivot was exactly the element we wanted
+ return finalSelection(data, k, kp1, end);
+ } else if (k < pivot) {
+ // The element is in the left partition
+ end = pivot;
+ if (node < heapLength) {
+ node = Math.min((node << 1) + 1, heapLength);
+ }
+ } else {
+ // The element is in the right partition
+ begin = pivot + 1;
+ if (node < heapLength) {
+ node = Math.min((node << 1) + 2, heapLength);
+ }
+ }
+ }
+ sortRange(data, begin, end);
+ if (kp1 != null) {
+ // Either end == data.length and k+1 is sorted; or
+ // end == pivot where data[k] <= data[pivot] <= data[pivot+j] for all j
+ kp1[0] = data[k + 1];
+ }
+ return data[k];
+ }
+
+ /**
+ * Select Kth value in the array. Optionally select the next value after K.
+ *
+ * Note: If K+1 is requested this method assumes it is a valid index into the array
+ * (i.e. K is not the last index in the array).
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param k Index whose value in the array is of interest.
+ * @param kp1 K+1th value (if not null)
+ * @param end Upper bound (exclusive) of the interval containing K.
+ * This should be either a pivot point {@code data[k] <= data[end]} or the length
+ * of the data array.
+ * @return Kth value
+ */
+ private static double finalSelection(double[] data, int k, double[] kp1, int end) {
+ if (kp1 != null) {
+ // After partitioning all elements above k are greater than or equal to k.
+ // Find the minimum of the elements above.
+ // Set the k+1 limit as either a pivot or the end of the data.
+ final int limit = Math.min(end, data.length - 1);
+ double min = data[k + 1];
+ for (int i = k + 2; i <= limit; i++) {
+ if (DoubleMath.lessThan(data[i], min)) {
+ min = data[i];
+ }
+ }
+ kp1[0] = min;
+ }
+ return data[k];
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * Uses a single-pivot partition method.
+ *
+ * @param data Values.
+ * @param k Indices.
+ */
+ void partitionSP(double[] data, int... k) {
+ final int n = k.length;
+ if (n <= 1) {
+ if (n == 1) {
+ selectSP(data, k[0], null);
+ }
+ return;
+ }
+ // Multiple pivots
+ final int length = data.length;
+ final BitSet pivots = new BitSet(length);
+
+ for (int i = 0; i < n; i++) {
+ final int kk = k[i];
+ if (pivots.get(kk)) {
+ // Already sorted
+ continue;
+ }
+ int begin;
+ int end;
+ if (i == 0) {
+ begin = 0;
+ end = length;
+ } else {
+ // Start inclusive
+ begin = pivots.previousSetBit(kk) + 1;
+ end = pivots.nextSetBit(kk + 1);
+ if (end < 0) {
+ end = length;
+ }
+ }
+ partitionSP(data, begin, end, pivots, kk);
+ }
+ }
+
+ /**
+ * Partition around the Kth value in the array.
+ *
+ * This method can be used for repeat calls to identify Kth values in
+ * the same array by caching locations of pivots (correctly sorted indices).
+ *
+ * Uses a single-pivot partition method.
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param pivots Cache of pivot points.
+ * @param k Index whose value in the array is of interest.
+ */
+ private void partitionSP(double[] data, int begin, int end, BitSet pivots, int k) {
+ // Find the unsorted range containing k
+ while (end - begin > minSelectSize) {
+ // Select a value and partition data array around it
+ final int pivot = partitionSP(data, begin, end,
+ pivotingStrategy.pivotIndex(data, begin, end - 1, k));
+ pivots.set(pivot);
+ if (k == pivot) {
+ // The pivot was exactly the element we wanted
+ return;
+ } else if (k < pivot) {
+ // The element is in the left partition
+ end = pivot;
+ } else {
+ // The element is in the right partition
+ begin = pivot + 1;
+ }
+ }
+ setPivots(begin, end, pivots);
+ sortRange(data, begin, end);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Uses a single-pivot partition method.
+ *
+ * @param data Data array.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param pivot Initial index of the pivot.
+ * @return index of the pivot after partition
+ */
+ private static int partitionSP(double[] data, int begin, int end, int pivot) {
+ final double value = data[pivot];
+ data[pivot] = data[begin];
+
+ int i = begin + 1;
+ int j = end - 1;
+ while (i < j) {
+ while (i < j && DoubleMath.greaterThan(data[j], value)) {
+ --j;
+ }
+ while (i < j && DoubleMath.lessThan(data[i], value)) {
+ ++i;
+ }
+ if (i < j) {
+ final double tmp = data[i];
+ data[i++] = data[j];
+ data[j--] = tmp;
+ }
+ }
+
+ if (i >= end || DoubleMath.greaterThan(data[i], value)) {
+ --i;
+ }
+ data[begin] = data[i];
+ data[i] = value;
+ return i;
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * Uses a single-pivot partition method.
+ *
+ * @param data Values.
+ * @param k Indices.
+ */
+ void partitionSPN(double[] data, int... k) {
+ final int n = k.length;
+ if (n <= 1) {
+ if (n == 1) {
+ selectSPN(data, k[0], null);
+ }
+ return;
+ }
+ // Multiple pivots
+
+ // Handle NaN
+ final int length = sortNaN(data);
+ if (length < 1) {
+ return;
+ }
+
+ final BitSet pivots = new BitSet(length);
+
+ // Flag any pivots that are zero
+ boolean zeros = false;
+ for (int i = 0; i < n; i++) {
+ final int kk = k[i];
+ if (kk >= length || pivots.get(kk)) {
+ // Already sorted
+ continue;
+ }
+ int begin;
+ int end;
+ if (i == 0) {
+ begin = 0;
+ end = length;
+ } else {
+ // Start inclusive
+ begin = pivots.previousSetBit(kk) + 1;
+ end = pivots.nextSetBit(kk + 1);
+ if (end < 0) {
+ end = length;
+ }
+ }
+ partitionSPN(data, begin, end, pivots, kk);
+ zeros = zeros || data[kk] == 0;
+ }
+
+ // Handle signed zeros
+ if (zeros) {
+ orderSignedZeros(data, 0, length);
+ }
+ }
+
+ /**
+ * Partition around the Kth value in the array.
+ *
+ * This method can be used for repeat calls to identify Kth values in
+ * the same array by caching locations of pivots (correctly sorted indices).
+ *
+ * Note: Requires that the range contains no NaN values. Does not partition
+ * around signed zeros.
+ *
+ * Uses a single-pivot partition method.
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param pivots Cache of pivot points.
+ * @param k Index whose value in the array is of interest.
+ */
+ private void partitionSPN(double[] data, int begin, int end, BitSet pivots, int k) {
+ // Find the unsorted range containing k
+ while (end - begin > minSelectSize) {
+ // Select a value and partition data array around it
+ final int pivot = partitionSPN(data, begin, end,
+ pivotingStrategy.pivotIndex(data, begin, end - 1, k));
+ pivots.set(pivot);
+ if (k == pivot) {
+ // The pivot was exactly the element we wanted
+ return;
+ } else if (k < pivot) {
+ // The element is in the left partition
+ end = pivot;
+ } else {
+ // The element is in the right partition
+ begin = pivot + 1;
+ }
+ }
+ setPivots(begin, end, pivots);
+ sortRange(data, begin, end);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Note: Requires that the range contains no NaN values. Does not partition
+ * around signed zeros.
+ *
+ * Uses a single-pivot partition method.
+ *
+ * @param data Data array.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param pivot Initial index of the pivot.
+ * @return index of the pivot after partition
+ */
+ private static int partitionSPN(double[] data, int begin, int end, int pivot) {
+ final double value = data[pivot];
+ data[pivot] = data[begin];
+
+ int i = begin + 1;
+ int j = end - 1;
+ while (i < j) {
+ while (i < j && data[j] > value) {
+ --j;
+ }
+ while (i < j && data[i] < value) {
+ ++i;
+ }
+ if (i < j) {
+ final double tmp = data[i];
+ data[i++] = data[j];
+ data[j--] = tmp;
+ }
+ }
+
+ if (i >= end || data[i] > value) {
+ --i;
+ }
+ data[begin] = data[i];
+ data[i] = value;
+ return i;
+ }
+
+ /**
+ * Sort an array range.
+ *
+ * @param data Data array.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ */
+ private static void sortRange(double[] data, int begin, int end) {
+ Arrays.sort(data, begin, end);
+ }
+
+ /**
+ * Sorts an array using an insertion sort.
+ *
+ * Note: Requires that the range contains no NaN values. It does not respect the
+ * order of signed zeros.
+ *
+ * This method is fast up to approximately 40 - 80 values.
+ *
+ * The {@code internal} flag indicates that the value at {@code data[begin - 1]}
+ * is sorted.
+ *
+ * @param data Data array.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param internal Internal flag.
+ */
+ private static void insertionSort(double[] data, int begin, int end, boolean internal) {
+ Sorting.sort(data, begin, end - 1, internal);
+ }
+
+ /**
+ * Move NaN values to the end of the array.
+ * This allows all other values to be compared using {@code <, ==, >} operators (with
+ * the exception of signed zeros).
+ *
+ * @param data Values.
+ * @return end of non-NaN values
+ */
+ private static int sortNaN(double[] data) {
+ int end = data.length;
+ // Avoid unnecessary moves
+ while (--end > 0) {
+ if (!Double.isNaN(data[end])) {
+ break;
+ }
+ }
+ end++;
+ for (int i = end; i > 0;) {
+ final double v = data[--i];
+ if (Double.isNaN(v)) {
+ data[i] = data[--end];
+ data[end] = v;
+ }
+ }
+ return end;
+ }
+
+ /**
+ * Detect and fix the sort order of signed zeros. Assumes the data may have been
+ * partially ordered around zero.
+ *
+ * Searches for zeros if {@code data[begin] <= 0} and {@code data[end - 1] >= 0}.
+ * If zeros are discovered in the range then they are assumed to be continuous.
+ *
+ * @param data Values.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ */
+ private static void fixSignedZeros(double[] data, int begin, int end) {
+ int j;
+ if (data[begin] <= 0 && data[end - 1] >= 0) {
+ int i = begin;
+ while (data[i] < 0) {
+ i++;
+ }
+ j = end - 1;
+ while (data[j] > 0) {
+ j--;
+ }
+ sortZero(data, i, j + 1);
+ }
+ }
+
+ /**
+ * Count the number of signed zeros in the range and order them to be correctly
+ * sorted. This checks all values in the range. It does not assume zeros are continuous.
+ *
+ * @param data Values.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ */
+ private static void orderSignedZeros(double[] data, int begin, int end) {
+ int c = countSignedZeros(data, begin, end);
+ if (c != 0) {
+ int i = begin - 1;
+ while (++i < end) {
+ if (data[i] == 0) {
+ data[i] = -0.0;
+ if (--c == 0) {
+ break;
+ }
+ }
+ }
+ while (++i < end) {
+ if (data[i] == 0) {
+ data[i] = 0.0;
+ }
+ }
+ }
+ }
+
+ /**
+ * Count the number of signed zeros (-0.0).
+ *
+ * @param data Values.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @return the count
+ */
+ static int countSignedZeros(double[] data, int begin, int end) {
+ // Count negative zeros
+ int c = 0;
+ for (int i = begin; i < end; i++) {
+ if (data[i] == 0 && Double.doubleToRawLongBits(data[i]) < 0) {
+ c++;
+ }
+ }
+ return c;
+ }
+
+ /**
+ * Sort a range of all zero values.
+ * This orders -0.0 before 0.0.
+ *
+ * @param data Values.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ */
+ static void sortZero(double[] data, int begin, int end) {
+ // Count negative zeros
+ int c = 0;
+ for (int i = begin; i < end; i++) {
+ if (Double.doubleToRawLongBits(data[i]) < 0) {
+ c++;
+ }
+ }
+ // Replace
+ if (c != 0) {
+ int i = begin;
+ while (c-- > 0) {
+ data[i++] = -0.0;
+ }
+ while (i < end) {
+ data[i++] = 0.0;
+ }
+ }
+ }
+
+ /**
+ * Sets the pivots.
+ *
+ * @param from Start (inclusive)
+ * @param to End (exclusive)
+ * @param pivots the pivots
+ */
+ private static void setPivots(int from, int to, BitSet pivots) {
+ if (from + 1 == to) {
+ pivots.set(from);
+ } else {
+ pivots.set(from, to);
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * Uses a Bentley-McIlroy quicksort partition method by Sedgewick.
+ *
+ * @param data Values.
+ * @param k Indices.
+ */
+ void partitionSBM(double[] data, int... k) {
+ final int n = k.length;
+ if (n < 1) {
+ return;
+ }
+
+ // Handle NaN
+ final int length = sortNaN(data);
+ if (length < 1) {
+ return;
+ }
+
+ if (n == 1) {
+ if (k[0] < length) {
+ partitionSBM(data, 0, length, k[0]);
+ }
+ return;
+ }
+ // Special case for partition around adjacent indices (for interpolation)
+ if (n == 2 && k[0] + 1 == k[1]) {
+ if (k[0] < length) {
+ final int p = partitionSBM(data, 0, length, k[0]);
+ if (p > k[1]) {
+ partitionMin(data, k[1], p);
+ }
+ }
+ return;
+ }
+
+ // To partition all k requires not moving any pivot k after it has been
+ // processed. This is supported using two strategies:
+ //
+ // 1. Processing k in sorted order:
+ // (k1, end), (k2, end), (k3, end), ... , k1 <= k2 <= k3
+ // This can reorder each region during processing without destroying sorted k.
+ //
+ // 2. Processing unique k and visiting array regions only once:
+ // Pre-process the pivots to make them unique and store the entire sorted
+ // region between the end pivots (k1, kn) in a BitSet type structure:
+ // |k1|......|k2|....|p|k3|k4|pppp|......|kn|
+ // k can be processed in any order, e.g. k3. We use already sorted regions
+ // |p| to bracket the search for each k, and skip k that are already sorted (k4).
+ // Worst case storage cost is Order(N / 64).
+ // The advantage is never visiting any part of the array twice. If the pivots
+ // saturate the entire range then performance degrades to the speed of
+ // the sort of the entire array.
+
+ // Multiple pivots
+ final BitSet pivots = new BitSet(length);
+
+ for (int i = 0; i < n; i++) {
+ final int kk = k[i];
+ if (kk >= length || pivots.get(kk)) {
+ // Already sorted
+ continue;
+ }
+ int begin;
+ int end;
+ if (i == 0) {
+ begin = 0;
+ end = length;
+ } else {
+ // Start inclusive
+ begin = pivots.previousSetBit(kk) + 1;
+ end = pivots.nextSetBit(kk + 1);
+ if (end < 0) {
+ end = length;
+ }
+ }
+ partitionSBM(data, begin, end, pivots, kk);
+ }
+ }
+
+ /**
+ * Move the minimum value to the start of the range.
+ *
+ * Note: Requires that the range contains no NaN values.
+ * Does not respect the ordering of signed zeros.
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ */
+ static void partitionMin(double[] data, int begin, int end) {
+ int i = begin;
+ double min = data[i];
+ int j = i;
+ while (++i < end) {
+ if (data[i] < min) {
+ min = data[i];
+ j = i;
+ }
+ }
+ //swap(data, begin, j)
+ data[j] = data[begin];
+ data[begin] = min;
+ }
+
+ /**
+ * Move the maximum value to the end of the range.
+ *
+ * Note: Requires that the range contains no NaN values.
+ * Does not respect the ordering of signed zeros.
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ */
+ static void partitionMax(double[] data, int begin, int end) {
+ int i = end - 1;
+ double max = data[i];
+ int j = i;
+ while (--i >= begin) {
+ if (data[i] > max) {
+ max = data[i];
+ j = i;
+ }
+ }
+ //swap(data, end - 1, j)
+ data[j] = data[end - 1];
+ data[end - 1] = max;
+ }
+
+ /**
+ * Partition around the Kth value in the array.
+ *
+ * This method can be used for repeat calls to identify Kth values in
+ * the same array by caching locations of pivots (correctly sorted indices).
+ *
+ * Note: Requires that the range contains no NaN values.
+ *
+ * Uses a Bentley-McIlroy quicksort partition method by Sedgewick.
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param pivots Cache of pivot points.
+ * @param k Index whose value in the array is of interest.
+ */
+ private void partitionSBM(double[] data, int begin, int end, BitSet pivots, int k) {
+ // Find the unsorted range containing k
+ final int[] upper = {0};
+ while (end - begin > minSelectSize) {
+ // Select a value and partition data array around it
+ final int from = partitionSBM(data, begin, end,
+ pivotingStrategy.pivotIndex(data, begin, end - 1, k), upper);
+ final int to = upper[0];
+ setPivots(from, to, pivots);
+ if (k >= to) {
+ // The element is in the right partition
+ begin = to;
+ } else if (k < from) {
+ // The element is in the left partition
+ end = from;
+ } else {
+ // The range contains the element we wanted
+ return;
+ }
+ }
+ setPivots(begin, end, pivots);
+ insertionSort(data, begin, end, begin != 0);
+ fixSignedZeros(data, begin, end);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Note: Requires that the range contains no NaN values.
+ *
+ * Uses a Bentley-McIlroy quicksort partition method by Sedgewick.
+ *
+ * @param data Data array.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param pivot Initial index of the pivot.
+ * @param upper Upper bound (exclusive) of the pivot range.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private static int partitionSBM(double[] data, int begin, int end, int pivot, int[] upper) {
+ // Single-pivot Bentley-McIlroy quicksort handling equal keys (Sedgewick's algorithm).
+ //
+ // Partition data using pivot P into less-than, greater-than or equal.
+ // P is placed at the end to act as a sentinal.
+ // k traverses the unknown region ??? and values moved if equal (l) or greater (g):
+ //
+ // left p i j q right
+ // | ==P | P | ==P |P|
+ //
+ // At the end P and additional equal values are swapped back to the centre.
+ //
+ // | P |
+ //
+ // Adapted from Sedgewick "Quicksort is optimal"
+ // https://sedgewick.io/wp-content/themes/sedgewick/talks/2002QuicksortIsOptimal.pdf
+ //
+ // The algorithm has been changed so that:
+ // - A pivot point must be provided.
+ // - An edge case where the search meets in the middle is handled.
+ // - Equal value data is not swapped to the end. Since the value is fixed then
+ // only the less than / greater than value must be moved from the end inwards.
+ // The end is then assumed to be the equal value. This would not work with
+ // object references. Equivalent swap calls are commented.
+ // - Added a fast-forward over initial range containing the pivot.
+
+ final int l = begin;
+ final int r = end - 1;
+
+ int p = l;
+ int q = r;
+
+ // Use the pivot index to set the upper sentinal value
+ final double v = data[pivot];
+ data[pivot] = data[r];
+ data[r] = v;
+
+ // Special case: count signed zeros
+ int c = 0;
+ if (v == 0) {
+ c = countSignedZeros(data, begin, end);
+ }
+
+ // Fast-forward over equal regions to reduce swaps
+ while (data[p] == v) {
+ if (++p == q) {
+ // Edge-case: constant value
+ if (c != 0) {
+ sortZero(data, begin, end);
+ }
+ upper[0] = end;
+ return begin;
+ }
+ }
+ // Cannot overrun as the prior scan using p stopped before the end
+ while (data[q - 1] == v) {
+ q--;
+ }
+
+ int i = p - 1;
+ int j = q;
+
+ for (;;) {
+ do {
+ ++i;
+ } while (data[i] < v);
+ while (v < data[--j]) {
+ // Stop at l (not i) allows scan loops to be independent
+ if (j == l) {
+ break;
+ }
+ }
+ if (i >= j) {
+ // Edge-case if search met on an internal pivot value
+ // (not at the greater equal region, i.e. i < q).
+ // Move this to the lower-equal region.
+ if (i == j && v == data[i]) {
+ //swap(data, i++, p++)
+ //data[i++] = data[p++];
+ data[i++] = data[p];
+ data[p++] = v;
+ }
+ break;
+ }
+ //swap(data, i, j)
+ final double vj = data[i];
+ final double vi = data[j];
+ data[i] = vi;
+ data[j] = vj;
+ if (vi == v) {
+ //swap(data, i, p++)
+ //data[i] = data[p++];
+ data[i] = data[p];
+ data[p++] = v;
+ }
+ if (vj == v) {
+ //swap(data, j, --q)
+ data[j] = data[--q];
+ data[q] = v;
+ }
+ }
+ // i is at the end (exclusive) of the less-than region
+
+ // Place pivot value in centre
+ //swap(data, r, i)
+ data[r] = data[i];
+ data[i] = v;
+
+ // Move equal regions to the centre.
+ // Set the pivot range [j, i) and move this outward for equal values.
+ j = i++;
+
+ // less-equal:
+ // for (int k = l; k < p; k++):
+ // swap(data, k, --j)
+ // greater-equal:
+ // for (int k = r; k-- > q; i++) {
+ // swap(data, k, i)
+
+ // Move the minimum of less-equal or less-than
+ int move = Math.min(p - l, j - p);
+ final int lower = j - (p - l);
+ for (int k = l; move-- > 0; k++) {
+ data[k] = data[--j];
+ data[j] = v;
+ }
+ // Move the minimum of greater-equal or greater-than
+ move = Math.min(r - q, q - i);
+ upper[0] = i + (r - q);
+ for (int k = r; move-- > 0; i++) {
+ data[--k] = data[i];
+ data[i] = v;
+ }
+
+ // Special case: fixed signed zeros
+ if (c != 0) {
+ p = lower;
+ while (c-- > 0) {
+ data[p++] = -0.0;
+ }
+ while (p < upper[0]) {
+ data[p++] = 0.0;
+ }
+ }
+
+ return lower;
+ }
+
+ /**
+ * Partition around the Kth value in the array.
+ *
+ * This method can be used for repeat calls to identify Kth values in
+ * the same array by caching locations of pivots (correctly sorted indices).
+ *
+ * Note: Requires that the range contains no NaN values.
+ *
+ * Uses a Bentley-McIlroy quicksort partition method by Sedgewick.
+ *
+ * Returns the last known pivot location adjacent to K.
+ * If {@code p <= k} the range [p, min{k+2, data.length}) is sorted.
+ * If {@code p > k} then p is a pivot.
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param k Index whose value in the array is of interest.
+ * @return the bound index
+ */
+ private int partitionSBM(double[] data, int begin, int end, int k) {
+ // Find the unsorted range containing k
+ final int[] upper = {0};
+ while (end - begin > minSelectSize) {
+ // Select a value and partition data array around it
+ final int from = partitionSBM(data, begin, end,
+ pivotingStrategy.pivotIndex(data, begin, end - 1, k), upper);
+ final int to = upper[0];
+ if (k >= to) {
+ // The element is in the right partition
+ begin = to;
+ } else if (k < from) {
+ // The element is in the left partition
+ end = from;
+ } else {
+ // The range contains the element we wanted
+ return end;
+ }
+ }
+ insertionSort(data, begin, end, begin != 0);
+ fixSignedZeros(data, begin, end);
+ // Either end == data.length and k+1 is sorted; or
+ // end == pivot and k+1 is sorted
+ return begin;
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * Uses a Bentley-McIlroy quicksort partition method.
+ *
+ * @param data Values.
+ * @param k Indices.
+ */
+ void partitionBM(double[] data, int... k) {
+ final int n = k.length;
+ if (n < 1) {
+ return;
+ }
+
+ // Handle NaN
+ final int length = sortNaN(data);
+ if (length < 1) {
+ return;
+ }
+
+ if (n == 1) {
+ partitionBM(data, 0, length, k[0]);
+ return;
+ }
+ // Special case for partition around adjacent indices (for interpolation)
+ if (n == 2 && k[0] + 1 == k[1]) {
+ final int p = partitionBM(data, 0, length, k[0]);
+ if (p > k[1]) {
+ partitionMin(data, k[1], p);
+ }
+ return;
+ }
+
+ // Multiple pivots
+ final BitSet pivots = new BitSet(length);
+
+ for (int i = 0; i < n; i++) {
+ final int kk = k[i];
+ if (kk >= length || pivots.get(kk)) {
+ // Already sorted
+ continue;
+ }
+ int begin;
+ int end;
+ if (i == 0) {
+ begin = 0;
+ end = length;
+ } else {
+ // Start inclusive
+ begin = pivots.previousSetBit(kk) + 1;
+ end = pivots.nextSetBit(kk + 1);
+ if (end < 0) {
+ end = length;
+ }
+ }
+ partitionBM(data, begin, end, pivots, kk);
+ }
+ }
+
+ /**
+ * Partition around the Kth value in the array.
+ *
+ * This method can be used for repeat calls to identify Kth values in
+ * the same array by caching locations of pivots (correctly sorted indices).
+ *
+ * Note: Requires that the range contains no NaN values.
+ *
+ * Uses a Bentley-McIlroy quicksort partition method.
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param pivots Cache of pivot points.
+ * @param k Index whose value in the array is of interest.
+ */
+ private void partitionBM(double[] data, int begin, int end, BitSet pivots, int k) {
+ // Find the unsorted range containing k
+ final int[] upper = {0};
+ while (end - begin > minSelectSize) {
+ // Select a value and partition data array around it
+ final int from = partitionBM(data, begin, end,
+ pivotingStrategy.pivotIndex(data, begin, end - 1, k), upper);
+ final int to = upper[0];
+ setPivots(from, to, pivots);
+ if (k >= to) {
+ // The element is in the right partition
+ begin = to;
+ } else if (k < from) {
+ // The element is in the left partition
+ end = from;
+ } else {
+ // The range contains the element we wanted
+ return;
+ }
+ }
+ setPivots(begin, end, pivots);
+ insertionSort(data, begin, end, begin != 0);
+ fixSignedZeros(data, begin, end);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Note: Requires that the range contains no NaN values.
+ *
+ * Uses a Bentley-McIlroy quicksort partition method.
+ *
+ * @param data Data array.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param pivot Initial index of the pivot.
+ * @param upper Upper bound (exclusive) of the pivot range.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private static int partitionBM(double[] data, int begin, int end, int pivot, int[] upper) {
+ // Partition method handling equal keys, Bentley-McIlroy quicksort.
+ //
+ // Adapted from program 7 in Bentley-McIlroy (1993)
+ // Engineering a sort function
+ // SOFTWARE—PRACTICE AND EXPERIENCE, VOL.23(11), 1249–1265
+ //
+ // 3-way partition of the data using a pivot value into
+ // less-than, equal or greater-than.
+ //
+ // First partition data into 4 reqions by scanning the unknown region from
+ // left (i) and right (j) and moving equal values to the ends:
+ // i-> <-j end
+ // l p | | q r|
+ // | equal | less | unknown | greater | equal ||
+ //
+ // <-j end
+ // l p i q r|
+ // | equal | less | greater | equal ||
+ //
+ // Then the equal values are copied from the ends to the centre:
+ // | less | equal | greater |
+
+ final int l = begin;
+ final int r = end - 1;
+
+ int i = l;
+ int j = r;
+ int p = l;
+ int q = r;
+
+ final double v = data[pivot];
+
+ // Special case: count signed zeros
+ int c = 0;
+ if (v == 0) {
+ c = countSignedZeros(data, begin, end);
+ }
+
+ for (;;) {
+ while (i <= j && data[i] <= v) {
+ if (data[i] == v) {
+ //swap(data, i, p++)
+ data[i] = data[p];
+ data[p++] = v;
+ }
+ i++;
+ }
+ while (j >= i && data[j] >= v) {
+ if (v == data[j]) {
+ //swap(data, j, q--)
+ data[j] = data[q];
+ data[q--] = v;
+ }
+ j--;
+ }
+ if (i > j) {
+ break;
+ }
+ swap(data, i++, j--);
+ }
+
+ // Move equal regions to the centre.
+ int s = Math.min(p - l, i - p);
+ for (int k = l; s > 0; k++, s--) {
+ //swap(data, k, i - s)
+ data[k] = data[i - s];
+ data[i - s] = v;
+ }
+ s = Math.min(q - j, r - q);
+ for (int k = i; s > 0; k++, s--) {
+ //swap(data, end - s, k)
+ data[end - s] = data[k];
+ data[k] = v;
+ }
+
+ // Set output range
+ i = i - p + l;
+ j = j - q + end;
+ upper[0] = j;
+
+ // Special case: fixed signed zeros
+ if (c != 0) {
+ p = i;
+ while (c-- > 0) {
+ data[p++] = -0.0;
+ }
+ while (p < j) {
+ data[p++] = 0.0;
+ }
+ }
+
+ return i;
+ }
+
+ /**
+ * Partition around the Kth value in the array.
+ *
+ * This method can be used for repeat calls to identify Kth values in
+ * the same array by caching locations of pivots (correctly sorted indices).
+ *
+ * Note: Requires that the range contains no NaN values.
+ *
+ * Uses a Bentley-McIlroy quicksort partition method.
+ *
+ * Returns the last known pivot location adjacent to K.
+ * If {@code p <= k} the range [p, min{k+2, data.length}) is sorted.
+ * If {@code p > k} then p is a pivot.
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param k Index whose value in the array is of interest.
+ * @return the bound index
+ */
+ private int partitionBM(double[] data, int begin, int end, int k) {
+ // Find the unsorted range containing k
+ final int[] upper = {0};
+ while (end - begin > minSelectSize) {
+ // Select a value and partition data array around it
+ final int from = partitionBM(data, begin, end,
+ pivotingStrategy.pivotIndex(data, begin, end - 1, k), upper);
+ final int to = upper[0];
+ if (k >= to) {
+ // The element is in the right partition
+ begin = to;
+ } else if (k < from) {
+ // The element is in the left partition
+ end = from;
+ } else {
+ // The range contains the element we wanted
+ return end;
+ }
+ }
+ insertionSort(data, begin, end, begin != 0);
+ fixSignedZeros(data, begin, end);
+ // Either end == data.length and k+1 is sorted; or
+ // end == pivot and k+1 is sorted
+ return begin;
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * Uses a dual-pivot quicksort method by Vladimir Yaroslavskiy.
+ *
+ * @param data Values.
+ * @param k Indices.
+ */
+ void partitionDP(double[] data, int... k) {
+ final int n = k.length;
+ if (n < 1) {
+ return;
+ }
+
+ // Handle NaN
+ final int length = sortNaN(data);
+ if (length < 1) {
+ return;
+ }
+
+ if (n == 1) {
+ partitionDP(data, 0, length, (BitSet) null, k[0]);
+ return;
+ }
+ // Special case for partition around adjacent indices (for interpolation)
+ if (n == 2 && k[0] + 1 == k[1]) {
+ final int p = partitionDP(data, 0, length, (BitSet) null, k[0]);
+ if (p > k[1]) {
+ partitionMin(data, k[1], p);
+ }
+ return;
+ }
+
+ // Multiple pivots
+ final BitSet pivots = new BitSet(length);
+
+ for (int i = 0; i < n; i++) {
+ final int kk = k[i];
+ if (kk >= length || pivots.get(kk)) {
+ // Already sorted
+ continue;
+ }
+ int begin;
+ int end;
+ if (i == 0) {
+ begin = 0;
+ end = length;
+ } else {
+ // Start inclusive
+ begin = pivots.previousSetBit(kk) + 1;
+ end = pivots.nextSetBit(kk + 1);
+ if (end < 0) {
+ end = length;
+ }
+ }
+ partitionDP(data, begin, end, pivots, kk);
+ }
+ }
+
+ /**
+ * Partition around the Kth value in the array.
+ *
+ * This method can be used for repeat calls to identify Kth values in
+ * the same array by caching locations of pivots (correctly sorted indices).
+ *
+ * Note: Requires that the range contains no NaN values.
+ *
+ * Uses a dual-pivot quicksort method by Vladimir Yaroslavskiy.
+ *
+ * Returns the pivot location adjacent to K to signal if K+1 is sorted.
+ * If {@code p <= k} the range [p, min{k+2, data.length}) is sorted.
+ * If {@code p > k} then p is a pivot.
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param pivots Cache of pivot points.
+ * @param k Index whose value in the array is of interest.
+ * @return the bound index
+ */
+ private int partitionDP(double[] data, int begin, int end, BitSet pivots, int k) {
+ // Find the unsorted range containing k
+ final int[] bounds = new int[4];
+ int div = 3;
+ while (end - begin > minSelectSize) {
+ div = partitionDP(data, begin, end, bounds, div);
+ final int k0 = bounds[0];
+ final int k1 = bounds[1];
+ final int k2 = bounds[2];
+ final int k3 = bounds[3];
+ // sorted in [k0, k1) and (k2, k3]
+ if (pivots != null) {
+ setPivots(k0, k1, pivots);
+ setPivots(k2 + 1, k3 + 1, pivots);
+ }
+ if (k > k3) {
+ // The element is in the right partition
+ begin = k3 + 1;
+ } else if (k < k0) {
+ // The element is in the left partition
+ end = k0;
+ } else if (k >= k1 && k <= k2) {
+ // Internal unsorted region
+ begin = k1;
+ end = k2 + 1;
+ } else {
+ // The sorted ranges contain the element we wanted.
+ // Return a pivot (k0; k2+1) to signal if k+1 is sorted.
+ if (k + 1 < k1) {
+ return k0;
+ }
+ if (k + 1 < k3) {
+ return k2 + 1;
+ }
+ return end;
+ }
+ }
+ if (pivots != null) {
+ setPivots(begin, end, pivots);
+ }
+ insertionSort(data, begin, end, begin != 0);
+ fixSignedZeros(data, begin, end);
+ // Either end == data.length and k+1 is sorted; or
+ // end == pivot and k+1 is sorted
+ return begin;
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Note: Requires that the range contains no NaN values. If the range contains
+ * signed zeros and one is chosen as a pivot point the sort order of zeros is correct.
+ *
+ * This method returns 4 points: the lower and upper pivots and bounds for
+ * the internal range of unsorted values.
+ * Bounds are set so {@code [k0, k1)} and {@code (k2, k3]} are fully sorted.
+ * When the range {@code [k0, k3]} contains fully sorted elements
+ * the result is set to {@code k1 = k3+1} and {@code k2 = k3}.
+ *
+ * Uses a dual-pivot quicksort method by Vladimir Yaroslavskiy.
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param bounds Points [k0, k1, k2, k3].
+ * @param div Divisor for the range to pick medians.
+ * @return div
+ */
+ private int partitionDP(double[] a, int left, int end, int[] bounds, int div) {
+ // Dual-pivot quicksort method by Vladimir Yaroslavskiy.
+ //
+ // Partition data using pivots P1 and P2 into less-than, greater-than or between.
+ // Pivot values P1 & P2 are placed at the end. If P1 < P2, P2 acts as a sentinal.
+ // k traverses the unknown region ??? and values moved if less (l) or greater (g):
+ //
+ // left l k g right
+ // |P1| Uses a dual-pivot quicksort method by Vladimir Yaroslavskiy.
+ *
+ * @param data Values.
+ * @param k Indices.
+ */
+ void partitionDP5(double[] data, int... k) {
+ final int n = k.length;
+ if (n < 1) {
+ return;
+ }
+
+ // Handle NaN
+ final int length = sortNaN(data);
+ if (length < 1) {
+ return;
+ }
+
+ if (n == 1) {
+ partitionDP5(data, 0, length, (BitSet) null, k[0]);
+ return;
+ }
+ // Special case for partition around adjacent indices (for interpolation)
+ if (n == 2 && k[0] + 1 == k[1]) {
+ final int p = partitionDP5(data, 0, length, (BitSet) null, k[0]);
+ if (p > k[1]) {
+ partitionMin(data, k[1], p);
+ }
+ return;
+ }
+
+ // Multiple pivots
+ final BitSet pivots = new BitSet(length);
+
+ for (int i = 0; i < n; i++) {
+ final int kk = k[i];
+ if (kk >= length || pivots.get(kk)) {
+ // Already sorted
+ continue;
+ }
+ int begin;
+ int end;
+ if (i == 0) {
+ begin = 0;
+ end = length;
+ } else {
+ // Start inclusive
+ begin = pivots.previousSetBit(kk) + 1;
+ end = pivots.nextSetBit(kk + 1);
+ if (end < 0) {
+ end = length;
+ }
+ }
+ partitionDP5(data, begin, end, pivots, kk);
+ }
+ }
+
+ /**
+ * Partition around the Kth value in the array.
+ *
+ * This method can be used for repeat calls to identify Kth values in
+ * the same array by caching locations of pivots (correctly sorted indices).
+ *
+ * Note: Requires that the range contains no NaN values.
+ *
+ * Uses a dual-pivot quicksort method by Vladimir Yaroslavskiy.
+ *
+ * Returns the pivot location adjacent to K to signal if K+1 is sorted.
+ * If {@code p <= k} the range [p, min{k+2, data.length}) is sorted.
+ * If {@code p > k} then p is a pivot.
+ *
+ * @param data Data array to use to find out the Kth value.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param pivots Cache of pivot points.
+ * @param k Index whose value in the array is of interest.
+ * @return the bound index
+ */
+ private int partitionDP5(double[] data, int begin, int end, BitSet pivots, int k) {
+ // Find the unsorted range containing k
+ final int[] bounds = new int[4];
+ while (end - begin > minSelectSize) {
+ partitionDP5(data, begin, end, bounds);
+ final int k0 = bounds[0];
+ final int k1 = bounds[1];
+ final int k2 = bounds[2];
+ final int k3 = bounds[3];
+ // sorted in [k0, k1) and (k2, k3]
+ if (pivots != null) {
+ setPivots(k0, k1, pivots);
+ setPivots(k2 + 1, k3 + 1, pivots);
+ }
+ if (k > k3) {
+ // The element is in the right partition
+ begin = k3 + 1;
+ } else if (k < k0) {
+ // The element is in the left partition
+ end = k0;
+ } else if (k >= k1 && k <= k2) {
+ // Internal unsorted region
+ begin = k1;
+ end = k2 + 1;
+ } else {
+ // The sorted ranges contain the element we wanted.
+ // Return a pivot (k0; k2+1) to signal if k+1 is sorted.
+ if (k + 1 < k1) {
+ return k0;
+ }
+ if (k + 1 < k3) {
+ return k2 + 1;
+ }
+ return end;
+ }
+ }
+ if (pivots != null) {
+ setPivots(begin, end, pivots);
+ }
+ insertionSort(data, begin, end, begin != 0);
+ fixSignedZeros(data, begin, end);
+ // Either end == data.length and k+1 is sorted; or
+ // end == pivot and k+1 is sorted
+ return begin;
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Note: Requires that the range contains no NaN values. If the range contains
+ * signed zeros and one is chosen as a pivot point the sort order of zeros is correct.
+ *
+ * This method returns 4 points: the lower and upper pivots and bounds for
+ * the internal range of unsorted values.
+ * Bounds are set so {@code [k0, k1)} and {@code (k2, k3]} are fully sorted.
+ * When the range {@code [k0, k3]} contains fully sorted elements
+ * the result is set to {@code k1 = k3+1} and {@code k2 = k3}.
+ *
+ * Uses a dual-pivot quicksort method by Vladimir Yaroslavskiy.
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param bounds Points [k0, k1, k2, k3].
+ */
+ private static void partitionDP5(double[] a, int left, int end, int[] bounds) {
+ // Dual-pivot quicksort method by Vladimir Yaroslavskiy.
+ //
+ // Adapted from:
+ //
+ // http://codeblab.com/wp-content/uploads/2009/09/DualPivotQuicksort.pdf
+ //
+ // Modified to allow partial sorting (partitioning):
+ // - Choose a pivot using 5 sorted points from the range.
+ // - Ignore insertion sort for tiny array (handled by calling code)
+ // - Ignore recursive calls for a full sort (handled by calling code)
+ // - Check for equal elements if a pivot is a signed zero
+ // - Fix signed zeros within the region between pivots
+ // - Change to fast-forward over initial ascending / descending runs
+ // - Change to a single-pivot partition method if the pivots are equal
+
+ final int right = end - 1;
+ final int len = right - left;
+
+ // Find pivots:
+
+ // Original method: Guess medians using 1/3 and 2/3 of range.
+ // Here we sort 5 points and choose 2 and 4 as the pivots: 1/6, 1/3, 1/2, 2/3, 5/6
+ // 1/6 ~ 1/8 + 1/32. Ensure the value is above zero to choose different points!
+ // This is safe is len >= 4.
+ final int sixth = 1 + (len >>> 3) + (len >>> 5);
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - sixth;
+ final int p1 = p2 - sixth;
+ final int p4 = p3 + sixth;
+ final int p5 = p4 + sixth;
+ Sorting.sort5(a, p1, p2, p3, p4, p5);
+
+ // For testing
+ //p2 = DualPivotingStrategy.SORT_5.pivotIndex(a, left, end - 1, bounds);
+ //p4 = bounds[0];
+
+ final double pivot1 = a[p2];
+ final double pivot2 = a[p4];
+
+ // Add property to control this switch so we can benchmark not using it.
+
+ if (pivot1 == pivot2) {
+ // pivots == median !
+ // Switch to a single pivot sort around the estimated median
+ final int lower = partitionSBM(a, left, end, p3, bounds);
+ final int upper = bounds[0];
+ // Set dual pivot range
+ bounds[0] = lower;
+ bounds[3] = upper - 1;
+ // No unsorted internal region
+ bounds[1] = upper;
+ bounds[2] = upper - 1;
+ return;
+ }
+
+ // Special case: Handle signed zeros
+ int c = 0;
+ if (pivot1 == 0 || pivot2 == 0) {
+ c = countSignedZeros(a, left, end);
+ }
+
+ // Move ends to the pivot locations.
+ // After sorting the final pivot locations are overwritten.
+ a[p2] = a[left];
+ a[p4] = a[right];
+ // It is assumed
+ //a[left] = pivot1
+ //a[right] = pivot2
+
+ // pointers
+ int less = left + 1;
+ int great = right - 1;
+
+ // Fast-forward ascending / descending runs to reduce swaps
+ while (a[less] < pivot1) {
+ less++;
+ }
+ while (a[great] > pivot2) {
+ great--;
+ }
+
+ // sorting
+ SORTING:
+ for (int k = less; k <= great; k++) {
+ final double v = a[k];
+ if (v < pivot1) {
+ //swap(a, k, less++)
+ a[k] = a[less];
+ a[less] = v;
+ less++;
+ } else if (v > pivot2) {
+ // Original
+ //while (k < great && a[great] > pivot2) {
+ // great--;
+ //}
+ while (a[great] > pivot2) {
+ if (great-- == k) {
+ // Done
+ break SORTING;
+ }
+ }
+ // swap(a, k, great--)
+ // if (a[k] < pivot1)
+ // swap(a, k, less++)
+ final double w = a[great];
+ a[great] = v;
+ great--;
+ // a[k] = w
+ if (w < pivot1) {
+ a[k] = a[less];
+ a[less] = w;
+ less++;
+ } else {
+ a[k] = w;
+ }
+ }
+ }
+ // swaps
+ //swap(a, less - 1, left)
+ //swap(a, great + 1, right)
+ a[left] = a[less - 1];
+ a[less - 1] = pivot1;
+ a[right] = a[great + 1];
+ a[great + 1] = pivot2;
+
+ // unsorted in [less, great]
+
+ // Set the pivots
+ bounds[0] = less - 1;
+ bounds[3] = great + 1;
+ //partitionDP5(a, left, less - 2)
+ //partitionDP5(a, great + 2, right)
+
+ // equal elements
+ // Original paper: If middle partition (dist) is bigger
+ // than (length - 13) then check for equal elements, i.e.
+ // if the middle was very large there may be many repeated elements.
+ // 13 = 27 / 2 where 27 is the threshold for quicksort.
+
+ // Look for equal elements if the centre is more than 2/3 the length
+ // We always do this if the pivots are signed zeros.
+ if ((less < p1 && great > p5 || c != 0) && pivot1 != pivot2) {
+
+ // Fast-forward to reduce swaps
+ while (a[less] == pivot1) {
+ less++;
+ }
+ while (a[great] == pivot2) {
+ great--;
+ }
+
+ // This copies the logic in the sorting loop using == comparisons
+ EQUAL:
+ for (int k = less; k <= great; k++) {
+ final double v = a[k];
+ if (v == pivot1) {
+ //swap(a, k, less++)
+ a[k] = a[less];
+ a[less] = v;
+ less++;
+ } else if (v == pivot2) {
+ while (a[great] == pivot2) {
+ if (great-- == k) {
+ // Done
+ break EQUAL;
+ }
+ }
+ final double w = a[great];
+ a[great] = v;
+ great--;
+ if (w == pivot1) {
+ a[k] = a[less];
+ a[less] = w;
+ less++;
+ } else {
+ a[k] = w;
+ }
+ }
+ }
+ }
+ // unsorted in [less, great]
+ if (pivot1 < pivot2 && less < great) {
+ //partitionDP5(a, less, great)
+ bounds[1] = less;
+ bounds[2] = great;
+ } else {
+ // Fully sorted
+ bounds[1] = bounds[3] + 1;
+ bounds[2] = bounds[3];
+ }
+
+ // Fix signed zeros
+ if (c != 0) {
+ int i;
+ if (pivot1 == 0) {
+ i = bounds[0];
+ while (c-- > 0) {
+ a[i++] = -0.0;
+ }
+ while (i < end && a[i] == 0) {
+ a[i++] = 0.0;
+ }
+ } else {
+ i = bounds[3];
+ while (a[i] == 0) {
+ a[i--] = 0.0;
+ if (i == left) {
+ break;
+ }
+ }
+ while (c-- > 0) {
+ a[++i] = -0.0;
+ }
+ }
+ }
+ }
+
+ /**
+ * Swaps the two specified elements in the array.
+ *
+ * @param array Array.
+ * @param i First index.
+ * @param j Second index.
+ */
+ private static void swap(double[] array, int i, int j) {
+ final double tmp = array[i];
+ array[i] = array[j];
+ array[j] = tmp;
+ }
+
+ /**
+ * Sort the data.
+ *
+ * Uses a single-pivot quicksort partition method.
+ *
+ * @param data Values.
+ */
+ void sortSP(double[] data) {
+ sortSP(data, 0, data.length);
+ }
+
+ /**
+ * Sort the data.
+ *
+ * @param data Values.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ */
+ private void sortSP(double[] data, int begin, int end) {
+ if (end - begin <= 1) {
+ return;
+ }
+ final int i = partitionSP(data, begin, end,
+ pivotingStrategy.pivotIndex(data, begin, end - 1, begin));
+ sortSP(data, begin, i);
+ sortSP(data, i + 1, end);
+ }
+
+ /**
+ * Sort the data.
+ *
+ * Uses a Bentley-McIlroy quicksort partition method.
+ *
+ * @param data Values.
+ */
+ void sortSBM(double[] data) {
+ sortSBM(data, 0, sortNaN(data));
+ }
+
+ /**
+ * Sort the data. Requires no NaN values in the range.
+ *
+ * @param data Values.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ */
+ private void sortSBM(double[] data, int begin, int end) {
+ if (end - begin <= minSelectSize) {
+ insertionSort(data, begin, end, begin != 0);
+ if (begin < end) {
+ fixSignedZeros(data, begin, end);
+ }
+ return;
+ }
+ final int[] to = {0};
+ final int from = partitionSBM(data, begin, end,
+ pivotingStrategy.pivotIndex(data, begin, end - 1, begin), to);
+ sortSBM(data, begin, from);
+ sortSBM(data, to[0], end);
+ }
+
+ /**
+ * Sort the data.
+ *
+ * Uses a Bentley-McIlroy quicksort partition method.
+ *
+ * @param data Values.
+ */
+ void sortBM(double[] data) {
+ sortBM(data, 0, sortNaN(data));
+ }
+
+ /**
+ * Sort the data. Requires no NaN values in the range.
+ *
+ * @param data Values.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ */
+ private void sortBM(double[] data, int begin, int end) {
+ if (end - begin <= minSelectSize) {
+ insertionSort(data, begin, end, begin != 0);
+ if (begin < end) {
+ fixSignedZeros(data, begin, end);
+ }
+ return;
+ }
+ final int[] to = {0};
+ final int from = partitionBM(data, begin, end,
+ pivotingStrategy.pivotIndex(data, begin, end - 1, begin), to);
+ sortBM(data, begin, from);
+ sortBM(data, to[0], end);
+ }
+
+ /**
+ * Sort the data.
+ *
+ * Uses a dual-pivot quicksort method by Vladimir Yaroslavskiy.
+ *
+ * @param data Values.
+ */
+ void sortDP(double[] data) {
+ sortDP(data, 0, sortNaN(data), 3);
+ }
+
+ /**
+ * Sort the data. Requires no NaN values in the range.
+ *
+ * @param data Values.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ * @param div Divisor for the range to pick medians.
+ */
+ private void sortDP(double[] data, int begin, int end, int div) {
+ if (end - begin <= minSelectSize) {
+ insertionSort(data, begin, end, begin != 0);
+ if (begin < end) {
+ fixSignedZeros(data, begin, end);
+ }
+ return;
+ }
+ final int[] bounds = new int[4];
+ div = partitionDP(data, begin, end, bounds, div);
+ final int k0 = bounds[0];
+ final int k1 = bounds[1];
+ final int k2 = bounds[2];
+ final int k3 = bounds[3];
+ sortDP(data, begin, k0, div);
+ sortDP(data, k3 + 1, end, div);
+ sortDP(data, k1, k2 + 1, div);
+ }
+
+ /**
+ * Sort the data.
+ *
+ * Uses a dual-pivot quicksort method by Vladimir Yaroslavskiy optimised
+ * to choose the pivots using 5 sorted points.
+ *
+ * @param data Values.
+ */
+ void sortDP5(double[] data) {
+ sortDP5(data, 0, sortNaN(data));
+ }
+
+ /**
+ * Sort the data. Requires no NaN values in the range.
+ *
+ * @param data Values.
+ * @param begin Lower bound (inclusive).
+ * @param end Upper bound (exclusive).
+ */
+ private void sortDP5(double[] data, int begin, int end) {
+ if (end - begin <= minSelectSize) {
+ insertionSort(data, begin, end, begin != 0);
+ if (begin < end) {
+ fixSignedZeros(data, begin, end);
+ }
+ return;
+ }
+ final int[] bounds = new int[4];
+ partitionDP5(data, begin, end, bounds);
+ final int k0 = bounds[0];
+ final int k1 = bounds[1];
+ final int k2 = bounds[2];
+ final int k3 = bounds[3];
+ sortDP5(data, begin, k0);
+ sortDP5(data, k3 + 1, end);
+ sortDP5(data, k1, k2 + 1);
+ }
+
+ /**
+ * Creates the pivots heap for a data array of the specified {@code length}.
+ * If the array is too small to use the pivots heap then an empty array is returned.
+ *
+ * @param length Length.
+ * @return the pivots heap
+ */
+ static int[] createPivotsHeap(int length) {
+ if (length <= MIN_SELECT_SIZE) {
+ return NO_PIVOTS;
+ }
+ // Size should be x^2 - 1, where x is the layers in the heap.
+ // Do not create more pivots than the array length. When partitions are small
+ // the pivots are no longer used so this does not have to contain all indices.
+ // Default size in Commons Math Percentile class was 1023 (10 layers).
+ final int n = nextPow2(length >>> 1);
+ final int[] pivotsHeap = new int[Math.min(n, 1 << 10) - 1];
+ Arrays.fill(pivotsHeap, -1);
+ return pivotsHeap;
+ }
+
+ /**
+ * Returns the closest power-of-two number greater than or equal to value.
+ *
+ * Warning: This will return {@link Integer#MIN_VALUE} for any value above
+ * {@code 1 << 30}. This is the next power of 2 as an unsigned integer.
+ *
+ * @param value the value (must be positive)
+ * @return the closest power-of-two number greater than or equal to value
+ */
+ private static int nextPow2(int value) {
+ // shift by -x is equal to shift by (32 - x) as only the low 5-bits are used.
+ return 1 << -Integer.numberOfLeadingZeros(value - 1);
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/NaNPolicy.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/NaNPolicy.java
new file mode 100644
index 000000000..197374dd7
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/NaNPolicy.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * Defines the policy for {@link Double#NaN NaN} values found in data.
+ *
+ * @since 1.2
+ */
+public enum NaNPolicy {
+ /** NaNs are included in the data. */
+ INCLUDE,
+ /** NaNs are excluded from the data. */
+ EXCLUDE,
+ /** NaNs result in an exception. */
+ ERROR
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/Partition.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/Partition.java
new file mode 100644
index 000000000..2377254b9
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/Partition.java
@@ -0,0 +1,10368 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.SplittableRandom;
+import java.util.function.IntConsumer;
+import java.util.function.IntUnaryOperator;
+import java.util.function.Supplier;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+
+/**
+ * Partition array data.
+ *
+ * Arranges elements such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * Examples:
+ *
+ * Note: Unless otherwise stated, methods in this class require that the floating-point data
+ * contains no NaN values; ordering does not respect the order of signed zeros imposed by
+ * {@link Double#compare(double, double)}.
+ *
+ * References
+ *
+ * Quickselect is introduced in Hoare [1]. This selects an element {@code k} from {@code n}
+ * using repeat division of the data around a partition element, recursing into the
+ * partition that contains {@code k}.
+ *
+ * Introselect is introduced in Musser [2]. This detects excess recursion in quickselect
+ * and reverts to heapselect to achieve an improved worst case bound on selection.
+ *
+ * Use of dual-pivot quickselect is analysed in Wild et al [3] and shown to require
+ * marginally more comparisons than single-pivot quickselect on a uniformly chosen order
+ * statistic {@code k} and extremal order statistic (see table 1, page 19). This analysis
+ * is reflected in the current implementation where dual-pivot quickselect is marginally
+ * slower when {@code k} is close to the end of the data. However the dual-pivot quickselect
+ * outperforms single-pivot quickselect when using multiple {@code k}; often significantly
+ * when {@code k} or {@code n} are large.
+ *
+ * Use of sampling to identify a pivot that places {@code k} in the smaller partition is
+ * performed in the SELECT algorithm of Floyd and Rivest [4]. The original algorithm partitions
+ * on a single pivot. This was extended by Kiwiel to partition using two pivots either side
+ * of {@code k} with high probability [5].
+ *
+ * Confidence bounds for the number of iterations to reduce a partition length by 2-x
+ * are provided in Valois [6].
+ *
+ * A worst-case linear time algorithm PICK is described in Blum et al [7]. This uses the
+ * median-of-medians as a partition element for selection which ensures a minimum fraction of the
+ * elements are eliminated per iteration. This was extended to use an asymmetric pivot choice
+ * with efficient reuse of the medians sample in the QuickselectAdpative algorithm of
+ * Alexandrescu [8].
+ *
+ * There are 3 variants using spacings of approximately 1/6, 1/7, and 1/8 computed
+ * using shifts to create 0.1719, 0.1406, and 0.125; with middle thirds on large
+ * lengths of 0.342, 0.28 and 0.25. The spacing using 1/7 is marginally faster when
+ * performing a full sort than the others; thus favouring a smaller middle third, but
+ * not too small, appears to be most performant.
+ */
+ static final DualPivotingStrategy DUAL_PIVOTING_STRATEGY = DualPivotingStrategy.SORT_5B;
+ /** Minimum selection size for quickselect/quicksort.
+ * Below this switch to sortselect/insertion sort rather than selection.
+ * Dual-pivot quicksort used 27 in Yaroslavskiy's original paper.
+ * Changes to this value are only noticeable when the input array is small.
+ *
+ * This is a legacy setting from when insertion sort was used as the stopper.
+ * This has been replaced by edge selection functions. Using insertion sort
+ * is slower as n must be sorted compared to an edge select that only sorts
+ * up to n/2 from the edge. It is disabled by default but can be used for
+ * benchmarking.
+ *
+ * If using insertion sort as the stopper for quickselect:
+ * Note that the original mapping can create a lower margin that
+ * does not contain k. This makes it possible to put k into the larger partition.
+ * For the middle and step left methods this heuristic is acceptable as the bias in
+ * margins is shifted but the smaller margin is at least 1/12 of the data and a choice
+ * of this side is not a severe penalty. For the far step left the original mapping
+ * will always create a smaller margin that does not contain k. Removing this
+ * adaptive k and using the median of the 12th-tile shows a measurable speed-up
+ * as the smaller margin always contains k. This result has been extended to change
+ * the mapping for the far step to ensure the smaller
+ * margin always contains at least k elements. This is faster and so enabled by default. */
+ static final int FLAG_QA_FAR_STEP_ADAPT_ORIGINAL = 0x10;
+ /** Use a 12th-tile for the sampling mode in the middle repeated step method.
+ * The default uses a 9th-tile which is a larger sample than the 12th-tile used in
+ * the step left/far left methods. */
+ static final int FLAG_QA_MIDDLE_12 = 0x20;
+ /** Position the sample for quickselect adaptive to place the mapped k' at the target index k.
+ * This is not possible for the far step methods as it can generated a bounds error as
+ * k approaches the edge. */
+ static final int FLAG_QA_SAMPLE_K = 0x40;
+
+ /** Threshold to use sub-sampling of the range to identify the single pivot.
+ * Sub-sampling uses the Floyd-Rivest algorithm to partition a sample of the data to
+ * identify a pivot so that the target element is in the smaller set after partitioning.
+ * The original FR paper used 600 otherwise reverted to the target index as the pivot.
+ * This implementation uses a sample to identify a median pivot which increases robustness
+ * at small size on a variety of data and allows raising the original FR threshold.
+ * At 600, FR has no speed-up; at double this the speed-up can be measured. */
+ static final int SELECT_SUB_SAMPLING_SIZE = 1200;
+
+ /** Message for an unsupported introselect configuration. */
+ private static final String UNSUPPORTED_INTROSELECT = "Unsupported introselect: ";
+
+ /** Transformer factory for double data with the behaviour of a JDK sort.
+ * Moves NaN to the end of the data and handles signed zeros. Works on the data in-place. */
+ private static final Supplier This threshold is also used in the sort methods to switch to insertion sort;
+ * and in legacy partition methods which do not use edge selection. These may perform
+ * key analysis using this value to determine saturation. */
+ private final int minQuickSelectSize;
+ /** Constant for edgeselect. */
+ private final int edgeSelectConstant;
+ /** Size for sortselect in the linearselect function. Optimal value for this is higher
+ * than for regular quickselect as the median-of-medians pivot strategy is expensive.
+ * For convenience (limit overrides for the constructor) this is not final. */
+ private int linearSortSelectSize = LINEAR_SORTSELECT_SIZE;
+ /** Threshold to use sub-sampling of the range to identify the single pivot.
+ * Sub-sampling uses the Floyd-Rivest algorithm to partition a sample of the data. This
+ * identifies a pivot so that the target element is in the smaller set after partitioning.
+ * The algorithm applies to searching for a single k.
+ * Not all single-pivot {@link PairedKeyStrategy} methods support sub-sampling. It is
+ * available to test in {@link #introselect(SPEPartition, double[], int, int, int, int)}.
+ *
+ * Sub-sampling can provide up to a 2-fold performance gain on large random data.
+ * It can have a 2-fold slowdown on some structured data (e.g. large shuffle data from
+ * the Bentley and McIlroy test data). Large shuffle data also observes a larger performance
+ * drop when using the SBM/BM/DNF partition methods (collect equal values) verses a
+ * simple SP method ignoring equal values. Here large ~500,000; the behaviour
+ * is observed at smaller sizes and becomes increasingly obvious at larger sizes.
+ *
+ * The algorithm relies on partitioning of a subset to be representative of partitioning
+ * of the entire data. Values in a small range partitioned around a pivot P
+ * should create P in a similar location to its position in the entire fully sorted array,
+ * ideally closer to the middle so ensuring elimination of the larger side.
+ * E.g. ordering around P in [ll, rr] will be similar to P's order in [l, r]:
+ * If the data in [ll, rr] is not representative then pivot selection based on a
+ * subset creates bad pivot choices and performance is worse than using a
+ * {@link PivotingStrategy}.
+ * Use of the Floyd-Rivest subset sampling is not always an improvement and is data
+ * dependent. The type of data cannot be known by the partition algorithm before processing.
+ * Thus the Floyd-Rivest subset sampling is more suitable as an option to be enabled by
+ * user settings.
+ *
+ * A random sub-sample can mitigate issues with non-representative data. This can
+ * be done by sampling with/without replacement into a new array; or shuffling in-place
+ * to part of the array. This implementation supports the later option.
+ *
+ * See
+ * Floyd-Rivest Algorithm (Wikipedia).
+ *
+ * Also used for the multiple of the original length to check the sum of the partition length
+ * for poor quickselect partitions.
+ * Also used for the number of iterations before checking the partition length has been
+ * reduced by a given factor of 2 (in iselect). */
+ private double recursionMultiple = RECURSION_MULTIPLE;
+ /** Constant {@code c} added to the length based recursion factor {@code x}.
+ * The recursion is set using {@code m * x + c}.
+ * Also used to specify the factor of two to reduce the partition length after a set
+ * number of iterations (in iselect). */
+ private int recursionConstant = RECURSION_CONSTANT;
+ /** Compression level for a {@link CompressedIndexSet} (in [1, 31]). */
+ private int compression = COMPRESSION_LEVEL;
+ /** Control flags level for Floyd-Rivest sub-sampling. */
+ private int controlFlags = CONTROL_FLAGS;
+ /** Consumer for the recursion level reached during partitioning. Used to analyse
+ * the distribution of the recursion for different input data. */
+ private IntConsumer recursionConsumer = i -> { /* no-op */ };
+
+ /** The single-pivot partition function. */
+ private SPEPartition spFunction;
+ /** The expand partition function. */
+ private ExpandPartition expandFunction;
+ /** The single-pivot linear partition function. */
+ private SPEPartition linearSpFunction;
+ /** Selection function used when {@code k} is close to the edge of the range. */
+ private SelectFunction edgeSelection;
+ /** Selection function used when quickselect progress is poor. */
+ private SelectFunction stopperSelection;
+ /** Quickselect adaptive mode. */
+ private AdaptMode adaptMode = ADAPT_MODE;
+
+ /** Quickselect adaptive mapping function applied when sampling-mode is on. */
+ private MapDistance samplingAdapt;
+ /** Quickselect adaptive mapping function applied when sampling-mode is on for
+ * distances close to the edge (i.e. the far-step functions). */
+ private MapDistance samplingEdgeAdapt;
+ /** Quickselect adaptive mapping function applied when sampling-mode is off. */
+ private MapDistance noSamplingAdapt;
+ /** Quickselect adaptive mapping function applied when sampling-mode is off for
+ * distances close to the edge (i.e. the far-step functions). */
+ private MapDistance noSamplingEdgeAdapt;
+
+ /**
+ * Define the strategy for processing multiple keys.
+ */
+ enum KeyStrategy {
+ /** Sort unique keys, collate ranges and process in ascending order. */
+ SEQUENTIAL,
+ /** Process in input order using an {@link IndexSet} to cover the entire range.
+ * Introselect implementations will use a {@link SearchableInterval}. */
+ INDEX_SET,
+ /** Process in input order using a {@link CompressedIndexSet} to cover the entire range.
+ * Introselect implementations will use a {@link SearchableInterval}. */
+ COMPRESSED_INDEX_SET,
+ /** Process in input order using a {@link PivotCache} to cover the minimum range. */
+ PIVOT_CACHE,
+ /** Sort unique keys and process using recursion with division of the keys
+ * for each sub-partition. */
+ ORDERED_KEYS,
+ /** Sort unique keys and process using recursion with a {@link ScanningKeyInterval}. */
+ SCANNING_KEY_SEARCHABLE_INTERVAL,
+ /** Sort unique keys and process using recursion with a {@link BinarySearchKeyInterval}. */
+ SEARCH_KEY_SEARCHABLE_INTERVAL,
+ /** Sort unique keys and process using recursion with a {@link KeyIndexIterator}. */
+ INDEX_ITERATOR,
+ /** Process in input order using an {@link IndexIterator} of a {@link CompressedIndexSet}. */
+ COMPRESSED_INDEX_ITERATOR,
+ /** Process using recursion with an {@link IndexSet}-based {@link UpdatingInterval}. */
+ INDEX_SET_UPDATING_INTERVAL,
+ /** Sort unique keys and process using recursion with an {@link UpdatingInterval}. */
+ KEY_UPDATING_INTERVAL,
+ /** Process using recursion with an {@link IndexSet}-based {@link SplittingInterval}. */
+ INDEX_SET_SPLITTING_INTERVAL,
+ /** Sort unique keys and process using recursion with a {@link SplittingInterval}. */
+ KEY_SPLITTING_INTERVAL;
+ }
+
+ /**
+ * Define the strategy for processing 1 key or 2 keys: (k, k+1).
+ */
+ enum PairedKeyStrategy {
+ /** Use a dedicated single key method that returns information about (k+1).
+ * Use recursion depth to trigger the stopper select. */
+ PAIRED_KEYS,
+ /** Use a dedicated single key method that returns information about (k+1).
+ * Recursion is monitored by checking the partition is reduced by 2-x after
+ * {@code c} iterations where {@code x} is the
+ * {@link #setRecursionConstant(int) recursion constant} and {@code c} is the
+ * {@link #setRecursionMultiple(double) recursion multiple} */
+ PAIRED_KEYS_2,
+ /** Use a dedicated single key method that returns information about (k+1).
+ * Use a multiple of the sum of the length of all partitions to trigger the stopper select. */
+ PAIRED_KEYS_LEN,
+ /** Use a method that accepts two separate keys. The keys do not define a range
+ * and are independent. */
+ TWO_KEYS,
+ /** Use a method that accepts two keys to define a range.
+ * Recursion is monitored by checking the partition is reduced by 2-x after
+ * {@code c} iterations where {@code x} is the
+ * {@link #setRecursionConstant(int) recursion constant} and {@code c} is the
+ * {@link #setRecursionMultiple(double) recursion multiple} */
+ KEY_RANGE,
+ /** Use an {@link SearchableInterval} covering the keys. This will reuse a multi-key
+ * strategy with keys that are a very small range. */
+ SEARCHABLE_INTERVAL,
+ /** Use an {@link UpdatingInterval} covering the keys. This will reuse a multi-key
+ * strategy with keys that are a very small range. */
+ UPDATING_INTERVAL;
+ }
+
+ /**
+ * Define the strategy for single-pivot partitioning. Partitioning may be binary
+ * ({@code <, >}), or ternary ({@code <, ==, >}) by collecting values equal to the
+ * pivot value. Typically partitioning will use two pointers i and j to traverse the
+ * sequence from either end; or a single pointer i for a single pass.
+ *
+ * Binary partitioning will be faster for quickselect when no equal elements are
+ * present. As duplicates become increasingly likely a ternary partition will be
+ * faster for quickselect to avoid repeat processing of values (that matched the
+ * previous pivot) on the next iteration. The type of ternary partition with the best
+ * performance depends on the number of duplicates. In the extreme case of 1 or 2
+ * unique elements it is more likely to match the {@code ==, !=} comparison to the
+ * pivot than {@code <, >} (see {@link #DNF3}). An ideal ternary scheme should have
+ * little impact on data with no repeats, and significantly improve performance as the
+ * number of repeat elements increases.
+ *
+ * Binary partitioning will skip over values already {@code <, >}, or
+ * {@code <=, =>} to the pivot value; otherwise values at the pointers i and j are
+ * swapped. If using {@code <, >} then values can be placed at either end of the
+ * sequence that are {@code >=, <=} respectively to act as sentinels during the scan.
+ * This is always possible in binary partitioning as the pivot can be one sentinel;
+ * any other value will be either {@code <=, =>} to the pivot and so can be used at
+ * one or the other end as appropriate. Note: Many schemes omit using sentinels. Modern
+ * processor branch prediction nullifies the cost of checking indices remain within
+ * the {@code [left, right]} bounds. However placing sentinels is a negligible cost
+ * and at least simplifies the code for the region traversal.
+ *
+ * Bentley-McIlroy ternary partitioning schemes move equal values to the ends
+ * during the traversal, these are moved to the centre after the pass. This may use
+ * minimal swaps based on region sizes. Note that values already {@code <, >} are not
+ * moved during traversal allowing moves to be minimised.
+ *
+ * Dutch National Flag schemes move non-equal values to either end and finish with
+ * the equal value region in the middle. This requires that every element is moved
+ * during traversal, even if already {@code <, >}. This can be mitigated by fast-forward
+ * of pointers at the current {@code <, >} end points until the condition is not true.
+ *
+ * @see SPEPartition
+ */
+ enum SPStrategy {
+ /**
+ * Single-pivot partitioning. Uses a method adapted from Floyd and Rivest (1975)
+ * which uses sentinels to avoid bounds checks on the i and j pointers.
+ * This is a baseline for the maximum speed when no equal elements are present.
+ */
+ SP,
+ /**
+ * Bentley-McIlroy ternary partitioning. Requires bounds checks on the i and j
+ * pointers during traversal. Comparisons to the pivot use {@code <=, =>} and a
+ * second check for {@code ==} if the first is true.
+ */
+ BM,
+ /**
+ * Sedgewick's Bentley-McIlroy ternary partitioning. Requires bounds checks on the
+ * j pointer during traversal. Comparisons to the pivot use {@code <, >} and a
+ * second check for {@code ==} when both i and j have stopped.
+ */
+ SBM,
+ /**
+ * Kiwiel's Bentley-McIlroy ternary partitioning. Similar to Sedgewick's BM but
+ * avoids bounds checks on both pointers during traversal using sentinels.
+ * Comparisons to the pivot use {@code <, >} and a second check for {@code ==}
+ * when both i and j have stopped. Handles i and j meeting at the pivot without a
+ * swap.
+ */
+ KBM,
+ /**
+ * Dutch National Flag partitioning. Single pointer iteration using {@code <, >}
+ * comparisons to move elements to the edges. Fast-forwards any initial {@code <}
+ * region. The {@code ==} region is filled with the pivot after region traversal.
+ */
+ DNF1,
+ /**
+ * Dutch National Flag partitioning. Single pointer iteration using {@code <, >}
+ * comparisons to move elements to the edges. Fast-forwards any initial {@code <}
+ * region. The {@code >} region uses fast-forward to reduce swaps. The {@code ==}
+ * region is filled with the pivot after region traversal.
+ */
+ DNF2,
+ /**
+ * Dutch National Flag partitioning. Single pointer iteration using {@code !=}
+ * comparison to identify elements to move to the edges, then {@code <, >}
+ * comparisons. Fast-forwards any initial {@code <} region. The {@code >} region
+ * uses fast-forward to reduce swaps. The {@code ==} region is filled during
+ * traversal.
+ */
+ DNF3;
+ }
+
+ /**
+ * Define the strategy for expanding a partition. This function is used when
+ * partitioning has used a sample located within the range to find the pivot.
+ * The remaining range below and above the sample can be partitioned without
+ * re-processing the sample.
+ *
+ * Schemes may be binary ({@code <, >}), or ternary ({@code <, ==, >}) by
+ * collecting values equal to the pivot value. Schemes may process the
+ * unpartitioned range below and above the partitioned middle using a sweep
+ * outwards towards the ends; or start at the ends and sweep inwards towards
+ * the partitioned middle.
+ *
+ * @see ExpandPartition
+ */
+ enum ExpandStrategy {
+ /** Use the current {@link SPStrategy} partition method. This will not expand
+ * the partition but will Partition the Entire Range (PER). This can be used
+ * to test if the implementations of expand are efficient. */
+ PER,
+ /** Ternary partition method 1. Sweeps outwards and uses sentinels at the ends
+ * to avoid pointer range checks. Equal values are moved directly into the
+ * central pivot range. */
+ T1,
+ /** Ternary partition method 2. Similar to {@link #T1} with different method
+ * to set the sentinels. */
+ T2,
+ /** Binary partition method 1. Sweeps outwards and uses sentinels at the ends
+ * to avoid pointer range checks. */
+ B1,
+ /** Binary partition method 2. Similar to {@link #B1} with different method
+ * to set the sentinels. */
+ B2,
+ }
+
+ /**
+ * Define the strategy for the linear select single-pivot partition function. Linear
+ * select functions use a deterministic sample to find a pivot value that will
+ * eliminate at least a set fraction of the range (fixed borders/margins). After the
+ * sample has been processed to find a pivot the entire range is partitioned. This can
+ * be done by re-processing the entire range, or expanding the partition.
+ *
+ * Adaption (selecting a non-central index in the median-of-medians sample) creates
+ * asymmetric borders; in practice the larger border is typically eliminated per
+ * iteration which improves performance.
+ *
+ * @see SPStrategy
+ * @see ExpandStrategy
+ * @see SPEPartition
+ * @see ExpandPartition
+ */
+ enum LinearStrategy {
+ /** Uses the Blum, Floyd, Pratt, Rivest, and Tarjan (BFPRT) median-of-medians algorithm
+ * with medians of 5. This is the baseline version that creates the median sample
+ * at the left end and repartitions the entire range using the pivot.
+ * Fixed borders of 3/10. */
+ BFPRT,
+ /** Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with medians of 3. This is the baseline version that creates the median sample
+ * at the left end and repartitions the entire range using the pivot.
+ * Fixed borders of 2/9. */
+ RS,
+ /** Uses the Blum, Floyd, Pratt, Rivest, and Tarjan (BFPRT) median-of-medians algorithm
+ * with medians of 5. This is the improved version that creates the median sample
+ * in the centre and expands the partition around the pivot sample.
+ * Fixed borders of 3/10. */
+ BFPRT_IM,
+ /** Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with medians of 3. This is the improved version that creates the median sample
+ * in the centre and expands the partition around the pivot sample.
+ * Fixed borders of 2/9. */
+ RS_IM,
+ /** Uses the Blum, Floyd, Pratt, Rivest, and Tarjan (BFPRT) median-of-medians algorithm
+ * with medians of 5. This is the improved version that creates the median sample
+ * in the centre and expands the partition around the pivot sample; the adaption
+ * is to use k to define the pivot in the sample instead of using the median.
+ * This will not have fixed borders. */
+ BFPRTA,
+ /** Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with medians of 3. This is the adaptive version that creates the median sample
+ * in the centre and expands the partition around the pivot sample; the adaption
+ * is to use k to define the pivot in the sample instead of using the median.
+ * This will not have fixed borders. */
+ RSA;
+ }
+
+ /**
+ * Define the strategy for selecting {@code k} close to the edge.
+ * These are named to allow regex identification for dynamic configuration
+ * in benchmarking using the name; this uses the E (Edge) prefix.
+ */
+ enum EdgeSelectStrategy {
+ /** Use heapselect version 1. Selects {@code k} and an additional
+ * {@code c} elements closer to the edge than {@code k} using a heap
+ * structure. */
+ ESH,
+ /** Use heapselect version 2. Differs from {@link #ESH} in the
+ * final unwinding of the heap to sort the range {@code [ka, kb]};
+ * the heap construction is identical. */
+ ESH2,
+ /** Use sortselect version 1. Uses an insertion sort to maintain {@code k}
+ * and all elements closer to the edge as sorted. */
+ ESS,
+ /** Use sortselect version 2. Differs from {@link #ESS} by a using pointer
+ * into the sorted range to improve insertion speed. In practice the more
+ * complex code is not more performant. */
+ ESS2;
+ }
+
+ /**
+ * Define the strategy for selecting {@code k} when quickselect progress is poor
+ * (worst case is quadratic). This should be a method providing good worst-case
+ * performance.
+ * These are named to allow regex identification for dynamic configuration
+ * in benchmarking using the name; this uses the S (Stopper) prefix.
+ */
+ enum StopperStrategy {
+ /** Use heapselect version 1. Selects {@code k} and an additional
+ * {@code c} elements closer to the edge than {@code k}. Heapselect
+ * provides increasingly slower performance with distance from the edge.
+ * It has better worst-case performance than quickselect. */
+ SSH,
+ /** Use heapselect version 2. Differs from {@link #SSH} in the
+ * final unwinding of the heap to sort the range {@code [ka, kb]};
+ * the heap construction is identical. */
+ SSH2,
+ /** Use a linear selection algorithm with Order(n) worst-case performance.
+ * This is a median-of-medians using medians of size 5. This is the base
+ * implementation using a median sample into the first 20% of the data
+ * and not the improved version (with sample in the centre). */
+ SLS,
+ /** Use the quickselect adaptive algorithm with Order(n) worst-case performance. */
+ SQA;
+ }
+
+ /**
+ * Partition function. Used to benchmark different implementations.
+ *
+ * Note: The function is applied within a {@code [left, right]} bound. This bound
+ * is set using the entire range of the data to process, or it may be a sub-range
+ * due to previous partitioning. In this case the value at {@code left - 1} and/or
+ * {@code right + 1} can be a pivot. The value at these pivot points will be {@code <=} or
+ * {@code >=} respectively to all values within the range. This information is valuable
+ * during recursive partitioning and is passed as flags to the partition method.
+ */
+ private interface PartitionFunction {
+
+ /**
+ * Partition (partially sort) the array in the range {@code [left, right]} around
+ * a central region {@code [ka, kb]}. The central region should be entirely
+ * sorted.
+ *
+ * The {@link PivotStore} is only required to record pivots after {@code kb}.
+ * This is to support sequential ascending order processing of regions to partition.
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower bound (inclusive) of the central region.
+ * @param kb Upper bound (inclusive) of the central region.
+ * @param leftInner Flag to indicate {@code left - 1} is a pivot.
+ * @param rightInner Flag to indicate {@code right + 1} is a pivot.
+ * @param pivots Used to store sorted regions.
+ */
+ void partitionSequential(double[] a, int left, int right, int ka, int kb,
+ boolean leftInner, boolean rightInner, PivotStore pivots);
+
+ /**
+ * Partition (partially sort) the array in the range {@code [left, right]} around
+ * a central region {@code [ka, kb]}. The central region should be entirely
+ * sorted.
+ *
+ * The {@link PivotStore} records all pivots and sorted regions.
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower bound (inclusive) of the central region.
+ * @param kb Upper bound (inclusive) of the central region.
+ * @param leftInner Flag to indicate {@code left - 1} is a pivot.
+ * @param rightInner Flag to indicate {@code right + 1} is a pivot.
+ * @param pivots Used to store sorted regions.
+ */
+ void partition(double[] a, int left, int right, int ka, int kb,
+ boolean leftInner, boolean rightInner, PivotStore pivots);
+
+ /**
+ * Sort the array in the range {@code [left, right]}.
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param leftInner Flag to indicate {@code left - 1} is a pivot.
+ * @param rightInner Flag to indicate {@code right + 1} is a pivot.
+ */
+ void sort(double[] a, int left, int right, boolean leftInner, boolean rightInner);
+ }
+
+ /**
+ * Single-pivot partition method handling equal values.
+ */
+ @FunctionalInterface
+ private interface SPEPartitionFunction extends PartitionFunction {
+ /**
+ * Partition an array slice around a single pivot. Partitioning exchanges array
+ * elements such that all elements smaller than pivot are before it and all
+ * elements larger than pivot are after it.
+ *
+ * This method returns 2 points describing the pivot range of equal values.
+ * P |
+ * } This method returns 2 points describing the pivot range of equal values.
+ * P |
+ * } This method returns 4 points describing the pivot ranges of equal values.
+ * P |
+ * } Bounds are set so {@code i < k0}, {@code i > k3} and {@code k1 < i < k2} are
+ * unsorted. When the range {@code [k0, k3]} contains fully sorted elements the result
+ * is set to {@code k1 = k3; k2 == k0}. This can occur if
+ * {@code P1 == P2} or there are zero or 1 value between the pivots
+ * {@code P1 < v < P2}. Any sort/partition of ranges [left, k0-1], [k1+1, k2-1] and
+ * [k3+1, right] must check the length is {@code > 1}.
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param bounds Points [k1, k2, k3].
+ * @param pivot1 Pivot1 location.
+ * @param pivot2 Pivot2 location.
+ * @return Lower bound (inclusive) of the pivot range [k0].
+ */
+ int partition(double[] a, int left, int right, int pivot1, int pivot2, int[] bounds);
+ }
+
+ /**
+ * Select function.
+ *
+ * Used to define the function to call when {@code k} is close
+ * to the edge; or when quickselect progress is poor. This allows
+ * the edge-select or stopper-function to be configured using parameters.
+ */
+ @FunctionalInterface
+ interface SelectFunction {
+ /**
+ * Partition the elements between {@code ka} and {@code kb}.
+ * It is assumed {@code left <= ka <= kb <= right}.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ */
+ void partition(double[] a, int left, int right, int ka, int kb);
+ }
+
+ /**
+ * Single-pivot partition method handling a pre-partitioned range in the centre.
+ */
+ @FunctionalInterface
+ interface ExpandPartition {
+ /**
+ * Expand a partition around a single pivot. Partitioning exchanges array
+ * elements such that all elements smaller than pivot are before it and all
+ * elements larger than pivot are after it. The central region is already
+ * partitioned.
+ *
+ * P | ??? |
+ * } This method returns 2 points describing the pivot range of equal values.
+ * P |
+ * } For convenience this accepts the input range {@code [l, r]} instead of the length
+ * of the original range. The implementation may use the range or ignore it and only
+ * use the new range size {@code n}.
+ *
+ * @param d Distance from the edge in {@code [0, r - l]}.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param n Size of the new range.
+ * @return the mapped distance in [0, n)
+ */
+ abstract int mapDistance(int d, int l, int r, int n);
+ }
+
+ /**
+ * Encapsulate the state of adaption in the quickselect adaptive algorithm.
+ *
+ * To ensure linear runtime performance a fixed size of data must be eliminated at each
+ * step. This requires a median-of-median-of-medians pivot sample generated from all the data
+ * with the target {@code k} mapped to the middle of the sample so that the margins of the
+ * possible partitions are a minimum size.
+ * Note that random selection of a pivot will achieve the same margins with some probability
+ * and is less expensive to compute; runtime performance may be better or worse due to average
+ * quality of the pivot. The adaption in quickselect adaptive is two-fold:
+ * The quickselect adaptive paper suggests sampling mode is turned off when margins are not
+ * achieved. That is the size after partitioning is not as small as expected.
+ * However there is no detail on whether to turn off adaption, and the margins that are
+ * targeted. This provides the following possible state transitions:
+ * The behaviour is captured in this enum as a state-machine. The finite state
+ * is dependent on the start state. The transition from one state to the next may require a
+ * count of failures to achieve; this is not captured in this state machine.
+ *
+ * Note that use of no-adaption when sampling (case 4) is unlikely to work unless the sample
+ * median is representative of the location of the pivot sample. This is true for
+ * median-of-median-of-medians but not the offset pivot samples used in quickselect adaptive;
+ * this is supported for completeness and can be used to demonstrate its inefficiency.
+ */
+ enum AdaptMode {
+ /** No sampling and no adaption (fixed margins) for worst-case linear runtime performance.
+ * This is a terminal state. */
+ FIXED {
+ @Override
+ boolean isSampleMode() {
+ return false;
+ }
+ @Override
+ boolean isAdapt() {
+ return false;
+ }
+ @Override
+ AdaptMode update(int size, int l, int r) {
+ // No further states
+ return this;
+ }
+ },
+ /** Sampling and adaption. Failure to achieve the expected partition size
+ * will revert to no sampling but retain adaption. */
+ ADAPT1 {
+ @Override
+ boolean isSampleMode() {
+ return true;
+ }
+ @Override
+ boolean isAdapt() {
+ return true;
+ }
+ @Override
+ AdaptMode update(int size, int l, int r) {
+ return r - l <= size ? this : ADAPT1B;
+ }
+ },
+ /** No sampling and use adaption. This is a terminal state. */
+ ADAPT1B {
+ @Override
+ boolean isSampleMode() {
+ return false;
+ }
+ @Override
+ boolean isAdapt() {
+ return true;
+ }
+ @Override
+ AdaptMode update(int size, int l, int r) {
+ // No further states
+ return this;
+ }
+ },
+ /** Sampling and adaption. Failure to achieve the expected partition size
+ * will revert to no sampling and no adaption. */
+ ADAPT2 {
+ @Override
+ boolean isSampleMode() {
+ return true;
+ }
+ @Override
+ boolean isAdapt() {
+ return true;
+ }
+ @Override
+ AdaptMode update(int size, int l, int r) {
+ return r - l <= size ? this : FIXED;
+ }
+ },
+ /** Sampling and adaption. Failure to achieve the expected partition size
+ * will revert to no sampling but retain adaption. */
+ ADAPT3 {
+ @Override
+ boolean isSampleMode() {
+ return true;
+ }
+ @Override
+ boolean isAdapt() {
+ return true;
+ }
+ @Override
+ AdaptMode update(int size, int l, int r) {
+ return r - l <= size ? this : ADAPT3B;
+ }
+ },
+ /** No sampling and use adaption. Failure to achieve the expected partition size
+ * will disable adaption (revert to fixed margins). */
+ ADAPT3B {
+ @Override
+ boolean isSampleMode() {
+ return false;
+ }
+ @Override
+ boolean isAdapt() {
+ return true;
+ }
+ @Override
+ AdaptMode update(int size, int l, int r) {
+ return r - l <= size ? this : FIXED;
+ }
+ },
+ /** Sampling and no adaption. Failure to achieve the expected partition size
+ * will disabled sampling (revert to fixed margins). */
+ ADAPT4 {
+ @Override
+ boolean isSampleMode() {
+ return true;
+ }
+ @Override
+ boolean isAdapt() {
+ return false;
+ }
+ @Override
+ AdaptMode update(int size, int l, int r) {
+ return r - l <= size ? this : FIXED;
+ }
+ };
+
+ /**
+ * Checks if sample-mode is enabled.
+ *
+ * @return true if sample mode is enabled
+ */
+ abstract boolean isSampleMode();
+
+ /**
+ * Checks if adaption is enabled.
+ *
+ * @return true if adaption is enabled
+ */
+ abstract boolean isAdapt();
+
+ /**
+ * Update the state using the expected {@code size} of the partition and the actual size.
+ *
+ * For convenience this accepts the range {@code [l, r]} instead of the actual size.
+ * The implementation may use the range or ignore it.
+ *
+ * @param size Expected size of the partition.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @return the new state
+ */
+ abstract AdaptMode update(int size, int l, int r);
+ }
+
+ /**
+ * Constructor with defaults.
+ */
+ Partition() {
+ this(PIVOTING_STRATEGY, DUAL_PIVOTING_STRATEGY, MIN_QUICKSELECT_SIZE,
+ EDGESELECT_CONSTANT, SUBSAMPLING_SIZE);
+ }
+
+ /**
+ * Constructor with specified pivoting strategy and quickselect size.
+ *
+ * Used to test single-pivot quicksort.
+ *
+ * @param pivotingStrategy Pivoting strategy to use.
+ * @param minQuickSelectSize Minimum size for quickselect.
+ */
+ Partition(PivotingStrategy pivotingStrategy, int minQuickSelectSize) {
+ this(pivotingStrategy, DUAL_PIVOTING_STRATEGY, minQuickSelectSize,
+ EDGESELECT_CONSTANT, SUBSAMPLING_SIZE);
+ }
+
+ /**
+ * Constructor with specified pivoting strategy and quickselect size.
+ *
+ * Used to test dual-pivot quicksort.
+ *
+ * @param dualPivotingStrategy Dual pivoting strategy to use.
+ * @param minQuickSelectSize Minimum size for quickselect.
+ */
+ Partition(DualPivotingStrategy dualPivotingStrategy, int minQuickSelectSize) {
+ this(PIVOTING_STRATEGY, dualPivotingStrategy, minQuickSelectSize,
+ EDGESELECT_CONSTANT, SUBSAMPLING_SIZE);
+ }
+
+ /**
+ * Constructor with specified pivoting strategy; quickselect size; and edgeselect configuration.
+ *
+ * Used to test single-pivot quickselect.
+ *
+ * @param pivotingStrategy Pivoting strategy to use.
+ * @param minQuickSelectSize Minimum size for quickselect.
+ * @param edgeSelectConstant Length constant used for edge select distance from end threshold.
+ * @param subSamplingSize Size threshold to use sub-sampling for single-pivot selection.
+ */
+ Partition(PivotingStrategy pivotingStrategy,
+ int minQuickSelectSize, int edgeSelectConstant, int subSamplingSize) {
+ this(pivotingStrategy, DUAL_PIVOTING_STRATEGY, minQuickSelectSize, edgeSelectConstant,
+ subSamplingSize);
+ }
+
+ /**
+ * Constructor with specified dual-pivoting strategy; quickselect size; and edgeselect configuration.
+ *
+ * Used to test dual-pivot quickselect.
+ *
+ * @param dualPivotingStrategy Dual pivoting strategy to use.
+ * @param minQuickSelectSize Minimum size for quickselect.
+ * @param edgeSelectConstant Length constant used for edge select distance from end threshold.
+ */
+ Partition(DualPivotingStrategy dualPivotingStrategy,
+ int minQuickSelectSize, int edgeSelectConstant) {
+ this(PIVOTING_STRATEGY, dualPivotingStrategy, minQuickSelectSize,
+ edgeSelectConstant, SUBSAMPLING_SIZE);
+ }
+
+ /**
+ * Constructor with specified pivoting strategy; quickselect size; and edgeselect configuration.
+ *
+ * @param pivotingStrategy Pivoting strategy to use.
+ * @param dualPivotingStrategy Dual pivoting strategy to use.
+ * @param minQuickSelectSize Minimum size for quickselect.
+ * @param edgeSelectConstant Length constant used for distance from end threshold.
+ * @param subSamplingSize Size threshold to use sub-sampling for single-pivot selection.
+ */
+ Partition(PivotingStrategy pivotingStrategy, DualPivotingStrategy dualPivotingStrategy,
+ int minQuickSelectSize, int edgeSelectConstant, int subSamplingSize) {
+ this.pivotingStrategy = pivotingStrategy;
+ this.dualPivotingStrategy = dualPivotingStrategy;
+ this.minQuickSelectSize = minQuickSelectSize;
+ this.edgeSelectConstant = edgeSelectConstant;
+ this.subSamplingSize = subSamplingSize;
+ // Default strategies
+ setSPStrategy(SP_STRATEGY);
+ setEdgeSelectStrategy(EDGE_STRATEGY);
+ setStopperStrategy(STOPPER_STRATEGY);
+ setExpandStrategy(EXPAND_STRATEGY);
+ setLinearStrategy(LINEAR_STRATEGY);
+ // Called to initialise state
+ setControlFlags(0);
+ }
+
+ /**
+ * Sets the single-pivot partition strategy.
+ *
+ * @param v Value.
+ * @return {@code this} for chaining
+ */
+ Partition setSPStrategy(SPStrategy v) {
+ switch (v) {
+ case BM:
+ spFunction = Partition::partitionBM;
+ break;
+ case DNF1:
+ spFunction = Partition::partitionDNF1;
+ break;
+ case DNF2:
+ spFunction = Partition::partitionDNF2;
+ break;
+ case DNF3:
+ spFunction = Partition::partitionDNF3;
+ break;
+ case KBM:
+ spFunction = Partition::partitionKBM;
+ break;
+ case SBM:
+ spFunction = Partition::partitionSBM;
+ break;
+ case SP:
+ spFunction = Partition::partitionSP;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown single-pivot strategy: " + v);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the single-pivot partition expansion strategy.
+ *
+ * @param v Value.
+ * @return {@code this} for chaining
+ */
+ Partition setExpandStrategy(ExpandStrategy v) {
+ switch (v) {
+ case PER:
+ // Partition the entire range using the single-pivot partition strategy
+ expandFunction = (a, left, right, start, end, pivot0, pivot1, upper) ->
+ spFunction.partition(a, left, right, (pivot0 + pivot1) >>> 1, upper);
+ break;
+ case T1:
+ expandFunction = Partition::expandPartitionT1;
+ break;
+ case B1:
+ expandFunction = Partition::expandPartitionB1;
+ break;
+ case T2:
+ expandFunction = Partition::expandPartitionT2;
+ break;
+ case B2:
+ expandFunction = Partition::expandPartitionB2;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown expand strategy: " + v);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the single-pivot linear select strategy.
+ *
+ * Note: The linear select strategy will partition remaining range after computing
+ * a pivot from a sample by single-pivot partitioning or by expanding the partition
+ * (see {@link #setExpandStrategy(ExpandStrategy)}).
+ *
+ * @param v Value.
+ * @return {@code this} for chaining
+ * @see #setExpandStrategy(ExpandStrategy)
+ */
+ Partition setLinearStrategy(LinearStrategy v) {
+ switch (v) {
+ case BFPRT:
+ linearSpFunction = this::linearBFPRTBaseline;
+ break;
+ case RS:
+ linearSpFunction = this::linearRepeatedStepBaseline;
+ break;
+ case BFPRT_IM:
+ noSamplingAdapt = MapDistance.MEDIAN;
+ linearSpFunction = this::linearBFPRTImproved;
+ break;
+ case BFPRTA:
+ // Here we re-use the same method as the only difference is adaption of k
+ noSamplingAdapt = MapDistance.ADAPT;
+ linearSpFunction = this::linearBFPRTImproved;
+ break;
+ case RS_IM:
+ noSamplingAdapt = MapDistance.MEDIAN;
+ linearSpFunction = this::linearRepeatedStepImproved;
+ break;
+ case RSA:
+ // Here we re-use the same method as the only difference is adaption of k
+ noSamplingAdapt = MapDistance.ADAPT;
+ linearSpFunction = this::linearRepeatedStepImproved;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown linear strategy: " + v);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the edge-select strategy.
+ *
+ * @param v Value.
+ * @return {@code this} for chaining
+ */
+ Partition setEdgeSelectStrategy(EdgeSelectStrategy v) {
+ switch (v) {
+ case ESH:
+ edgeSelection = Partition::heapSelectRange;
+ break;
+ case ESH2:
+ edgeSelection = Partition::heapSelectRange2;
+ break;
+ case ESS:
+ edgeSelection = Partition::sortSelectRange;
+ break;
+ case ESS2:
+ edgeSelection = Partition::sortSelectRange2;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown edge select: " + v);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the stopper strategy (when quickselect progress is poor).
+ *
+ * @param v Value.
+ * @return {@code this} for chaining
+ */
+ Partition setStopperStrategy(StopperStrategy v) {
+ switch (v) {
+ case SSH:
+ stopperSelection = Partition::heapSelectRange;
+ break;
+ case SSH2:
+ stopperSelection = Partition::heapSelectRange2;
+ break;
+ case SLS:
+ // Linear select does not match the interface as it:
+ // - requires the single-pivot partition function
+ // - uses a bounds array to allow minimising the partition region size after pivot selection
+ stopperSelection = (a, l, r, ka, kb) -> linearSelect(getSPFunction(),
+ a, l, r, ka, kb, new int[2]);
+ break;
+ case SQA:
+ // Linear select does not match the interface as it:
+ // - uses a bounds array to allow minimising the partition region size after pivot selection
+ // - uses control flags to set sampling mode on/off
+ stopperSelection = (a, l, r, ka, kb) -> quickSelectAdaptive(a, l, r, ka, kb, new int[1],
+ adaptMode);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown stopper: " + v);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the key strategy.
+ *
+ * @param v Value.
+ * @return {@code this} for chaining
+ */
+ Partition setKeyStrategy(KeyStrategy v) {
+ this.keyStrategy = v;
+ return this;
+ }
+
+ /**
+ * Sets the paired key strategy.
+ *
+ * @param v Value.
+ * @return {@code this} for chaining
+ */
+ Partition setPairedKeyStrategy(PairedKeyStrategy v) {
+ this.pairedKeyStrategy = v;
+ return this;
+ }
+
+ /**
+ * Sets the recursion multiple.
+ *
+ * @param v Value.
+ * @return {@code this} for chaining
+ */
+ Partition setRecursionMultiple(double v) {
+ this.recursionMultiple = v;
+ return this;
+ }
+
+ /**
+ * Sets the recursion constant.
+ *
+ * @param v Value.
+ * @return {@code this} for chaining
+ */
+ Partition setRecursionConstant(int v) {
+ this.recursionConstant = v;
+ return this;
+ }
+
+ /**
+ * Sets the compression for a {@link CompressedIndexSet}.
+ *
+ * @param v Value.
+ * @return {@code this} for chaining
+ */
+ Partition setCompression(int v) {
+ if (v < 1 || v > Integer.SIZE - 1) {
+ throw new IllegalArgumentException("Bad compression: " + v);
+ }
+ this.compression = v;
+ return this;
+ }
+
+ /**
+ * Sets the control flags for Floyd-Rivest sub-sampling.
+ *
+ * @param v Value.
+ * @return {@code this} for chaining
+ */
+ Partition setControlFlags(int v) {
+ this.controlFlags = v;
+ // Quickselect adaptive requires functions to map k to the sample.
+ // These functions must be set based on the margins in the repeated step method.
+ // These will differ due to the implementation and whether the first step is
+ // skipped (sampling mode on).
+ if ((v & FLAG_QA_FAR_STEP_ADAPT_ORIGINAL) != 0) {
+ // Use the same mapping for all repeated step functions.
+ // This is the original behaviour from Alexandrescu (2016).
+ samplingAdapt = samplingEdgeAdapt = noSamplingAdapt = noSamplingEdgeAdapt = MapDistance.ADAPT;
+ } else {
+ // Default behaviour. This optimises the adaption for the algorithm.
+ samplingAdapt = MapDistance.ADAPT;
+ if ((v & FLAG_QA_FAR_STEP) != 0) {
+ // Switches the far-step to minimum-of-4, median-of-3.
+ // When sampling mode is on all samples are from median-of-3 and we
+ // use the same adaption.
+ samplingEdgeAdapt = MapDistance.ADAPT;
+ } else {
+ // Original far-step of lower-median-of-4, minimum-of-3
+ // When sampling mode is on the sample is a minimum-of-3. This halves the
+ // lower margin from median-of-3. Change the adaption to avoid
+ // a tiny lower margin (and possibility of k falling in a very large partition).
+ // Note: The only way we can ensure that k is inside the lower margin is by using
+ // (r-l) as the sample k. Compromise by using the midpoint for a 50% chance that
+ // k is inside the lower margin.
+ samplingEdgeAdapt = MapDistance.MEDIAN;
+ }
+ noSamplingAdapt = MapDistance.ADAPT;
+ // Force edge margin to contain the target index
+ noSamplingEdgeAdapt = MapDistance.EDGE_ADAPT;
+ }
+ return this;
+ }
+
+ /**
+ * Sets the size for sortselect for the linearselect algorithm.
+ * Must be above 0 for the algorithm to return (else an infinite loop occurs).
+ * The minimum size required depends on the expand partition function, and the
+ * same size relative to the range (e.g. 1/5, 1/9 or 1/12).
+ *
+ * @param v Value.
+ * @return {@code this} for chaining
+ */
+ Partition setLinearSortSelectSize(int v) {
+ if (v < 1) {
+ throw new IllegalArgumentException("Bad linear sortselect size: " + v);
+ }
+ this.linearSortSelectSize = v;
+ return this;
+ }
+
+ /**
+ * Sets the quickselect adaptive mode.
+ *
+ * @param v Value.
+ * @return {@code this} for chaining
+ */
+ Partition setAdaptMode(AdaptMode v) {
+ this.adaptMode = v;
+ return this;
+ }
+
+ /**
+ * Sets the recursion consumer. This is called with the value of the recursion
+ * counter immediately before the introselect routine returns. It is used to
+ * analyse recursion depth on various input data.
+ *
+ * @param v Value.
+ */
+ void setRecursionConsumer(IntConsumer v) {
+ this.recursionConsumer = Objects.requireNonNull(v);
+ }
+
+ /**
+ * Gets the single-pivot partition function.
+ *
+ * @return the single-pivot partition function
+ */
+ SPEPartition getSPFunction() {
+ return spFunction;
+ }
+
+ /**
+ * Configure the properties used by the static quickselect adaptive algorithm.
+ * The increment is used to update the current mode when the margins are not achieved.
+ *
+ * @param mode Initial mode.
+ * @param increment Flag increment
+ */
+ static void configureQaAdaptive(int mode, int increment) {
+ qaMode = mode;
+ qaIncrement = increment;
+ }
+
+ /**
+ * Move the minimum value to the start of the range.
+ *
+ * Note: Respects the ordering of signed zeros.
+ *
+ * Assumes {@code left <= right}.
+ *
+ * @param data Data.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void selectMin(double[] data, int left, int right) {
+ selectMinIgnoreZeros(data, left, right);
+ // Edge-case: if min was 0.0, check for a -0.0 above and swap.
+ if (data[left] == 0) {
+ minZero(data, left, right);
+ }
+ }
+
+ /**
+ * Move the maximum value to the end of the range.
+ *
+ * Note: Respects the ordering of signed zeros.
+ *
+ * Assumes {@code left <= right}.
+ *
+ * @param data Data.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void selectMax(double[] data, int left, int right) {
+ selectMaxIgnoreZeros(data, left, right);
+ // Edge-case: if max was -0.0, check for a 0.0 below and swap.
+ if (data[right] == 0) {
+ maxZero(data, left, right);
+ }
+ }
+
+ /**
+ * Place a negative signed zero at {@code left} before any positive signed zero in the range,
+ * {@code -0.0 < 0.0}.
+ *
+ * Warning: Only call when {@code data[left]} is zero.
+ *
+ * @param data Data.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ private static void minZero(double[] data, int left, int right) {
+ // Assume data[left] is zero and check the sign bit
+ if (Double.doubleToRawLongBits(data[left]) >= 0) {
+ // Check for a -0.0 above and swap.
+ // We only require 1 swap as this is not a full sort of zeros.
+ for (int k = left; ++k <= right;) {
+ if (data[k] == 0 && Double.doubleToRawLongBits(data[k]) < 0) {
+ data[k] = 0.0;
+ data[left] = -0.0;
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Place a positive signed zero at {@code right} after any negative signed zero in the range,
+ * {@code -0.0 < 0.0}.
+ *
+ * Warning: Only call when {@code data[right]} is zero.
+ *
+ * @param data Data.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ private static void maxZero(double[] data, int left, int right) {
+ // Assume data[right] is zero and check the sign bit
+ if (Double.doubleToRawLongBits(data[right]) < 0) {
+ // Check for a 0.0 below and swap.
+ // We only require 1 swap as this is not a full sort of zeros.
+ for (int k = right; --k >= left;) {
+ if (data[k] == 0 && Double.doubleToRawLongBits(data[k]) >= 0) {
+ data[k] = -0.0;
+ data[right] = 0.0;
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Move the minimum value to the start of the range.
+ *
+ * Assumes {@code left <= right}.
+ *
+ * @param data Data.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void selectMinIgnoreZeros(double[] data, int left, int right) {
+ // Mitigate worst case performance on descending data by backward sweep
+ double min = data[left];
+ for (int i = right + 1; --i > left;) {
+ final double v = data[i];
+ if (v < min) {
+ data[i] = min;
+ min = v;
+ }
+ }
+ data[left] = min;
+ }
+
+ /**
+ * Move the two smallest values to the start of the range.
+ *
+ * Assumes {@code left < right}.
+ *
+ * @param data Data.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void selectMin2IgnoreZeros(double[] data, int left, int right) {
+ double min1 = data[left + 1];
+ if (min1 < data[left]) {
+ min1 = data[left];
+ data[left] = data[left + 1];
+ }
+ // Mitigate worst case performance on descending data by backward sweep
+ for (int i = right + 1, end = left + 1; --i > end;) {
+ final double v = data[i];
+ if (v < min1) {
+ data[i] = min1;
+ if (v < data[left]) {
+ min1 = data[left];
+ data[left] = v;
+ } else {
+ min1 = v;
+ }
+ }
+ }
+ data[left + 1] = min1;
+ }
+
+ /**
+ * Move the maximum value to the end of the range.
+ *
+ * Assumes {@code left <= right}.
+ *
+ * @param data Data.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void selectMaxIgnoreZeros(double[] data, int left, int right) {
+ // Mitigate worst case performance on descending data by backward sweep
+ double max = data[right];
+ for (int i = left - 1; ++i < right;) {
+ final double v = data[i];
+ if (v > max) {
+ data[i] = max;
+ max = v;
+ }
+ }
+ data[right] = max;
+ }
+
+ /**
+ * Move the two largest values to the end of the range.
+ *
+ * Assumes {@code left < right}.
+ *
+ * @param data Data.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void selectMax2IgnoreZeros(double[] data, int left, int right) {
+ double max1 = data[right - 1];
+ if (max1 > data[right]) {
+ max1 = data[right];
+ data[right] = data[right - 1];
+ }
+ // Mitigate worst case performance on descending data by backward sweep
+ for (int i = left - 1, end = right - 1; ++i < end;) {
+ final double v = data[i];
+ if (v > max1) {
+ data[i] = max1;
+ if (v > data[right]) {
+ max1 = data[right];
+ data[right] = v;
+ } else {
+ max1 = v;
+ }
+ }
+ }
+ data[right - 1] = max1;
+ }
+
+ /**
+ * Sort the elements using a heap sort algorithm.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void heapSort(double[] a, int left, int right) {
+ // We could make a choice here between select left or right
+ heapSelectLeft(a, left, right, right, right - left);
+ }
+
+ /**
+ * Partition the elements {@code ka} and {@code kb} using a heap select algorithm. It
+ * is assumed {@code left <= ka <= kb <= right}. Any range between the two elements is
+ * not ensured to be sorted.
+ *
+ * If there is no range between the two point, i.e. {@code ka == kb} or
+ * {@code ka + 1 == kb}, it is preferred to use
+ * {@link #heapSelectRange(double[], int, int, int, int)}. The result is the same but
+ * the decision choice is simpler for the range function.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ * @see #heapSelectRange(double[], int, int, int, int)
+ */
+ static void heapSelectPair(double[] a, int left, int right, int ka, int kb) {
+ // Avoid the overhead of heap select on tiny data (supports right <= left).
+ if (right - left < MIN_HEAPSELECT_SIZE) {
+ Sorting.sort(a, left, right);
+ return;
+ }
+ // Call the appropriate heap partition function based on
+ // building a heap up to 50% of the length
+ // |l|-----|ka|--------|kb|------|r|
+ // ---d1----
+ // -----d3----
+ // ---------d2----------
+ // ----------d4-----------
+ final int d1 = ka - left;
+ final int d2 = kb - left;
+ final int d3 = right - kb;
+ final int d4 = right - ka;
+ if (d1 + d3 < Math.min(d2, d4)) {
+ // Partition both ends.
+ // Note: Not possible if ka == kb.
+ // s1 + s3 == r - l and >= than the smallest
+ // distance to one of the ends
+ heapSelectLeft(a, left, right, ka, 0);
+ // Repeat for the other side above ka
+ heapSelectRight(a, ka + 1, right, kb, 0);
+ } else if (d2 < d4) {
+ heapSelectLeft(a, left, right, kb, kb - ka);
+ } else {
+ // s4
+ heapSelectRight(a, left, right, ka, kb - ka);
+ }
+ }
+
+ /**
+ * Partition the elements between {@code ka} and {@code kb} using a heap select
+ * algorithm. It is assumed {@code left <= ka <= kb <= right}.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ * @see #heapSelectPair(double[], int, int, int, int)
+ */
+ static void heapSelectRange(double[] a, int left, int right, int ka, int kb) {
+ // Combine the test for right <= left with
+ // avoiding the overhead of heap select on tiny data.
+ if (right - left < MIN_HEAPSELECT_SIZE) {
+ Sorting.sort(a, left, right);
+ return;
+ }
+ // Call the appropriate heap partition function based on
+ // building a heap up to 50% of the length
+ // |l|-----|ka|--------|kb|------|r|
+ // |---------d1-----------|
+ // |----------d2-----------|
+ // Note: Optimisation for small heap size (n=1,2) is negligible.
+ // The main overhead is the test for insertion against the current top of the heap
+ // which grows increasingly unlikely as the range is scanned.
+ if (kb - left < right - ka) {
+ heapSelectLeft(a, left, right, kb, kb - ka);
+ } else {
+ heapSelectRight(a, left, right, ka, kb - ka);
+ }
+ }
+
+ /**
+ * Partition the minimum {@code n} elements below {@code k} where
+ * {@code n = k - left + 1}. Uses a heap select algorithm.
+ *
+ * Works with any {@code k} in the range {@code left <= k <= right}
+ * and can be used to perform a full sort of the range below {@code k}
+ * using the {@code count} parameter.
+ *
+ * For best performance this should be called with
+ * {@code k - left < right - k}, i.e.
+ * to partition a value in the lower half of the range.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param k Index to select.
+ * @param count Size of range to sort below k.
+ */
+ static void heapSelectLeft(double[] a, int left, int right, int k, int count) {
+ // Create a max heap in-place in [left, k], rooted at a[left] = max
+ // |l|-max-heap-|k|--------------|
+ // Build the heap using Floyd's heap-construction algorithm for heap size n.
+ // Start at parent of the last element in the heap (k),
+ // i.e. start = parent(n-1) : parent(c) = floor((c - 1) / 2) : c = k - left
+ int end = k + 1;
+ for (int p = left + ((k - left - 1) >> 1); p >= left; p--) {
+ maxHeapSiftDown(a, a[p], p, left, end);
+ }
+ // Scan the remaining data and insert
+ // Mitigate worst case performance on descending data by backward sweep
+ double max = a[left];
+ for (int i = right + 1; --i > k;) {
+ final double v = a[i];
+ if (v < max) {
+ a[i] = max;
+ maxHeapSiftDown(a, v, left, left, end);
+ max = a[left];
+ }
+ }
+
+ // To partition elements k (and below) move the top of the heap to the position
+ // immediately after the end of the reduced size heap; the previous end
+ // of the heap [k] is placed at the top
+ // |l|-max-heap-|k|--------------|
+ // | <-swap-> |
+ // The heap can be restored by sifting down the new top.
+
+ // Always require the top 1
+ a[left] = a[k];
+ a[k] = max;
+
+ if (count > 0) {
+ --end;
+ // Sifting limited to heap size of 2 (i.e. don't sift heap n==1)
+ for (int c = Math.min(count, end - left - 1); --c >= 0;) {
+ maxHeapSiftDown(a, a[left], left, left, end--);
+ // Move top of heap to the sorted end
+ max = a[left];
+ a[left] = a[end];
+ a[end] = max;
+ }
+ }
+ }
+
+ /**
+ * Sift the element down the max heap.
+ *
+ * Assumes {@code root <= p < end}, i.e. the max heap is above root.
+ *
+ * @param a Heap data.
+ * @param v Value to sift.
+ * @param p Start position.
+ * @param root Root of the heap.
+ * @param end End of the heap (exclusive).
+ */
+ private static void maxHeapSiftDown(double[] a, double v, int p, int root, int end) {
+ // child2 = root + 2 * (parent - root) + 2
+ // = 2 * parent - root + 2
+ while (true) {
+ // Right child
+ int c = (p << 1) - root + 2;
+ if (c > end) {
+ // No left child
+ break;
+ }
+ // Use the left child if right doesn't exist, or it is greater
+ if (c == end || a[c] < a[c - 1]) {
+ --c;
+ }
+ if (v >= a[c]) {
+ // Parent greater than largest child - done
+ break;
+ }
+ // Swap and descend
+ a[p] = a[c];
+ p = c;
+ }
+ a[p] = v;
+ }
+
+ /**
+ * Partition the maximum {@code n} elements above {@code k} where
+ * {@code n = right - k + 1}. Uses a heap select algorithm.
+ *
+ * Works with any {@code k} in the range {@code left <= k <= right}
+ * and can be used to perform a full sort of the range above {@code k}
+ * using the {@code count} parameter.
+ *
+ * For best performance this should be called with
+ * {@code k - left > right - k}, i.e.
+ * to partition a value in the upper half of the range.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param k Index to select.
+ * @param count Size of range to sort below k.
+ */
+ static void heapSelectRight(double[] a, int left, int right, int k, int count) {
+ // Create a min heap in-place in [k, right], rooted at a[right] = min
+ // |--------------|k|-min-heap-|r|
+ // Build the heap using Floyd's heap-construction algorithm for heap size n.
+ // Start at parent of the last element in the heap (k),
+ // i.e. start = parent(n-1) : parent(c) = floor((c - 1) / 2) : c = right - k
+ int end = k - 1;
+ for (int p = right - ((right - k - 1) >> 1); p <= right; p++) {
+ minHeapSiftDown(a, a[p], p, right, end);
+ }
+ // Scan the remaining data and insert
+ // Mitigate worst case performance on descending data by backward sweep
+ double min = a[right];
+ for (int i = left - 1; ++i < k;) {
+ final double v = a[i];
+ if (v > min) {
+ a[i] = min;
+ minHeapSiftDown(a, v, right, right, end);
+ min = a[right];
+ }
+ }
+
+ // To partition elements k (and above) move the top of the heap to the position
+ // immediately before the end of the reduced size heap; the previous end
+ // of the heap [k] is placed at the top.
+ // |--------------|k|-min-heap-|r|
+ // | <-swap-> |
+ // The heap can be restored by sifting down the new top.
+
+ // Always require the top 1
+ a[right] = a[k];
+ a[k] = min;
+
+ if (count > 0) {
+ ++end;
+ // Sifting limited to heap size of 2 (i.e. don't sift heap n==1)
+ for (int c = Math.min(count, right - end - 1); --c >= 0;) {
+ minHeapSiftDown(a, a[right], right, right, end++);
+ // Move top of heap to the sorted end
+ min = a[right];
+ a[right] = a[end];
+ a[end] = min;
+ }
+ }
+ }
+
+ /**
+ * Sift the element down the min heap.
+ *
+ * Assumes {@code root >= p > end}, i.e. the max heap is below root.
+ *
+ * @param a Heap data.
+ * @param v Value to sift.
+ * @param p Start position.
+ * @param root Root of the heap.
+ * @param end End of the heap (exclusive).
+ */
+ private static void minHeapSiftDown(double[] a, double v, int p, int root, int end) {
+ // child2 = root - 2 * (root - parent) - 2
+ // = 2 * parent - root - 2
+ while (true) {
+ // Right child
+ int c = (p << 1) - root - 2;
+ if (c < end) {
+ // No left child
+ break;
+ }
+ // Use the left child if right doesn't exist, or it is less
+ if (c == end || a[c] > a[c + 1]) {
+ ++c;
+ }
+ if (v <= a[c]) {
+ // Parent less than smallest child - done
+ break;
+ }
+ // Swap and descend
+ a[p] = a[c];
+ p = c;
+ }
+ a[p] = v;
+ }
+
+ /**
+ * Partition the elements between {@code ka} and {@code kb} using a heap select
+ * algorithm. It is assumed {@code left <= ka <= kb <= right}.
+ *
+ * Differs from {@link #heapSelectRange(double[], int, int, int, int)} by using
+ * a different extraction of the sorted elements from the heap.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ * @see #heapSelectPair(double[], int, int, int, int)
+ */
+ static void heapSelectRange2(double[] a, int left, int right, int ka, int kb) {
+ // Combine the test for right <= left with
+ // avoiding the overhead of heap select on tiny data.
+ if (right - left < MIN_HEAPSELECT_SIZE) {
+ Sorting.sort(a, left, right);
+ return;
+ }
+ // Use the smallest heap
+ if (kb - left < right - ka) {
+ heapSelectLeft2(a, left, right, ka, kb);
+ } else {
+ heapSelectRight2(a, left, right, ka, kb);
+ }
+ }
+
+ /**
+ * Partition the elements between {@code ka} and {@code kb} using a heap select
+ * algorithm. It is assumed {@code left <= ka <= kb <= right}.
+ *
+ * For best performance this should be called with {@code k} in the lower
+ * half of the range.
+ *
+ * Differs from {@link #heapSelectLeft(double[], int, int, int, int)} by using
+ * a different extraction of the sorted elements from the heap.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ */
+ static void heapSelectLeft2(double[] a, int left, int right, int ka, int kb) {
+ // Create a max heap in-place in [left, k], rooted at a[left] = max
+ // |l|-max-heap-|k|--------------|
+ // Build the heap using Floyd's heap-construction algorithm for heap size n.
+ // Start at parent of the last element in the heap (k),
+ // i.e. start = parent(n-1) : parent(c) = floor((c - 1) / 2) : c = k - left
+ int end = kb + 1;
+ for (int p = left + ((kb - left - 1) >> 1); p >= left; p--) {
+ maxHeapSiftDown(a, a[p], p, left, end);
+ }
+ // Scan the remaining data and insert
+ // Mitigate worst case performance on descending data by backward sweep
+ double max = a[left];
+ for (int i = right + 1; --i > kb;) {
+ final double v = a[i];
+ if (v < max) {
+ a[i] = max;
+ maxHeapSiftDown(a, v, left, left, end);
+ max = a[left];
+ }
+ }
+ // Partition [ka, kb]
+ // |l|-max-heap-|k|--------------|
+ // | <-swap-> | then sift down reduced size heap
+ // Avoid sifting heap of size 1
+ final int last = Math.max(left, ka - 1);
+ while (--end > last) {
+ maxHeapSiftDown(a, a[end], left, left, end);
+ a[end] = max;
+ max = a[left];
+ }
+ }
+
+ /**
+ * Partition the elements between {@code ka} and {@code kb} using a heap select
+ * algorithm. It is assumed {@code left <= ka <= kb <= right}.
+ *
+ * For best performance this should be called with {@code k} in the upper
+ * half of the range.
+ *
+ * Differs from {@link #heapSelectRight(double[], int, int, int, int)} by using
+ * a different extraction of the sorted elements from the heap.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ */
+ static void heapSelectRight2(double[] a, int left, int right, int ka, int kb) {
+ // Create a min heap in-place in [k, right], rooted at a[right] = min
+ // |--------------|k|-min-heap-|r|
+ // Build the heap using Floyd's heap-construction algorithm for heap size n.
+ // Start at parent of the last element in the heap (k),
+ // i.e. start = parent(n-1) : parent(c) = floor((c - 1) / 2) : c = right - k
+ int end = ka - 1;
+ for (int p = right - ((right - ka - 1) >> 1); p <= right; p++) {
+ minHeapSiftDown(a, a[p], p, right, end);
+ }
+ // Scan the remaining data and insert
+ // Mitigate worst case performance on descending data by backward sweep
+ double min = a[right];
+ for (int i = left - 1; ++i < ka;) {
+ final double v = a[i];
+ if (v > min) {
+ a[i] = min;
+ minHeapSiftDown(a, v, right, right, end);
+ min = a[right];
+ }
+ }
+ // Partition [ka, kb]
+ // |--------------|k|-min-heap-|r|
+ // | <-swap-> | then sift down reduced size heap
+ // Avoid sifting heap of size 1
+ final int last = Math.min(right, kb + 1);
+ while (++end < last) {
+ minHeapSiftDown(a, a[end], right, right, end);
+ a[end] = min;
+ min = a[right];
+ }
+ }
+
+ /**
+ * Partition the elements between {@code ka} and {@code kb} using a sort select
+ * algorithm. It is assumed {@code left <= ka <= kb <= right}.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ */
+ static void sortSelectRange(double[] a, int left, int right, int ka, int kb) {
+ // Combine the test for right <= left with
+ // avoiding the overhead of sort select on tiny data.
+ if (right - left <= MIN_SORTSELECT_SIZE) {
+ Sorting.sort(a, left, right);
+ return;
+ }
+ // Sort the smallest side
+ if (kb - left < right - ka) {
+ sortSelectLeft(a, left, right, kb);
+ } else {
+ sortSelectRight(a, left, right, ka);
+ }
+ }
+
+ /**
+ * Partition the minimum {@code n} elements below {@code k} where
+ * {@code n = k - left + 1}. Uses an insertion sort algorithm.
+ *
+ * Works with any {@code k} in the range {@code left <= k <= right}
+ * and performs a full sort of the range below {@code k}.
+ *
+ * For best performance this should be called with
+ * {@code k - left < right - k}, i.e.
+ * to partition a value in the lower half of the range.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param k Index to select.
+ */
+ static void sortSelectLeft(double[] a, int left, int right, int k) {
+ // Sort
+ for (int i = left; ++i <= k;) {
+ final double v = a[i];
+ // Move preceding higher elements above (if required)
+ if (v < a[i - 1]) {
+ int j = i;
+ while (--j >= left && v < a[j]) {
+ a[j + 1] = a[j];
+ }
+ a[j + 1] = v;
+ }
+ }
+ // Scan the remaining data and insert
+ // Mitigate worst case performance on descending data by backward sweep
+ double m = a[k];
+ for (int i = right + 1; --i > k;) {
+ final double v = a[i];
+ if (v < m) {
+ a[i] = m;
+ int j = k;
+ while (--j >= left && v < a[j]) {
+ a[j + 1] = a[j];
+ }
+ a[j + 1] = v;
+ m = a[k];
+ }
+ }
+ }
+
+ /**
+ * Partition the maximum {@code n} elements above {@code k} where
+ * {@code n = right - k + 1}. Uses an insertion sort algorithm.
+ *
+ * Works with any {@code k} in the range {@code left <= k <= right}
+ * and can be used to perform a full sort of the range above {@code k}.
+ *
+ * For best performance this should be called with
+ * {@code k - left > right - k}, i.e.
+ * to partition a value in the upper half of the range.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param k Index to select.
+ */
+ static void sortSelectRight(double[] a, int left, int right, int k) {
+ // Sort
+ for (int i = right; --i >= k;) {
+ final double v = a[i];
+ // Move succeeding lower elements below (if required)
+ if (v > a[i + 1]) {
+ int j = i;
+ while (++j <= right && v > a[j]) {
+ a[j - 1] = a[j];
+ }
+ a[j - 1] = v;
+ }
+ }
+ // Scan the remaining data and insert
+ // Mitigate worst case performance on descending data by backward sweep
+ double m = a[k];
+ for (int i = left - 1; ++i < k;) {
+ final double v = a[i];
+ if (v > m) {
+ a[i] = m;
+ int j = k;
+ while (++j <= right && v > a[j]) {
+ a[j - 1] = a[j];
+ }
+ a[j - 1] = v;
+ m = a[k];
+ }
+ }
+ }
+
+ /**
+ * Partition the elements between {@code ka} and {@code kb} using a sort select
+ * algorithm. It is assumed {@code left <= ka <= kb <= right}.
+ *
+ * Differs from {@link #sortSelectRange(double[], int, int, int, int)} by using
+ * a pointer to a position in the sorted array to skip ahead during insertion.
+ * This extra complexity does not improve performance.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ */
+ static void sortSelectRange2(double[] a, int left, int right, int ka, int kb) {
+ // Combine the test for right <= left with
+ // avoiding the overhead of sort select on tiny data.
+ if (right - left <= MIN_SORTSELECT_SIZE) {
+ Sorting.sort(a, left, right);
+ return;
+ }
+ // Sort the smallest side
+ if (kb - left < right - ka) {
+ sortSelectLeft2(a, left, right, kb);
+ } else {
+ sortSelectRight2(a, left, right, ka);
+ }
+ }
+
+ /**
+ * Partition the minimum {@code n} elements below {@code k} where
+ * {@code n = k - left + 1}. Uses an insertion sort algorithm.
+ *
+ * Works with any {@code k} in the range {@code left <= k <= right}
+ * and performs a full sort of the range below {@code k}.
+ *
+ * For best performance this should be called with
+ * {@code k - left < right - k}, i.e.
+ * to partition a value in the lower half of the range.
+ *
+ * Differs from {@link #sortSelectLeft(double[], int, int, int)} by using
+ * a pointer to a position in the sorted array to skip ahead during insertion.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param k Index to select.
+ */
+ static void sortSelectLeft2(double[] a, int left, int right, int k) {
+ // Sort
+ for (int i = left; ++i <= k;) {
+ final double v = a[i];
+ // Move preceding higher elements above (if required)
+ if (v < a[i - 1]) {
+ int j = i;
+ while (--j >= left && v < a[j]) {
+ a[j + 1] = a[j];
+ }
+ a[j + 1] = v;
+ }
+ }
+ // Scan the remaining data and insert
+ // Mitigate worst case performance on descending data by backward sweep
+ double m = a[k];
+ // Pointer to a position in the sorted array
+ final int p = (left + k) >>> 1;
+ for (int i = right + 1; --i > k;) {
+ final double v = a[i];
+ if (v < m) {
+ a[i] = m;
+ int j = k;
+ if (v < a[p]) {
+ // Skip ahead
+ //System.arraycopy(a, p, a, p + 1, k - p);
+ while (j > p) {
+ // left index is evaluated before right decrement
+ a[j] = a[--j];
+ }
+ // j == p
+ while (--j >= left && v < a[j]) {
+ a[j + 1] = a[j];
+ }
+ } else {
+ // No bounds check on left: a[p] <= v < a[k]
+ while (v < a[--j]) {
+ a[j + 1] = a[j];
+ }
+ }
+ a[j + 1] = v;
+ m = a[k];
+ }
+ }
+ }
+
+ /**
+ * Partition the maximum {@code n} elements above {@code k} where
+ * {@code n = right - k + 1}. Uses an insertion sort algorithm.
+ *
+ * Works with any {@code k} in the range {@code left <= k <= right}
+ * and can be used to perform a full sort of the range above {@code k}.
+ *
+ * For best performance this should be called with
+ * {@code k - left > right - k}, i.e.
+ * to partition a value in the upper half of the range.
+ *
+ * Differs from {@link #sortSelectRight(double[], int, int, int)} by using
+ * a pointer to a position in the sorted array to skip ahead during insertion.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param k Index to select.
+ */
+ static void sortSelectRight2(double[] a, int left, int right, int k) {
+ // Sort
+ for (int i = right; --i >= k;) {
+ final double v = a[i];
+ // Move succeeding lower elements below (if required)
+ if (v > a[i + 1]) {
+ int j = i;
+ while (++j <= right && v > a[j]) {
+ a[j - 1] = a[j];
+ }
+ a[j - 1] = v;
+ }
+ }
+ // Scan the remaining data and insert
+ // Mitigate worst case performance on descending data by backward sweep
+ double m = a[k];
+ // Pointer to a position in the sorted array
+ final int p = (right + k) >>> 1;
+ for (int i = left - 1; ++i < k;) {
+ final double v = a[i];
+ if (v > m) {
+ a[i] = m;
+ int j = k;
+ if (v > a[p]) {
+ // Skip ahead
+ //System.arraycopy(a, p, a, p - 1, p - k);
+ while (j < p) {
+ // left index is evaluated before right increment
+ a[j] = a[++j];
+ }
+ // j == p
+ while (++j <= right && v > a[j]) {
+ a[j - 1] = a[j];
+ }
+ } else {
+ // No bounds check on right: a[k] < v <= a[p]
+ while (v > a[++j]) {
+ a[j - 1] = a[j];
+ }
+ }
+ a[j - 1] = v;
+ m = a[k];
+ }
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * Note: This is the only quickselect method in this class not based on introselect.
+ * This is a legacy method containing alternatives for iterating over
+ * multiple keys that are not supported by introselect, namely:
+ *
+ * Note: In each method indices are processed independently. Thus each bracket around an
+ * index to partition does not know the number of recursion steps used to obtain the start
+ * pivots defining the bracket. Excess recursion cannot be efficiently tracked for each
+ * partition. This is unlike introselect which tracks recursion and can switch to algorithm
+ * if quickselect convergence is slow.
+ *
+ * Benchmarking can be used to show these alternatives are slower.
+ *
+ * @param part Partition function.
+ * @param data Values.
+ * @param right Upper bound of data (inclusive).
+ * @param k Indices (may be destructively modified).
+ * @param count Count of indices.
+ */
+ private void partition(PartitionFunction part, double[] data, int right, int[] k, int count) {
+ if (count < 1 || right < 1) {
+ return;
+ }
+ // Validate indices. Excludes indices > right.
+ final int n = countIndices(k, count, right);
+ if (n < 1) {
+ return;
+ }
+ if (n == 1) {
+ part.partition(data, 0, right, k[0], k[0], false, false);
+ } else if (n == 2 && Math.abs(k[0] - k[1]) <= (minQuickSelectSize >>> 1)) {
+ final int ka = Math.min(k[0], k[1]);
+ final int kb = Math.max(k[0], k[1]);
+ part.partition(data, 0, right, ka, kb, false, false);
+ } else {
+ // Allow non-sequential / sequential processing to be selected
+ if (keyStrategy == KeyStrategy.SEQUENTIAL) {
+ // Sequential processing
+ final ScanningPivotCache pivots = keyAnalysis(right + 1, k, n, minQuickSelectSize >>> 1);
+ if (k[0] == Integer.MIN_VALUE) {
+ // Full-sort recommended. Assume the partition function
+ // can choose to switch to using Arrays.sort.
+ part.sort(data, 0, right, false, false);
+ } else {
+ partitionSequential(part, data, k, n, right, pivots);
+ }
+ } else if (keyStrategy == KeyStrategy.INDEX_SET) {
+ // Non-sequential processing using non-optimised storage
+ final IndexSet pivots = IndexSet.ofRange(0, right);
+ // First index must partition the entire range
+ part.partition(data, 0, right, k[0], k[0], false, false, pivots);
+ for (int i = 1; i < n; i++) {
+ final int ki = k[i];
+ if (pivots.get(ki)) {
+ continue;
+ }
+ final int l = pivots.previousSetBit(ki);
+ int r = pivots.nextSetBit(ki);
+ if (r < 0) {
+ r = right + 1;
+ }
+ part.partition(data, l + 1, r - 1, ki, ki, l >= 0, r <= right, pivots);
+ }
+ } else if (keyStrategy == KeyStrategy.PIVOT_CACHE) {
+ // Non-sequential processing using a pivot cache to optimise storage
+ final PivotCache pivots = createPivotCacheForIndices(k, n);
+
+ // Handle single-point or tiny range
+ if ((pivots.right() - pivots.left()) <= (minQuickSelectSize >>> 1)) {
+ part.partition(data, 0, right, pivots.left(), pivots.right(), false, false);
+ return;
+ }
+
+ // Bracket the range so the rest is internal.
+ // Note: Partition function handles min/max searching if ka/kb are
+ // at the end of the range.
+ final int ka = pivots.left();
+ part.partition(data, 0, right, ka, ka, false, false, pivots);
+ final int kb = pivots.right();
+ int l = pivots.previousPivot(kb);
+ int r = pivots.nextPivot(kb);
+ if (r < 0) {
+ // Partition did not visit downstream
+ r = right + 1;
+ }
+ part.partition(data, l + 1, r - 1, kb, kb, true, r <= right, pivots);
+ for (int i = 0; i < n; i++) {
+ final int ki = k[i];
+ if (pivots.contains(ki)) {
+ continue;
+ }
+ l = pivots.previousPivot(ki);
+ r = pivots.nextPivot(ki);
+ part.partition(data, l + 1, r - 1, ki, ki, true, true, pivots);
+ }
+ } else {
+ throw new IllegalStateException("Unsupported: " + keyStrategy);
+ }
+ }
+ }
+
+ /**
+ * Return a {@link PivotCache} implementation to support the range
+ * {@code [left, right]} as defined by minimum and maximum index.
+ *
+ * @param indices Indices.
+ * @param n Count of indices (must be strictly positive).
+ * @return the pivot cache
+ */
+ private static PivotCache createPivotCacheForIndices(int[] indices, int n) {
+ int min = indices[0];
+ int max = min;
+ for (int i = 1; i < n; i++) {
+ final int k = indices[i];
+ min = Math.min(min, k);
+ max = Math.max(max, k);
+ }
+ return PivotCaches.ofFullRange(min, max);
+ }
+
+ /**
+ * Analysis of keys to partition. The indices k are updated in-place. The keys are
+ * processed to eliminate duplicates and sorted in ascending order. Close points are
+ * joined into ranges using the minimum separation. A zero or negative separation
+ * prevents creating ranges.
+ *
+ * On output the indices contain ranges or single points to partition in ascending
+ * order. Single points are identified as negative values and should be bit-flipped
+ * to the index value.
+ *
+ * If compression occurs the result will contain fewer indices than {@code n}.
+ * The end of the compressed range is marked using {@link Integer#MIN_VALUE}. This
+ * is outside the valid range for any single index and signals to stop processing
+ * the ordered indices.
+ *
+ * A {@link PivotCache} implementation is returned for optimal bracketing
+ * of indices in the range after the first target range / point.
+ *
+ * Examples:
+ *
+ * The length of data to partition can be used to determine if processing is
+ * required. A full sort of the data is recommended by returning
+ * {@code k[0] == Integer.MIN_VALUE}. This occurs if the length is sufficiently small
+ * or the first range to partition covers the entire data.
+ *
+ * Note: The signal marker {@code Integer.MIN_VALUE} is {@code Integer.MAX_VALUE}
+ * bit flipped. It this is outside the range of any valid index into an array.
+ *
+ * @param size Length of the data to partition.
+ * @param k Indices.
+ * @param n Count of indices (must be strictly positive).
+ * @param minSeparation Minimum separation between points (set to zero to disable ranges).
+ * @return the pivot cache
+ */
+ // package-private for testing
+ ScanningPivotCache keyAnalysis(int size, int[] k, int n, int minSeparation) {
+ // Tiny data, signal to sort it
+ if (size < minQuickSelectSize) {
+ k[0] = Integer.MIN_VALUE;
+ return null;
+ }
+ // Sort the keys
+ final IndexSet indices = Sorting.sortUnique(Math.max(6, minQuickSelectSize), k, n);
+ // Find the max index
+ int right = k[n - 1];
+ if (right < 0) {
+ right = ~right;
+ }
+ // Join up close keys using the min separation distance.
+ final int left = compressRange(k, n, minSeparation);
+ if (left < 0) {
+ // Nothing to partition after the first target.
+ // Recommend full sort if the range is effectively complete.
+ // A range requires n > 1 and positive indices.
+ if (n != 1 && k[0] >= 0 && size - (k[1] - k[0]) < minQuickSelectSize) {
+ k[0] = Integer.MIN_VALUE;
+ }
+ return null;
+ }
+ // Return an optimal PivotCache to process keys in sorted order
+ if (indices != null) {
+ // Reuse storage from sorting large number of indices
+ return indices.asScanningPivotCache(left, right);
+ }
+ return IndexSet.createScanningPivotCache(left, right);
+ }
+
+ /**
+ * Compress sorted indices into ranges using the minimum separation.
+ * Single points are identified by bit flipping to negative. The
+ * first unused position after compression is set to {@link Integer#MIN_VALUE},
+ * unless this is outside the array length (i.e. no compression).
+ *
+ * @param k Unique indices (sorted).
+ * @param n Count of indices (must be strictly positive).
+ * @param minSeparation Minimum separation between points.
+ * @return the first index after the initial pair / point (or -1)
+ */
+ private static int compressRange(int[] k, int n, int minSeparation) {
+ if (n == 1) {
+ // Single point, mark the first unused position
+ if (k.length > 1) {
+ k[1] = Integer.MIN_VALUE;
+ }
+ return -1;
+ }
+ // Start of range is in k[j]; end in p2
+ int j = 0;
+ int p2 = k[0];
+ int secondTarget = -1;
+ for (int i = 0; ++i < n;) {
+ if (k[i] < 0) {
+ // Start of duplicate indices
+ break;
+ }
+ if (k[i] <= p2 + minSeparation) {
+ // Extend range
+ p2 = k[i];
+ } else {
+ // Store range or point (bit flipped)
+ if (k[j] == p2) {
+ k[j] = ~p2;
+ } else {
+ k[++j] = p2;
+ }
+ j++;
+ // Next range is k[j] to p2
+ k[j] = p2 = k[i];
+ // Set the position of the second target
+ if (secondTarget < 0) {
+ secondTarget = p2;
+ }
+ }
+ }
+ // Store range or point (bit flipped)
+ // Note: If there is only 1 range then the second target is -1
+ if (k[j] == p2) {
+ k[j] = ~p2;
+ } else {
+ k[++j] = p2;
+ }
+ j++;
+ // Add a marker at the end of the compressed indices
+ if (k.length > j) {
+ k[j] = Integer.MIN_VALUE;
+ }
+ return secondTarget;
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * The keys must have been pre-processed by {@link #keyAnalysis(int, int[], int, int)}
+ * to structure them for sequential processing.
+ *
+ * @param part Partition function.
+ * @param data Values.
+ * @param k Indices (created by key analysis).
+ * @param n Count of indices.
+ * @param right Upper bound (inclusive).
+ * @param pivots Cache of pivots (created by key analysis).
+ */
+ private static void partitionSequential(PartitionFunction part, double[] data, int[] k, int n,
+ int right, ScanningPivotCache pivots) {
+ // Sequential processing of [s, s] single points / [s, e] pairs (regions).
+ // Single-points are identified as negative indices.
+ // The partition algorithm must run so each [s, e] is sorted:
+ // lower---se----------------s---e---------upper
+ // Pivots are stored to allow lower / upper to be set for the next region:
+ // lower---se-------p--------s-p-e-----p---upper
+ int i = 1;
+ int s = k[0];
+ int e;
+ if (s < 0) {
+ e = s = ~s;
+ } else {
+ e = k[i++];
+ }
+
+ // Key analysis has configured the pivot cache correctly for the first region.
+ // If there is no cache, there is only 1 region.
+ if (pivots == null) {
+ part.partition(data, 0, right, s, e, false, false);
+ return;
+ }
+
+ part.partitionSequential(data, 0, right, s, e, false, false, pivots);
+
+ // Process remaining regions
+ while (i < n) {
+ s = k[i++];
+ if (s < 0) {
+ e = s = ~s;
+ } else {
+ e = k[i++];
+ }
+ if (s > right) {
+ // End of indices
+ break;
+ }
+ // Cases:
+ // 1. l------s-----------r Single point (s==e)
+ // 2. l------se----------r An adjacent pair of points
+ // 3. l------s------e----r A range of points (may contain internal pivots)
+ // Find bounding region of range: [l, r)
+ // Left (inclusive) is always above 0 as we have partitioned upstream already.
+ // Right (exclusive) may not have been searched yet so we check right bounds.
+ final int l = pivots.previousPivot(s);
+ final int r = pivots.nextPivotOrElse(e, right + 1);
+
+ // Create regions:
+ // Partition: l------s--p1
+ // Sort: p1-----p2
+ // Partition: p2-----e-----r
+ // Look for internal pivots.
+ int p1 = -1;
+ int p2 = -1;
+ if (e - s > 1) {
+ final int p = pivots.nextPivot(s + 1);
+ if (p > s && p < e) {
+ p1 = p;
+ p2 = pivots.previousPivot(e - 1);
+ if (p2 - p1 > SORT_BETWEEN_SIZE) {
+ // Special-case: multiple internal pivots
+ // Full-sort of (p1, p2). Walk the unsorted regions:
+ // l------s--p1 p2----e-----r
+ // ppppp-----pppp----pppp---------
+ // s1-e1 s1e1 s1-----e1
+ int e1 = pivots.previousNonPivot(p2);
+ while (p1 < e1) {
+ final int s1 = pivots.previousPivot(e1);
+ part.sort(data, s1 + 1, e1, true, true);
+ e1 = pivots.previousNonPivot(s1);
+ }
+ }
+ }
+ }
+
+ // Pivots are only required for the next downstream region
+ int sn = right + 1;
+ if (i < n) {
+ sn = k[i];
+ if (sn < 0) {
+ sn = ~sn;
+ }
+ }
+ // Current implementations will signal if this is outside the support.
+ // Occurs on the last region the cache was created to support (i.e. sn > right).
+ final boolean unsupportedCacheRange = !pivots.moveLeft(sn);
+
+ // Note: The partition function uses inclusive left and right bounds
+ // so use +/- 1 from pivot values. If r is not a pivot it is right + 1
+ // which is a valid exclusive upper bound.
+
+ if (p1 > s) {
+ // At least 1 internal pivot:
+ // l <= s < p1 and p2 < e <= r
+ // If l == s or r == e these calls should fully sort the respective range
+ part.partition(data, l + 1, p1 - 1, s, p1 - 1, true, p1 <= right);
+ if (unsupportedCacheRange) {
+ part.partition(data, p2 + 1, r - 1, p2 + 1, e, true, r <= right);
+ } else {
+ part.partitionSequential(data, p2 + 1, r - 1, p2 + 1, e, true, r <= right, pivots);
+ }
+ } else {
+ // Single range
+ if (unsupportedCacheRange) {
+ part.partition(data, l + 1, r - 1, s, e, true, r <= right);
+ } else {
+ part.partitionSequential(data, l + 1, r - 1, s, e, true, r <= right, pivots);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sort the data.
+ *
+ * Uses a Bentley-McIlroy quicksort partition method. Signed zeros
+ * are corrected when encountered during processing.
+ *
+ * @param data Values.
+ */
+ void sortSBM(double[] data) {
+ // Handle NaN
+ final int right = sortNaN(data);
+ sort((SPEPartitionFunction) this::partitionSBMWithZeros, data, right);
+ }
+
+ /**
+ * Sort the data by recursive partitioning (quicksort).
+ *
+ * @param part Partition function.
+ * @param data Values.
+ * @param right Upper bound (inclusive).
+ */
+ private static void sort(PartitionFunction part, double[] data, int right) {
+ if (right < 1) {
+ return;
+ }
+ // Signal entire range
+ part.sort(data, 0, right, false, false);
+ }
+
+ /**
+ * Sort the data using an introsort.
+ *
+ * Uses the configured single-pivot partition method; falling back
+ * to heapsort when quicksort recursion is slow.
+ *
+ * @param data Values.
+ */
+ void sortISP(double[] data) {
+ // NaN processing is done in the introsort method
+ introsort(getSPFunction(), data);
+ }
+
+ /**
+ * Sort the array using an introsort. The single-pivot partition method is provided as an argument.
+ * Switches to heapsort when recursive partitioning reaches a maximum depth.
+ *
+ * The partition method is not required to handle signed zeros.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @see Introsort (Wikipedia)
+ */
+ private void introsort(SPEPartition part, double[] a) {
+ // Handle NaN / signed zeros
+ final DoubleDataTransformer t = SORT_TRANSFORMER.get();
+ // Assume this is in-place
+ t.preProcess(a);
+ final int end = t.length();
+ if (end > 1) {
+ introsort(part, a, 0, end - 1, createMaxDepthSinglePivot(end));
+ }
+ // Restore signed zeros
+ t.postProcess(a);
+ }
+
+ /**
+ * Sort the array.
+ *
+ * Uses an introsort. The single-pivot partition method is provided as an argument.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param maxDepth Maximum depth for recursion.
+ * @see Introsort (Wikipedia)
+ */
+ private void introsort(SPEPartition part, double[] a, int left, int right, int maxDepth) {
+ // Only one side requires recursion. The other side
+ // can remain within this function call.
+ final int l = left;
+ int r = right;
+ final int[] upper = {0};
+ while (true) {
+ // Full sort of small data
+ if (r - l < minQuickSelectSize) {
+ Sorting.sort(a, l, r);
+ return;
+ }
+ if (maxDepth == 0) {
+ // Too much recursion
+ heapSort(a, l, r);
+ return;
+ }
+
+ // Pick a pivot and partition
+ final int p0 = part.partition(a, l, r,
+ pivotingStrategy.pivotIndex(a, l, r, l),
+ upper);
+ final int p1 = upper[0];
+
+ // Recurse right side
+ introsort(part, a, p1 + 1, r, --maxDepth);
+ // Continue on the left side
+ r = p0 - 1;
+ }
+ }
+
+ /**
+ * Sort the data using an introsort.
+ *
+ * Uses a dual-pivot quicksort method; falling back
+ * to heapsort when quicksort recursion is slow.
+ *
+ * @param data Values.
+ */
+ void sortIDP(double[] data) {
+ // NaN processing is done in the introsort method
+ introsort((DPPartition) Partition::partitionDP, data);
+ }
+
+ /**
+ * Sort the array using an introsort. The dual-pivot partition method is provided as an argument.
+ * Switches to heapsort when recursive partitioning reaches a maximum depth.
+ *
+ * The partition method is not required to handle signed zeros.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @see Introsort (Wikipedia)
+ */
+ private void introsort(DPPartition part, double[] a) {
+ // Handle NaN / signed zeros
+ final DoubleDataTransformer t = SORT_TRANSFORMER.get();
+ // Assume this is in-place
+ t.preProcess(a);
+ final int end = t.length();
+ if (end > 1) {
+ introsort(part, a, 0, end - 1, createMaxDepthDualPivot(end));
+ }
+ // Restore signed zeros
+ t.postProcess(a);
+ }
+
+ /**
+ * Sort the array.
+ *
+ * Uses an introsort. The dual-pivot partition method is provided as an argument.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param maxDepth Maximum depth for recursion.
+ * @see Introsort (Wikipedia)
+ */
+ private void introsort(DPPartition part, double[] a, int left, int right, int maxDepth) {
+ // Only two regions require recursion. The third region
+ // can remain within this function call.
+ final int l = left;
+ int r = right;
+ final int[] upper = {0, 0, 0};
+ while (true) {
+ // Full sort of small data
+ if (r - l < minQuickSelectSize) {
+ Sorting.sort(a, l, r);
+ return;
+ }
+ if (maxDepth == 0) {
+ // Too much recursion
+ heapSort(a, l, r);
+ return;
+ }
+
+ // Pick 2 pivots and partition
+ int p0 = dualPivotingStrategy.pivotIndex(a, l, r, upper);
+ p0 = part.partition(a, l, r, p0, upper[0], upper);
+ final int p1 = upper[0];
+ final int p2 = upper[1];
+ final int p3 = upper[2];
+
+ // Recurse middle and right sides
+ --maxDepth;
+ introsort(part, a, p3 + 1, r, maxDepth);
+ introsort(part, a, p1 + 1, p2 - 1, maxDepth);
+ // Continue on the left side
+ r = p0 - 1;
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * All indices are assumed to be within {@code [0, right]}.
+ *
+ * Uses an introselect variant. The single-pivot quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is controlled by
+ * current configuration.
+ *
+ * The partition method is not required to handle signed zeros.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param k Indices (may be destructively modified).
+ * @param count Count of indices (assumed to be strictly positive).
+ */
+ private void introselect(SPEPartition part, double[] a, int[] k, int count) {
+ // Handle NaN / signed zeros
+ final DoubleDataTransformer t = SORT_TRANSFORMER.get();
+ // Assume this is in-place
+ t.preProcess(a);
+ final int end = t.length();
+ int n = count;
+ if (end > 1) {
+ // Filter indices invalidated by NaN check
+ if (end < a.length) {
+ for (int i = n; --i >= 0;) {
+ final int v = k[i];
+ if (v >= end) {
+ // swap(k, i, --n)
+ k[i] = k[--n];
+ k[n] = v;
+ }
+ }
+ }
+ introselect(part, a, end - 1, k, n);
+ }
+ // Restore signed zeros
+ t.postProcess(a, k, n);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * All indices are assumed to be within {@code [0, right]}.
+ *
+ * Uses an introselect variant. The quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is controlled by
+ * current configuration.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Indices (may be destructively modified).
+ * @param n Count of indices (assumed to be strictly positive).
+ */
+ private void introselect(SPEPartition part, double[] a, int right, int[] k, int n) {
+ if (n < 1) {
+ return;
+ }
+ final int maxDepth = createMaxDepthSinglePivot(right + 1);
+ // Handle cases without multiple keys
+ if (n == 1) {
+ // Dedicated methods for a single key. These use different strategies
+ // to trigger the stopper on quickselect recursion
+ if (pairedKeyStrategy == PairedKeyStrategy.PAIRED_KEYS) {
+ introselect(part, a, 0, right, k[0], maxDepth);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.PAIRED_KEYS_2) {
+ // This uses the configured recursion constant c.
+ // The length must halve every c iterations.
+ introselect2(part, a, 0, right, k[0]);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.PAIRED_KEYS_LEN) {
+ introselect(part, a, 0, right, k[0]);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.TWO_KEYS) {
+ // Dedicated method for two separate keys using the same key
+ introselect(part, a, 0, right, k[0], k[0], maxDepth);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.KEY_RANGE) {
+ // Dedicated method for a range of keys using the same key
+ introselect2(part, a, 0, right, k[0], k[0]);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.SEARCHABLE_INTERVAL) {
+ // Reuse the SearchableInterval method using the same key
+ introselect(part, a, 0, right, IndexIntervals.anyIndex(), k[0], k[0], maxDepth);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.UPDATING_INTERVAL) {
+ // Reuse the UpdatingInterval method using a single key
+ introselect(part, a, 0, right, IndexIntervals.interval(k[0]), maxDepth);
+ } else {
+ throw new IllegalStateException(UNSUPPORTED_INTROSELECT + pairedKeyStrategy);
+ }
+ return;
+ }
+ // Special case for partition around adjacent indices (for interpolation)
+ if (n == 2 && k[0] + 1 == k[1]) {
+ // Dedicated method for a single key, returns information about k+1
+ if (pairedKeyStrategy == PairedKeyStrategy.PAIRED_KEYS) {
+ final int p = introselect(part, a, 0, right, k[0], maxDepth);
+ // p <= k to signal k+1 is unsorted, or p+1 is a pivot.
+ // if k is sorted, and p+1 is sorted, k+1 is sorted if k+1 == p.
+ if (p > k[1]) {
+ selectMinIgnoreZeros(a, k[1], p);
+ }
+ } else if (pairedKeyStrategy == PairedKeyStrategy.PAIRED_KEYS_2) {
+ final int p = introselect2(part, a, 0, right, k[0]);
+ if (p > k[1]) {
+ selectMinIgnoreZeros(a, k[1], p);
+ }
+ } else if (pairedKeyStrategy == PairedKeyStrategy.PAIRED_KEYS_LEN) {
+ final int p = introselect(part, a, 0, right, k[0]);
+ if (p > k[1]) {
+ selectMinIgnoreZeros(a, k[1], p);
+ }
+ } else if (pairedKeyStrategy == PairedKeyStrategy.TWO_KEYS) {
+ // Dedicated method for two separate keys
+ // Note: This can handle keys that are not adjacent
+ // e.g. keys near opposite ends without a partition step.
+ final int ka = Math.min(k[0], k[1]);
+ final int kb = Math.max(k[0], k[1]);
+ introselect(part, a, 0, right, ka, kb, maxDepth);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.KEY_RANGE) {
+ // Dedicated method for a range of keys using the same key
+ final int ka = Math.min(k[0], k[1]);
+ final int kb = Math.max(k[0], k[1]);
+ introselect2(part, a, 0, right, ka, kb);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.SEARCHABLE_INTERVAL) {
+ // Reuse the SearchableInterval method using a range of two keys
+ introselect(part, a, 0, right, IndexIntervals.anyIndex(), k[0], k[1], maxDepth);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.UPDATING_INTERVAL) {
+ // Reuse the UpdatingInterval method using a range of two keys
+ introselect(part, a, 0, right, IndexIntervals.interval(k[0], k[1]), maxDepth);
+ } else {
+ throw new IllegalStateException(UNSUPPORTED_INTROSELECT + pairedKeyStrategy);
+ }
+ return;
+ }
+
+ // Note: Sorting to unique keys is an overhead. This can be eliminated
+ // by requesting the caller passes sorted keys.
+
+ // Note: Attempts to perform key analysis here to detect a full sort
+ // add an overhead for sparse keys and do not increase performance
+ // for saturated keys unless data is structured with ascending/descending
+ // runs so that it is fast with JDK's merge sort algorithm in Arrays.sort.
+
+ if (keyStrategy == KeyStrategy.ORDERED_KEYS) {
+ final int unique = Sorting.sortIndices(k, n);
+ introselect(part, a, 0, right, k, 0, unique - 1, maxDepth);
+ } else if (keyStrategy == KeyStrategy.SCANNING_KEY_SEARCHABLE_INTERVAL) {
+ final int unique = Sorting.sortIndices(k, n);
+ final SearchableInterval keys = ScanningKeyInterval.of(k, unique);
+ introselect(part, a, 0, right, keys, keys.left(), keys.right(), maxDepth);
+ } else if (keyStrategy == KeyStrategy.SEARCH_KEY_SEARCHABLE_INTERVAL) {
+ final int unique = Sorting.sortIndices(k, n);
+ final SearchableInterval keys = BinarySearchKeyInterval.of(k, unique);
+ introselect(part, a, 0, right, keys, keys.left(), keys.right(), maxDepth);
+ } else if (keyStrategy == KeyStrategy.COMPRESSED_INDEX_SET) {
+ final SearchableInterval keys = CompressedIndexSet.of(compression, k, n);
+ introselect(part, a, 0, right, keys, keys.left(), keys.right(), maxDepth);
+ } else if (keyStrategy == KeyStrategy.INDEX_SET) {
+ final SearchableInterval keys = IndexSet.of(k, n);
+ introselect(part, a, 0, right, keys, keys.left(), keys.right(), maxDepth);
+ } else if (keyStrategy == KeyStrategy.KEY_UPDATING_INTERVAL) {
+ final int unique = Sorting.sortIndices(k, n);
+ final UpdatingInterval keys = KeyUpdatingInterval.of(k, unique);
+ introselect(part, a, 0, right, keys, maxDepth);
+ } else if (keyStrategy == KeyStrategy.INDEX_SET_UPDATING_INTERVAL) {
+ final UpdatingInterval keys = BitIndexUpdatingInterval.of(k, n);
+ introselect(part, a, 0, right, keys, maxDepth);
+ } else if (keyStrategy == KeyStrategy.KEY_SPLITTING_INTERVAL) {
+ final int unique = Sorting.sortIndices(k, n);
+ final SplittingInterval keys = KeyUpdatingInterval.of(k, unique);
+ introselect(part, a, 0, right, keys, maxDepth);
+ } else if (keyStrategy == KeyStrategy.INDEX_SET_SPLITTING_INTERVAL) {
+ final SplittingInterval keys = BitIndexUpdatingInterval.of(k, n);
+ introselect(part, a, 0, right, keys, maxDepth);
+ } else if (keyStrategy == KeyStrategy.INDEX_ITERATOR) {
+ final int unique = Sorting.sortIndices(k, n);
+ final IndexIterator keys = KeyIndexIterator.of(k, unique);
+ introselect(part, a, 0, right, keys, keys.left(), keys.right(), maxDepth);
+ } else if (keyStrategy == KeyStrategy.COMPRESSED_INDEX_ITERATOR) {
+ final IndexIterator keys = CompressedIndexSet.iterator(compression, k, n);
+ introselect(part, a, 0, right, keys, keys.left(), keys.right(), maxDepth);
+ } else {
+ throw new IllegalStateException(UNSUPPORTED_INTROSELECT + keyStrategy);
+ }
+ }
+
+ /**
+ * Partition the array such that index {@code k} corresponds to its
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * Uses an introselect variant. The quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is controlled by
+ * current configuration.
+ *
+ * Returns information {@code p} on whether {@code k+1} is sorted.
+ * If {@code p <= k} then {@code k+1} is sorted.
+ * If {@code p > k} then {@code p+1} is a pivot.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Index.
+ * @param maxDepth Maximum depth for recursion.
+ * @return the index {@code p}
+ */
+ private int introselect(SPEPartition part, double[] a, int left, int right,
+ int k, int maxDepth) {
+ int l = left;
+ int r = right;
+ final int[] upper = {0};
+ while (true) {
+ // It is possible to use edgeselect when k is close to the end
+ // |l|-----|k|---------|k|--------|r|
+ // ---d1----
+ // -----d2----
+ final int d1 = k - l;
+ final int d2 = r - k;
+ if (Math.min(d1, d2) < edgeSelectConstant) {
+ edgeSelection.partition(a, l, r, k, k);
+ // Last known unsorted value >= k
+ return r;
+ }
+
+ if (maxDepth == 0) {
+ // Too much recursion
+ // Note: For testing the Floyd-Rivest algorithm we trigger the recursion
+ // consumer as a signal that FR failed due to a non-representative sample.
+ recursionConsumer.accept(maxDepth);
+ stopperSelection.partition(a, l, r, k, k);
+ // Last known unsorted value >= k
+ return r;
+ }
+
+ // Pick a pivot and partition
+ int pivot;
+ // length - 1
+ int n = r - l;
+ if (n > subSamplingSize) {
+ // Floyd-Rivest: use SELECT recursively on a sample of size S to get an estimate
+ // for the (k-l+1)-th smallest element into a[k], biased slightly so that the
+ // (k-l+1)-th element is expected to lie in the smaller set after partitioning.
+ ++n;
+ final int ith = k - l + 1;
+ final double z = Math.log(n);
+ final double s = 0.5 * Math.exp(0.6666666666666666 * z);
+ final double sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * Integer.signum(ith - (n >> 1));
+ final int ll = Math.max(l, (int) (k - ith * s / n + sd));
+ final int rr = Math.min(r, (int) (k + (n - ith) * s / n + sd));
+ // Optional random sampling
+ if ((controlFlags & FLAG_RANDOM_SAMPLING) != 0) {
+ final IntUnaryOperator rng = createRNG(n, k);
+ // Shuffle [ll, k) from [l, k)
+ if (ll > l) {
+ for (int i = k; i > ll;) {
+ // l + rand [0, i - l + 1) : i is currently i+1
+ final int j = l + rng.applyAsInt(i - l);
+ final double t = a[--i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ // Shuffle (k, rr] from (k, r]
+ if (rr < r) {
+ for (int i = k; i < rr;) {
+ // r - rand [0, r - i + 1) : i is currently i-1
+ final int j = r - rng.applyAsInt(r - i);
+ final double t = a[++i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ }
+ introselect(part, a, ll, rr, k, lnNtoMaxDepthSinglePivot(z));
+ pivot = k;
+ } else {
+ // default pivot strategy
+ pivot = pivotingStrategy.pivotIndex(a, l, r, k);
+ }
+
+ final int p0 = part.partition(a, l, r, pivot, upper);
+ final int p1 = upper[0];
+
+ maxDepth--;
+ if (k < p0) {
+ // The element is in the left partition
+ r = p0 - 1;
+ } else if (k > p1) {
+ // The element is in the right partition
+ l = p1 + 1;
+ } else {
+ // The range contains the element we wanted.
+ // Signal if k+1 is sorted.
+ // This can be true if the pivot was a range [p0, p1]
+ return k < p1 ? k : r;
+ }
+ }
+ }
+
+ /**
+ * Partition the array such that index {@code k} corresponds to its
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * Uses an introselect variant. The quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is controlled by
+ * current configuration.
+ *
+ * Returns information {@code p} on whether {@code k+1} is sorted.
+ * If {@code p <= k} then {@code k+1} is sorted.
+ * If {@code p > k} then {@code p+1} is a pivot.
+ *
+ * Recursion is monitored by checking the partition is reduced by 2-x after
+ * {@code c} iterations where {@code x} is the
+ * {@link #setRecursionConstant(int) recursion constant} and {@code c} is the
+ * {@link #setRecursionMultiple(double) recursion multiple} (variables reused for convenience).
+ * Confidence bounds for dividing a length by 2-x are provided in Valois (2000)
+ * as {@code c = floor((6/5)x) + b}:
+ * Ideally {@code c >= 3} using {@code x = 1}. E.g. We can use 3 iterations to be 76%
+ * confident the sequence will divide in half; or 7 iterations to be 99% confident the
+ * sequence will divide into a quarter. A larger factor {@code b} reduces the sensitivity
+ * of introspection.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Index.
+ * @return the index {@code p}
+ */
+ private int introselect2(SPEPartition part, double[] a, int left, int right, int k) {
+ int l = left;
+ int r = right;
+ final int[] upper = {0};
+ int counter = (int) recursionMultiple;
+ int threshold = (right - left) >>> recursionConstant;
+ int depth = singlePivotMaxDepth(right - left);
+ while (true) {
+ // It is possible to use edgeselect when k is close to the end
+ // |l|-----|k|---------|k|--------|r|
+ // ---d1----
+ // -----d2----
+ final int d1 = k - l;
+ final int d2 = r - k;
+ if (Math.min(d1, d2) < edgeSelectConstant) {
+ edgeSelection.partition(a, l, r, k, k);
+ // Last known unsorted value >= k
+ return r;
+ }
+
+ // length - 1
+ int n = r - l;
+ depth--;
+ if (--counter < 0) {
+ if (n > threshold) {
+ // Did not reduce the length after set number of iterations.
+ // Here riselect (Valois (2000)) would use random points to choose the pivot
+ // to inject entropy and restart. This continues until the sum of the partition
+ // lengths is too high (twice the original length). Here we just switch.
+
+ // Note: For testing we trigger the recursion consumer
+ recursionConsumer.accept(depth);
+ stopperSelection.partition(a, l, r, k, k);
+ // Last known unsorted value >= k
+ return r;
+ }
+ // Once the confidence has been achieved we use (6/5)x with x=1.
+ // So check every 5/6 iterations that the length is halving.
+ if (counter == -5) {
+ counter = 1;
+ }
+ threshold >>>= 1;
+ }
+
+ // Pick a pivot and partition
+ int pivot;
+ if (n > subSamplingSize) {
+ // Floyd-Rivest: use SELECT recursively on a sample of size S to get an estimate
+ // for the (k-l+1)-th smallest element into a[k], biased slightly so that the
+ // (k-l+1)-th element is expected to lie in the smaller set after partitioning.
+ ++n;
+ final int ith = k - l + 1;
+ final double z = Math.log(n);
+ final double s = 0.5 * Math.exp(0.6666666666666666 * z);
+ final double sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * Integer.signum(ith - (n >> 1));
+ final int ll = Math.max(l, (int) (k - ith * s / n + sd));
+ final int rr = Math.min(r, (int) (k + (n - ith) * s / n + sd));
+ // Optional random sampling
+ if ((controlFlags & FLAG_RANDOM_SAMPLING) != 0) {
+ final IntUnaryOperator rng = createRNG(n, k);
+ // Shuffle [ll, k) from [l, k)
+ if (ll > l) {
+ for (int i = k; i > ll;) {
+ // l + rand [0, i - l + 1) : i is currently i+1
+ final int j = l + rng.applyAsInt(i - l);
+ final double t = a[--i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ // Shuffle (k, rr] from (k, r]
+ if (rr < r) {
+ for (int i = k; i < rr;) {
+ // r - rand [0, r - i + 1) : i is currently i-1
+ final int j = r - rng.applyAsInt(r - i);
+ final double t = a[++i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ }
+ // Sample recursion restarts from [ll, rr]
+ introselect2(part, a, ll, rr, k);
+ pivot = k;
+ } else {
+ // default pivot strategy
+ pivot = pivotingStrategy.pivotIndex(a, l, r, k);
+ }
+
+ final int p0 = part.partition(a, l, r, pivot, upper);
+ final int p1 = upper[0];
+
+ if (k < p0) {
+ // The element is in the left partition
+ r = p0 - 1;
+ } else if (k > p1) {
+ // The element is in the right partition
+ l = p1 + 1;
+ } else {
+ // The range contains the element we wanted.
+ // Signal if k+1 is sorted.
+ // This can be true if the pivot was a range [p0, p1]
+ return k < p1 ? k : r;
+ }
+ }
+ }
+
+ /**
+ * Partition the array such that index {@code k} corresponds to its
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * Uses an introselect variant. The quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is controlled by
+ * current configuration.
+ *
+ * Returns information {@code p} on whether {@code k+1} is sorted.
+ * If {@code p <= k} then {@code k+1} is sorted.
+ * If {@code p > k} then {@code p+1} is a pivot.
+ *
+ * Recursion is monitored by checking the sum of partition lengths is less than
+ * {@code m * (r - l)} where {@code m} is the
+ * {@link #setRecursionMultiple(double) recursion multiple}.
+ * Ideally {@code c} should be a value above 1.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Index.
+ * @return the index {@code p}
+ */
+ private int introselect(SPEPartition part, double[] a, int left, int right, int k) {
+ int l = left;
+ int r = right;
+ final int[] upper = {0};
+ // Set the limit on the sum of the length. Since the length is subtracted at the start
+ // of the loop use (1 + recursionMultiple).
+ long limit = (long) ((1 + recursionMultiple) * (right - left));
+ int depth = singlePivotMaxDepth(right - left);
+ while (true) {
+ // It is possible to use edgeselect when k is close to the end
+ // |l|-----|k|---------|k|--------|r|
+ // ---d1----
+ // -----d2----
+ final int d1 = k - l;
+ final int d2 = r - k;
+ if (Math.min(d1, d2) < edgeSelectConstant) {
+ edgeSelection.partition(a, l, r, k, k);
+ // Last known unsorted value >= k
+ return r;
+ }
+
+ // length - 1
+ int n = r - l;
+ limit -= n;
+ depth--;
+
+ if (limit < 0) {
+ // Excess total partition length
+ // Note: For testing we trigger the recursion consumer
+ recursionConsumer.accept(depth);
+ stopperSelection.partition(a, l, r, k, k);
+ // Last known unsorted value >= k
+ return r;
+ }
+
+ // Pick a pivot and partition
+ int pivot;
+ if (n > subSamplingSize) {
+ // Floyd-Rivest: use SELECT recursively on a sample of size S to get an estimate
+ // for the (k-l+1)-th smallest element into a[k], biased slightly so that the
+ // (k-l+1)-th element is expected to lie in the smaller set after partitioning.
+ ++n;
+ final int ith = k - l + 1;
+ final double z = Math.log(n);
+ final double s = 0.5 * Math.exp(0.6666666666666666 * z);
+ final double sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * Integer.signum(ith - (n >> 1));
+ final int ll = Math.max(l, (int) (k - ith * s / n + sd));
+ final int rr = Math.min(r, (int) (k + (n - ith) * s / n + sd));
+ // Optional random sampling
+ if ((controlFlags & FLAG_RANDOM_SAMPLING) != 0) {
+ final IntUnaryOperator rng = createRNG(n, k);
+ // Shuffle [ll, k) from [l, k)
+ if (ll > l) {
+ for (int i = k; i > ll;) {
+ // l + rand [0, i - l + 1) : i is currently i+1
+ final int j = l + rng.applyAsInt(i - l);
+ final double t = a[--i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ // Shuffle (k, rr] from (k, r]
+ if (rr < r) {
+ for (int i = k; i < rr;) {
+ // r - rand [0, r - i + 1) : i is currently i-1
+ final int j = r - rng.applyAsInt(r - i);
+ final double t = a[++i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ }
+ // Sample recursion restarts from [ll, rr]
+ introselect(part, a, ll, rr, k);
+ pivot = k;
+ } else {
+ // default pivot strategy
+ pivot = pivotingStrategy.pivotIndex(a, l, r, k);
+ }
+
+ final int p0 = part.partition(a, l, r, pivot, upper);
+ final int p1 = upper[0];
+
+ if (k < p0) {
+ // The element is in the left partition
+ r = p0 - 1;
+ } else if (k > p1) {
+ // The element is in the right partition
+ l = p1 + 1;
+ } else {
+ // The range contains the element we wanted.
+ // Signal if k+1 is sorted.
+ // This can be true if the pivot was a range [p0, p1]
+ return k < p1 ? k : r;
+ }
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code ka} and {@code kb} correspond to their
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code k} and any index {@code i}:
+ *
+ * Note: Requires {@code ka <= kb}. The use of two indices is to support processing
+ * of pairs of indices {@code (k, k+1)}. However the indices are treated independently
+ * and partitioned by recursion. They may be equal, neighbours or well separated.
+ *
+ * Uses an introselect variant. The quickselect is provided as an argument; the
+ * fall-back on poor convergence of the quickselect is a heapselect.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param ka Index.
+ * @param kb Index.
+ * @param maxDepth Maximum depth for recursion.
+ */
+ private void introselect(SPEPartition part, double[] a, int left, int right,
+ int ka, int kb, int maxDepth) {
+ // Only one side requires recursion. The other side
+ // can remain within this function call.
+ int l = left;
+ int r = right;
+ int ka1 = ka;
+ int kb1 = kb;
+ final int[] upper = {0};
+ while (true) {
+ // length - 1
+ final int n = r - l;
+
+ if (n < minQuickSelectSize) {
+ // Sort selection on small data
+ sortSelectRange(a, l, r, ka1, kb1);
+ return;
+ }
+
+ // It is possible to use heapselect when ka1 and kb1 are close to the ends
+ // |l|-----|ka1|--------|kb1|------|r|
+ // ---d1----
+ // -----d3----
+ // ---------d2-----------
+ // ----------d4-----------
+ final int d1 = ka1 - l;
+ final int d2 = kb1 - l;
+ final int d3 = r - kb1;
+ final int d4 = r - ka1;
+ if (maxDepth == 0 ||
+ Math.min(d1 + d3, Math.min(d2, d4)) < edgeSelectConstant) {
+ // Too much recursion, or ka1 and kb1 are both close to the ends
+ // Note: Does not use the edgeSelection function as the indices are not a range
+ heapSelectPair(a, l, r, ka1, kb1);
+ return;
+ }
+
+ // Pick a pivot and partition
+ final int p0 = part.partition(a, l, r,
+ pivotingStrategy.pivotIndex(a, l, r, ka),
+ upper);
+ final int p1 = upper[0];
+
+ // Recursion to max depth
+ // Note: Here we possibly branch left and right with multiple keys.
+ // It is possible that the partition has split the pair
+ // and the recursion proceeds with a single point.
+ maxDepth--;
+ // Recurse left side if required
+ if (ka1 < p0) {
+ if (kb1 <= p1) {
+ // Entirely on left side
+ r = p0 - 1;
+ kb1 = r < kb1 ? ka1 : kb1;
+ continue;
+ }
+ introselect(part, a, l, p0 - 1, ka1, ka1, maxDepth);
+ ka1 = kb1;
+ }
+ if (kb1 <= p1) {
+ // No right side
+ return;
+ }
+ // Continue on the right side
+ l = p1 + 1;
+ ka1 = ka1 < l ? kb1 : ka1;
+ }
+ }
+
+ /**
+ * Partition the array such that index {@code k} corresponds to its
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code [ka, kb]} and any index {@code i}:
+ *
+ * This function accepts indices {@code [ka, kb]} that define the
+ * range of indices to partition. It is expected that the range is small.
+ *
+ * Uses an introselect variant. The quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is controlled by
+ * current configuration.
+ *
+ * Recursion is monitored by checking the partition is reduced by 2-x after
+ * {@code c} iterations where {@code x} is the
+ * {@link #setRecursionConstant(int) recursion constant} and {@code c} is the
+ * {@link #setRecursionMultiple(double) recursion multiple} (variables reused for convenience).
+ * Confidence bounds for dividing a length by 2-x are provided in Valois (2000)
+ * as {@code c = floor((6/5)x) + b}:
+ * Ideally {@code c >= 3} using {@code x = 1}. E.g. We can use 3 iterations to be 76%
+ * confident the sequence will divide in half; or 7 iterations to be 99% confident the
+ * sequence will divide into a quarter. A larger factor {@code b} reduces the sensitivity
+ * of introspection.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param ka First key of interest.
+ * @param kb Last key of interest.
+ */
+ private void introselect2(SPEPartition part, double[] a, int left, int right, int ka, int kb) {
+ int l = left;
+ int r = right;
+ final int[] upper = {0};
+ int counter = (int) recursionMultiple;
+ int threshold = (right - left) >>> recursionConstant;
+ while (true) {
+ // It is possible to use edgeselect when k is close to the end
+ // |l|-----|ka|kkkkkkkk|kb|------|r|
+ if (Math.min(kb - l, r - ka) < edgeSelectConstant) {
+ edgeSelection.partition(a, l, r, ka, kb);
+ return;
+ }
+
+ // length - 1
+ int n = r - l;
+ if (--counter < 0) {
+ if (n > threshold) {
+ // Did not reduce the length after set number of iterations.
+ // Here riselect (Valois (2000)) would use random points to choose the pivot
+ // to inject entropy and restart. This continues until the sum of the partition
+ // lengths is too high (twice the original length). Here we just switch.
+
+ // Note: For testing we trigger the recursion consumer with the remaining length
+ recursionConsumer.accept(r - l);
+ stopperSelection.partition(a, l, r, ka, kb);
+ return;
+ }
+ // Once the confidence has been achieved we use (6/5)x with x=1.
+ // So check every 5/6 iterations that the length is halving.
+ if (counter == -5) {
+ counter = 1;
+ }
+ threshold >>>= 1;
+ }
+
+ // Pick a pivot and partition
+ int pivot;
+ if (n > subSamplingSize) {
+ // Floyd-Rivest: use SELECT recursively on a sample of size S to get an estimate
+ // for the (k-l+1)-th smallest element into a[k], biased slightly so that the
+ // (k-l+1)-th element is expected to lie in the smaller set after partitioning.
+ ++n;
+ final int ith = ka - l + 1;
+ final double z = Math.log(n);
+ final double s = 0.5 * Math.exp(0.6666666666666666 * z);
+ final double sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * Integer.signum(ith - (n >> 1));
+ final int ll = Math.max(l, (int) (ka - ith * s / n + sd));
+ final int rr = Math.min(r, (int) (ka + (n - ith) * s / n + sd));
+ // Optional random sampling
+ if ((controlFlags & FLAG_RANDOM_SAMPLING) != 0) {
+ final IntUnaryOperator rng = createRNG(n, ka);
+ // Shuffle [ll, k) from [l, k)
+ if (ll > l) {
+ for (int i = ka; i > ll;) {
+ // l + rand [0, i - l + 1) : i is currently i+1
+ final int j = l + rng.applyAsInt(i - l);
+ final double t = a[--i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ // Shuffle (k, rr] from (k, r]
+ if (rr < r) {
+ for (int i = ka; i < rr;) {
+ // r - rand [0, r - i + 1) : i is currently i-1
+ final int j = r - rng.applyAsInt(r - i);
+ final double t = a[++i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ }
+ // Sample recursion restarts from [ll, rr]
+ introselect2(part, a, ll, rr, ka, ka);
+ pivot = ka;
+ } else {
+ // default pivot strategy
+ pivot = pivotingStrategy.pivotIndex(a, l, r, ka);
+ }
+
+ final int p0 = part.partition(a, l, r, pivot, upper);
+ final int p1 = upper[0];
+
+ // Note: Here we expect [ka, kb] to be small and splitting is unlikely.
+ // p0 p1
+ // |l|--|ka|kkkk|kb|--|P|-------------------|r|
+ // |l|----------------|P|--|ka|kkk|kb|------|r|
+ // |l|-----------|ka|k|P|k|kb|--------------|r|
+ if (kb < p0) {
+ // The element is in the left partition
+ r = p0 - 1;
+ } else if (ka > p1) {
+ // The element is in the right partition
+ l = p1 + 1;
+ } else {
+ // Pivot splits [ka, kb]. Expect ends to be close to the pivot and finish.
+ if (ka < p0) {
+ sortSelectRight(a, l, p0 - 1, ka);
+ }
+ if (kb > p1) {
+ sortSelectLeft(a, p1 + 1, r, kb);
+ }
+ return;
+ }
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code k} and any index {@code i}:
+ *
+ * This function accepts an ordered array of indices {@code k} and pointers
+ * to the first and last positions in {@code k} that define the range indices
+ * to partition.
+ *
+ * A binary search is used to search for keys in {@code [ia, ib]}
+ * to create {@code [ia, ib1]} and {@code [ia1, ib]} if partitioning splits the range.
+ *
+ * Uses an introselect variant. The quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is a heapselect.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Indices to partition (ordered).
+ * @param ia Index of first key.
+ * @param ib Index of last key.
+ * @param maxDepth Maximum depth for recursion.
+ */
+ private void introselect(SPEPartition part, double[] a, int left, int right,
+ int[] k, int ia, int ib, int maxDepth) {
+ // Only one side requires recursion. The other side
+ // can remain within this function call.
+ int l = left;
+ int r = right;
+ int ia1 = ia;
+ int ib1 = ib;
+ final int[] upper = {0};
+ while (true) {
+ // Switch to paired key implementation if possible.
+ // Note: adjacent indices can refer to well separated keys.
+ // This is the major difference between this implementation
+ // and an implementation using an IndexInterval (which does not
+ // have a fast way to determine if there are any keys within the range).
+ if (ib1 - ia1 <= 1) {
+ introselect(part, a, l, r, k[ia1], k[ib1], maxDepth);
+ return;
+ }
+
+ // length - 1
+ final int n = r - l;
+ int ka = k[ia1];
+ final int kb = k[ib1];
+
+ if (n < minQuickSelectSize) {
+ // Sort selection on small data
+ sortSelectRange(a, l, r, ka, kb);
+ return;
+ }
+
+ // It is possible to use heapselect when ka and kb are close to the same end
+ // |l|-----|ka|--------|kb|------|r|
+ // ---------s2----------
+ // ----------s4-----------
+ if (Math.min(kb - l, r - ka) < edgeSelectConstant) {
+ edgeSelection.partition(a, l, r, ka, kb);
+ return;
+ }
+
+ if (maxDepth == 0) {
+ // Too much recursion
+ heapSelectRange(a, l, r, ka, kb);
+ return;
+ }
+
+ // Pick a pivot and partition
+ final int p0 = part.partition(a, l, r,
+ pivotingStrategy.pivotIndex(a, l, r, ka),
+ upper);
+ final int p1 = upper[0];
+
+ // Recursion to max depth
+ // Note: Here we possibly branch left and right with multiple keys.
+ // It is possible that the partition has split the keys
+ // and the recursion proceeds with a reduced set on either side.
+ // p0 p1
+ // |l|--|ka|--k----k--|P|------k--|kb|------|r|
+ // ia1 iba | ia1 ib1
+ // Search less/greater is bounded at ia1/ib1
+ maxDepth--;
+ // Recurse left side if required
+ if (ka < p0) {
+ if (kb <= p1) {
+ // Entirely on left side
+ r = p0 - 1;
+ if (r < kb) {
+ ib1 = searchLessOrEqual(k, ia1, ib1, r);
+ }
+ continue;
+ }
+ // Require a split here
+ introselect(part, a, l, p0 - 1, k, ia1, searchLessOrEqual(k, ia1, ib1, p0 - 1), maxDepth);
+ ia1 = searchGreaterOrEqual(k, ia1, ib1, l);
+ ka = k[ia1];
+ }
+ if (kb <= p1) {
+ // No right side
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+ // Continue on the right side
+ l = p1 + 1;
+ if (ka < l) {
+ ia1 = searchGreaterOrEqual(k, ia1, ib1, l);
+ }
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code k} and any index {@code i}:
+ *
+ * This function accepts a {@link SearchableInterval} of indices {@code k} and the
+ * first index {@code ka} and last index {@code kb} that define the range of indices
+ * to partition. The {@link SearchableInterval} is used to search for keys in {@code [ka, kb]}
+ * to create {@code [ka, kb1]} and {@code [ka1, kb]} if partitioning splits the range.
+ *
+ * Uses an introselect variant. The quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is a heapselect.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Interval of indices to partition (ordered).
+ * @param ka First key.
+ * @param kb Last key.
+ * @param maxDepth Maximum depth for recursion.
+ */
+ // package-private for benchmarking
+ void introselect(SPEPartition part, double[] a, int left, int right,
+ SearchableInterval k, int ka, int kb, int maxDepth) {
+ // Only one side requires recursion. The other side
+ // can remain within this function call.
+ int l = left;
+ int r = right;
+ int ka1 = ka;
+ int kb1 = kb;
+ final int[] upper = {0};
+ while (true) {
+ // length - 1
+ int n = r - l;
+
+ if (n < minQuickSelectSize) {
+ // Sort selection on small data
+ sortSelectRange(a, l, r, ka1, kb1);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // It is possible to use heapselect when kaa and kb1 are close to the same end
+ // |l|-----|ka1|--------|kb1|------|r|
+ // ---------s2----------
+ // ----------s4-----------
+ if (Math.min(kb1 - l, r - ka1) < edgeSelectConstant) {
+ edgeSelection.partition(a, l, r, ka1, kb1);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ if (maxDepth == 0) {
+ // Too much recursion
+ heapSelectRange(a, l, r, ka1, kb1);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // Pick a pivot and partition
+ int pivot;
+ if (n > subSamplingSize) {
+ // Floyd-Rivest: use SELECT recursively on a sample of size S to get an estimate
+ // for the (k-l+1)-th smallest element into a[k], biased slightly so that the
+ // (k-l+1)-th element is expected to lie in the smaller set after partitioning.
+ // Note: This targets ka1 and ignores kb1 for pivot selection.
+ ++n;
+ final int ith = ka1 - l + 1;
+ final double z = Math.log(n);
+ final double s = 0.5 * Math.exp(0.6666666666666666 * z);
+ final double sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * Integer.signum(ith - (n >> 1));
+ final int ll = Math.max(l, (int) (ka1 - ith * s / n + sd));
+ final int rr = Math.min(r, (int) (ka1 + (n - ith) * s / n + sd));
+ // Optional random sampling
+ if ((controlFlags & FLAG_RANDOM_SAMPLING) != 0) {
+ final IntUnaryOperator rng = createRNG(n, ka1);
+ // Shuffle [ll, k) from [l, k)
+ if (ll > l) {
+ for (int i = ka1; i > ll;) {
+ // l + rand [0, i - l + 1) : i is currently i+1
+ final int j = l + rng.applyAsInt(i - l);
+ final double t = a[--i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ // Shuffle (k, rr] from (k, r]
+ if (rr < r) {
+ for (int i = ka1; i < rr;) {
+ // r - rand [0, r - i + 1) : i is currently i-1
+ final int j = r - rng.applyAsInt(r - i);
+ final double t = a[++i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ }
+ introselect(part, a, ll, rr, k, ka1, ka1, lnNtoMaxDepthSinglePivot(z));
+ pivot = ka1;
+ } else {
+ // default pivot strategy
+ pivot = pivotingStrategy.pivotIndex(a, l, r, ka1);
+ }
+
+ final int p0 = part.partition(a, l, r, pivot, upper);
+ final int p1 = upper[0];
+
+ // Recursion to max depth
+ // Note: Here we possibly branch left and right with multiple keys.
+ // It is possible that the partition has split the keys
+ // and the recursion proceeds with a reduced set on either side.
+ // p0 p1
+ // |l|--|ka1|--k----k--|P|------k--|kb1|------|r|
+ // kb1 | ka1
+ // Search previous/next is bounded at ka1/kb1
+ maxDepth--;
+ // Recurse left side if required
+ if (ka1 < p0) {
+ if (kb1 <= p1) {
+ // Entirely on left side
+ r = p0 - 1;
+ if (r < kb1) {
+ kb1 = k.previousIndex(r);
+ }
+ continue;
+ }
+ introselect(part, a, l, p0 - 1, k, ka1, k.split(p0, p1, upper), maxDepth);
+ ka1 = upper[0];
+ }
+ if (kb1 <= p1) {
+ // No right side
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+ // Continue on the right side
+ l = p1 + 1;
+ if (ka1 < l) {
+ ka1 = k.nextIndex(l);
+ }
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code k} and any index {@code i}:
+ *
+ * This function accepts a {@link UpdatingInterval} of indices {@code k} that define the
+ * range of indices to partition. The {@link UpdatingInterval} can be narrowed or split as
+ * partitioning divides the range.
+ *
+ * Uses an introselect variant. The quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is controlled by
+ * current configuration.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Interval of indices to partition (ordered).
+ * @param maxDepth Maximum depth for recursion.
+ */
+ // package-private for benchmarking
+ void introselect(SPEPartition part, double[] a, int left, int right,
+ UpdatingInterval k, int maxDepth) {
+ // Only one side requires recursion. The other side
+ // can remain within this function call.
+ int l = left;
+ int r = right;
+ int ka = k.left();
+ int kb = k.right();
+ final int[] upper = {0};
+ while (true) {
+ // length - 1
+ final int n = r - l;
+
+ if (n < minQuickSelectSize) {
+ // Sort selection on small data
+ sortSelectRange(a, l, r, ka, kb);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // It is possible to use heapselect when ka and kb are close to the same end
+ // |l|-----|ka|--------|kb|------|r|
+ // ---------s2----------
+ // ----------s4-----------
+ if (Math.min(kb - l, r - ka) < edgeSelectConstant) {
+ edgeSelection.partition(a, l, r, ka, kb);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ if (maxDepth == 0) {
+ // Too much recursion
+ heapSelectRange(a, l, r, ka, kb);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // Pick a pivot and partition
+ final int p0 = part.partition(a, l, r,
+ pivotingStrategy.pivotIndex(a, l, r, ka),
+ upper);
+ final int p1 = upper[0];
+
+ // Recursion to max depth
+ // Note: Here we possibly branch left and right with multiple keys.
+ // It is possible that the partition has split the keys
+ // and the recursion proceeds with a reduced set on either side.
+ // p0 p1
+ // |l|--|ka|--k----k--|P|------k--|kb|------|r|
+ // kb | ka
+ maxDepth--;
+ // Recurse left side if required
+ if (ka < p0) {
+ if (kb <= p1) {
+ // Entirely on left side
+ r = p0 - 1;
+ if (r < kb) {
+ kb = k.updateRight(r);
+ }
+ continue;
+ }
+ introselect(part, a, l, p0 - 1, k.splitLeft(p0, p1), maxDepth);
+ ka = k.left();
+ } else if (kb <= p1) {
+ // No right side
+ recursionConsumer.accept(maxDepth);
+ return;
+ } else if (ka <= p1) {
+ ka = k.updateLeft(p1 + 1);
+ }
+ // Continue on the right side
+ l = p1 + 1;
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code k} and any index {@code i}:
+ *
+ * This function accepts a {@link SplittingInterval} of indices {@code k} that define the
+ * range of indices to partition. The {@link SplittingInterval} is split as
+ * partitioning divides the range.
+ *
+ * Uses an introselect variant. The quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is a heapselect.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param keys Interval of indices to partition (ordered).
+ * @param maxDepth Maximum depth for recursion.
+ */
+ // package-private for benchmarking
+ void introselect(SPEPartition part, double[] a, int left, int right,
+ SplittingInterval keys, int maxDepth) {
+ // Only one side requires recursion. The other side
+ // can remain within this function call.
+ int l = left;
+ int r = right;
+ SplittingInterval k = keys;
+ int ka = k.left();
+ int kb = k.right();
+ final int[] upper = {0};
+ while (true) {
+ // length - 1
+ final int n = r - l;
+
+ if (n < minQuickSelectSize) {
+ // Sort selection on small data
+ sortSelectRange(a, l, r, ka, kb);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // It is possible to use heapselect when ka and kb are close to the same end
+ // |l|-----|ka|--------|kb|------|r|
+ // ---------s2----------
+ // ----------s4-----------
+ if (Math.min(kb - l, r - ka) < edgeSelectConstant) {
+ edgeSelection.partition(a, l, r, ka, kb);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ if (maxDepth == 0) {
+ // Too much recursion
+ heapSelectRange(a, l, r, ka, kb);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // Pick a pivot and partition
+ final int p0 = part.partition(a, l, r,
+ pivotingStrategy.pivotIndex(a, l, r, ka),
+ upper);
+ final int p1 = upper[0];
+
+ // Recursion to max depth
+ // Note: Here we possibly branch left and right with multiple keys.
+ // It is possible that the partition has split the keys
+ // and the recursion proceeds with a reduced set on either side.
+ // p0 p1
+ // |l|--|ka|--k----k--|P|------k--|kb|------|r|
+ // kb | ka
+ maxDepth--;
+ final SplittingInterval lk = k.split(p0, p1);
+ // Recurse left side if required
+ if (lk != null) {
+ // Avoid recursive method calls
+ if (k.empty()) {
+ // Entirely on left side
+ r = p0 - 1;
+ kb = lk.right();
+ k = lk;
+ continue;
+ }
+ introselect(part, a, l, p0 - 1, lk, maxDepth);
+ }
+ if (k.empty()) {
+ // No right side
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+ // Continue on the right side
+ l = p1 + 1;
+ ka = k.left();
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code k} and any index {@code i}:
+ *
+ * This function accepts an {@link IndexIterator} of indices {@code k}; for
+ * convenience the lower and upper indices of the current interval are passed as the
+ * first index {@code ka} and last index {@code kb} of the closed interval of indices
+ * to partition. These may be within the lower and upper indices if the interval was
+ * split during recursion: {@code lower <= ka <= kb <= upper}.
+ *
+ * The data is recursively partitioned using left-most ordering. When the current
+ * interval has been partitioned the {@link IndexIterator} is used to advance to the
+ * next interval to partition.
+ *
+ * Uses an introselect variant. The quickselect is provided as an argument; the
+ * fall-back on poor convergence of the quickselect is a heapselect.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Interval of indices to partition (ordered).
+ * @param ka First key.
+ * @param kb Last key.
+ * @param maxDepth Maximum depth for recursion.
+ */
+ // package-private for benchmarking
+ void introselect(SPEPartition part, double[] a, int left, int right,
+ IndexIterator k, int ka, int kb, int maxDepth) {
+ // Left side requires recursion; right side remains within this function
+ // When this function returns all indices in [left, right] must be processed.
+ int l = left;
+ int lo = ka;
+ int hi = kb;
+ final int[] upper = {0};
+ while (true) {
+ if (maxDepth == 0) {
+ // Too much recursion.
+ // Advance the iterator to the end of the current range.
+ // Note: heapSelectRange handles hi > right.
+ // Single API method: advanceBeyond(right): return hi <= right
+ while (hi < right && k.next()) {
+ hi = k.right();
+ }
+ heapSelectRange(a, l, right, lo, hi);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // length - 1
+ final int n = right - l;
+
+ // If interval is close to one end then edgeselect.
+ // Only elect left if there are no further indices in the range.
+ // |l|-----|lo|--------|hi|------|right|
+ // ---------d1----------
+ // --------------d2-----------
+ if (Math.min(hi - l, right - lo) < edgeSelectConstant) {
+ if (hi - l > right - lo) {
+ // Right end. Do not check above hi, just select to the end
+ edgeSelection.partition(a, l, right, lo, right);
+ recursionConsumer.accept(maxDepth);
+ return;
+ } else if (k.nextAfter(right)) {
+ // Left end
+ // Only if no further indices in the range.
+ // If false this branch will continue to be triggered until
+ // a partition is made to separate the next indices.
+ edgeSelection.partition(a, l, right, lo, hi);
+ recursionConsumer.accept(maxDepth);
+ // Advance iterator
+ l = hi + 1;
+ if (!k.positionAfter(hi) || Math.max(k.left(), l) > right) {
+ // No more keys, or keys beyond the current bounds
+ return;
+ }
+ lo = Math.max(k.left(), l);
+ hi = Math.min(right, k.right());
+ // Continue right (allows a second heap select for the right side)
+ continue;
+ }
+ }
+
+ // If interval is close to both ends then full sort
+ // |l|-----|lo|--------|hi|------|right|
+ // ---d1----
+ // ----d2--------
+ // (lo - l) + (right - hi) == (right - l) - (hi - lo)
+ if (n - (hi - lo) < minQuickSelectSize) {
+ // Handle small data. This is done as the JDK sort will
+ // use insertion sort for small data. For double data it
+ // will also pre-process the data for NaN and signed
+ // zeros which is an overhead to avoid.
+ if (n < minQuickSelectSize) {
+ // Must not use sortSelectRange in [lo, hi] as the iterator
+ // has not been advanced to check after hi
+ sortSelectRight(a, l, right, lo);
+ } else {
+ // Note: This disregards the current level of recursion
+ // but can exploit the JDK's more advanced sort algorithm.
+ Arrays.sort(a, l, right + 1);
+ }
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // Here: l <= lo <= hi <= right
+ // Pick a pivot and partition
+ final int p0 = part.partition(a, l, right,
+ pivotingStrategy.pivotIndex(a, l, right, ka),
+ upper);
+ final int p1 = upper[0];
+
+ maxDepth--;
+ // Recursion left
+ if (lo < p0) {
+ introselect(part, a, l, p0 - 1, k, lo, Math.min(hi, p0 - 1), maxDepth);
+ // Advance iterator
+ // Single API method: fastForwardAndLeftWithin(p1, right)
+ if (!k.positionAfter(p1) || k.left() > right) {
+ // No more keys, or keys beyond the current bounds
+ return;
+ }
+ lo = k.left();
+ hi = Math.min(right, k.right());
+ }
+ if (hi <= p1) {
+ // Advance iterator
+ if (!k.positionAfter(p1) || k.left() > right) {
+ // No more keys, or keys beyond the current bounds
+ return;
+ }
+ lo = k.left();
+ hi = Math.min(right, k.right());
+ }
+ // Continue right
+ l = p1 + 1;
+ lo = Math.max(lo, l);
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * All indices are assumed to be within {@code [0, right]}.
+ *
+ * Uses an introselect variant. The dual pivot quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is controlled by
+ * current configuration.
+ *
+ * The partition method is not required to handle signed zeros.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param k Indices (may be destructively modified).
+ * @param count Count of indices (assumed to be strictly positive).
+ */
+ void introselect(DPPartition part, double[] a, int[] k, int count) {
+ // Handle NaN / signed zeros
+ final DoubleDataTransformer t = SORT_TRANSFORMER.get();
+ // Assume this is in-place
+ t.preProcess(a);
+ final int end = t.length();
+ int n = count;
+ if (end > 1) {
+ // Filter indices invalidated by NaN check
+ if (end < a.length) {
+ for (int i = n; --i >= 0;) {
+ final int v = k[i];
+ if (v >= end) {
+ // swap(k, i, --n)
+ k[i] = k[--n];
+ k[n] = v;
+ }
+ }
+ }
+ introselect(part, a, end - 1, k, n);
+ }
+ // Restore signed zeros
+ t.postProcess(a, k, n);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * All indices are assumed to be within {@code [0, right]}.
+ *
+ * Uses an introselect variant. The dual pivot quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is controlled by
+ * current configuration.
+ *
+ * This function assumes {@code n > 0} and {@code right > 0}; otherwise
+ * there is nothing to do.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Indices (may be destructively modified).
+ * @param n Count of indices (assumed to be strictly positive).
+ */
+ private void introselect(DPPartition part, double[] a, int right, int[] k, int n) {
+ if (n < 1) {
+ return;
+ }
+ final int maxDepth = createMaxDepthDualPivot(right + 1);
+ // Handle cases without multiple keys
+ if (n == 1) {
+ if (pairedKeyStrategy == PairedKeyStrategy.PAIRED_KEYS) {
+ // Dedicated method for a single key
+ introselect(part, a, 0, right, k[0], maxDepth);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.TWO_KEYS) {
+ // Dedicated method for two keys using the same key
+ introselect(part, a, 0, right, k[0], k[0], maxDepth);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.SEARCHABLE_INTERVAL) {
+ // Reuse the IndexInterval method using the same key
+ introselect(part, a, 0, right, IndexIntervals.anyIndex(), k[0], k[0], maxDepth);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.UPDATING_INTERVAL) {
+ // Reuse the Interval method using a single key
+ introselect(part, a, 0, right, IndexIntervals.interval(k[0]), maxDepth);
+ } else {
+ throw new IllegalStateException(UNSUPPORTED_INTROSELECT + pairedKeyStrategy);
+ }
+ return;
+ }
+ // Special case for partition around adjacent indices (for interpolation)
+ if (n == 2 && k[0] + 1 == k[1]) {
+ if (pairedKeyStrategy == PairedKeyStrategy.PAIRED_KEYS) {
+ // Dedicated method for a single key, returns information about k+1
+ final int p = introselect(part, a, 0, right, k[0], maxDepth);
+ // p <= k to signal k+1 is unsorted, or p+1 is a pivot.
+ // if k is sorted, and p+1 is sorted, k+1 is sorted if k+1 == p.
+ if (p > k[1]) {
+ selectMinIgnoreZeros(a, k[1], p);
+ }
+ } else if (pairedKeyStrategy == PairedKeyStrategy.TWO_KEYS) {
+ // Dedicated method for two keys
+ // Note: This can handle keys that are not adjacent
+ // e.g. keys near opposite ends without a partition step.
+ final int ka = Math.min(k[0], k[1]);
+ final int kb = Math.max(k[0], k[1]);
+ introselect(part, a, 0, right, ka, kb, maxDepth);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.SEARCHABLE_INTERVAL) {
+ // Reuse the IndexInterval method using a range of two keys
+ introselect(part, a, 0, right, IndexIntervals.anyIndex(), k[0], k[1], maxDepth);
+ } else if (pairedKeyStrategy == PairedKeyStrategy.UPDATING_INTERVAL) {
+ // Reuse the Interval method using a range of two keys
+ introselect(part, a, 0, right, IndexIntervals.interval(k[0], k[1]), maxDepth);
+ } else {
+ throw new IllegalStateException(UNSUPPORTED_INTROSELECT + pairedKeyStrategy);
+ }
+ return;
+ }
+
+ // Detect possible saturated range.
+ // minimum keys = 10
+ // min separation = 2^3 (could use log2(minQuickSelectSize) here)
+ // saturation = 0.95
+ //if (keysAreSaturated(right + 1, k, n, 10, 3, 0.95)) {
+ // Arrays.sort(a, 0, right + 1);
+ // return;
+ //}
+
+ // Note: Sorting to unique keys is an overhead. This can be eliminated
+ // by requesting the caller passes sorted keys (or quantiles in order).
+
+ if (keyStrategy == KeyStrategy.ORDERED_KEYS) {
+ // DP does not offer ORDERED_KEYS implementation but we include the branch
+ // for completeness.
+ throw new IllegalStateException(UNSUPPORTED_INTROSELECT + keyStrategy);
+ } else if (keyStrategy == KeyStrategy.SCANNING_KEY_SEARCHABLE_INTERVAL) {
+ final int unique = Sorting.sortIndices(k, n);
+ final SearchableInterval keys = ScanningKeyInterval.of(k, unique);
+ introselect(part, a, 0, right, keys, keys.left(), keys.right(), maxDepth);
+ } else if (keyStrategy == KeyStrategy.SEARCH_KEY_SEARCHABLE_INTERVAL) {
+ final int unique = Sorting.sortIndices(k, n);
+ final SearchableInterval keys = BinarySearchKeyInterval.of(k, unique);
+ introselect(part, a, 0, right, keys, keys.left(), keys.right(), maxDepth);
+ } else if (keyStrategy == KeyStrategy.COMPRESSED_INDEX_SET) {
+ final SearchableInterval keys = CompressedIndexSet.of(compression, k, n);
+ introselect(part, a, 0, right, keys, keys.left(), keys.right(), maxDepth);
+ } else if (keyStrategy == KeyStrategy.INDEX_SET) {
+ final SearchableInterval keys = IndexSet.of(k, n);
+ introselect(part, a, 0, right, keys, keys.left(), keys.right(), maxDepth);
+ } else if (keyStrategy == KeyStrategy.KEY_UPDATING_INTERVAL) {
+ final int unique = Sorting.sortIndices(k, n);
+ final UpdatingInterval keys = KeyUpdatingInterval.of(k, unique);
+ introselect(part, a, 0, right, keys, maxDepth);
+ } else if (keyStrategy == KeyStrategy.INDEX_SET_UPDATING_INTERVAL) {
+ final UpdatingInterval keys = BitIndexUpdatingInterval.of(k, n);
+ introselect(part, a, 0, right, keys, maxDepth);
+ } else if (keyStrategy == KeyStrategy.KEY_SPLITTING_INTERVAL) {
+ final int unique = Sorting.sortIndices(k, n);
+ final SplittingInterval keys = KeyUpdatingInterval.of(k, unique);
+ introselect(part, a, 0, right, keys, maxDepth);
+ } else if (keyStrategy == KeyStrategy.INDEX_SET_SPLITTING_INTERVAL) {
+ final SplittingInterval keys = BitIndexUpdatingInterval.of(k, n);
+ introselect(part, a, 0, right, keys, maxDepth);
+ } else if (keyStrategy == KeyStrategy.INDEX_ITERATOR) {
+ final int unique = Sorting.sortIndices(k, n);
+ final IndexIterator keys = KeyIndexIterator.of(k, unique);
+ introselect(part, a, 0, right, keys, keys.left(), keys.right(), maxDepth);
+ } else if (keyStrategy == KeyStrategy.COMPRESSED_INDEX_ITERATOR) {
+ final IndexIterator keys = CompressedIndexSet.iterator(compression, k, n);
+ introselect(part, a, 0, right, keys, keys.left(), keys.right(), maxDepth);
+ } else {
+ throw new IllegalStateException(UNSUPPORTED_INTROSELECT + keyStrategy);
+ }
+ }
+
+ /**
+ * Partition the array such that index {@code k} corresponds to its
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * Uses an introselect variant. The quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is controlled by
+ * current configuration.
+ *
+ * Returns information {@code p} on whether {@code k+1} is sorted.
+ * If {@code p <= k} then {@code k+1} is sorted.
+ * If {@code p > k} then {@code p+1} is a pivot.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Index.
+ * @param maxDepth Maximum depth for recursion.
+ * @return the index {@code p}
+ */
+ private int introselect(DPPartition part, double[] a, int left, int right,
+ int k, int maxDepth) {
+ int l = left;
+ int r = right;
+ final int[] upper = {0, 0, 0};
+ while (true) {
+ // It is possible to use edgeselect when k is close to the end
+ // |l|-----|k|---------|k|--------|r|
+ // ---d1----
+ // -----d3----
+ final int d1 = k - l;
+ final int d3 = r - k;
+ if (Math.min(d1, d3) < edgeSelectConstant) {
+ edgeSelection.partition(a, l, r, k, k);
+ // Last known unsorted value >= k
+ return r;
+ }
+
+ if (maxDepth == 0) {
+ // Too much recursion
+ stopperSelection.partition(a, l, r, k, k);
+ // Last known unsorted value >= k
+ return r;
+ }
+
+ // Pick 2 pivots and partition
+ int p0 = dualPivotingStrategy.pivotIndex(a, l, r, upper);
+ p0 = part.partition(a, l, r, p0, upper[0], upper);
+ final int p1 = upper[0];
+ final int p2 = upper[1];
+ final int p3 = upper[2];
+
+ maxDepth--;
+ if (k < p0) {
+ // The element is in the left partition
+ r = p0 - 1;
+ continue;
+ } else if (k > p3) {
+ // The element is in the right partition
+ l = p3 + 1;
+ continue;
+ }
+ // Check the interval overlaps the middle; and the middle exists.
+ // p0 p1 p2 p3
+ // |l|-----------------|P|------------------|P|----|r|
+ // Eliminate: ----kb1 ka1----
+ if (k <= p1 || p2 <= k || p2 - p1 <= 2) {
+ // Signal if k+1 is sorted.
+ // This can be true if the pivots were ranges [p0, p1] or [p2, p3]
+ // This check will match *most* sorted k for the 3 eliminated cases.
+ // It will not identify p2 - p1 <= 2 when k == p1. In this case
+ // k+1 is sorted and a min-select for k+1 is a fast scan up to r.
+ return k != p1 && k < p3 ? k : r;
+ }
+ // Continue in the middle partition
+ l = p1 + 1;
+ r = p2 - 1;
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code ka} and {@code kb} correspond to their
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code k} and any index {@code i}:
+ *
+ * Note: Requires {@code ka <= kb}. The use of two indices is to support processing
+ * of pairs of indices {@code (k, k+1)}. However the indices are treated independently
+ * and partitioned by recursion. They may be equal, neighbours or well separated.
+ *
+ * Uses an introselect variant. The quickselect is provided as an argument; the
+ * fall-back on poor convergence of the quickselect is a heapselect.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param ka Index.
+ * @param kb Index.
+ * @param maxDepth Maximum depth for recursion.
+ */
+ private void introselect(DPPartition part, double[] a, int left, int right,
+ int ka, int kb, int maxDepth) {
+ // Only one side requires recursion. The other side
+ // can remain within this function call.
+ int l = left;
+ int r = right;
+ int ka1 = ka;
+ int kb1 = kb;
+ final int[] upper = {0, 0, 0};
+ while (true) {
+ // length - 1
+ final int n = r - l;
+
+ if (n < minQuickSelectSize) {
+ // Sort selection on small data
+ sortSelectRange(a, l, r, ka1, kb1);
+ return;
+ }
+
+ // It is possible to use heapselect when ka1 and kb1 are close to the ends
+ // |l|-----|ka1|--------|kb1|------|r|
+ // ---s1----
+ // -----s3----
+ // ---------s2-----------
+ // ----------s4-----------
+ final int s1 = ka1 - l;
+ final int s2 = kb1 - l;
+ final int s3 = r - kb1;
+ final int s4 = r - ka1;
+ if (maxDepth == 0 ||
+ Math.min(s1 + s3, Math.min(s2, s4)) < edgeSelectConstant) {
+ // Too much recursion, or ka1 and kb1 are both close to the ends
+ // Note: Does not use the edgeSelection function as the indices are not a range
+ heapSelectPair(a, l, r, ka1, kb1);
+ return;
+ }
+
+ // Pick 2 pivots and partition
+ int p0 = dualPivotingStrategy.pivotIndex(a, l, r, upper);
+ p0 = part.partition(a, l, r, p0, upper[0], upper);
+ final int p1 = upper[0];
+ final int p2 = upper[1];
+ final int p3 = upper[2];
+
+ // Recursion to max depth
+ // Note: Here we possibly branch left and right with multiple keys.
+ // It is possible that the partition has split the pair
+ // and the recursion proceeds with a single point.
+ maxDepth--;
+ // Recurse left side if required
+ if (ka1 < p0) {
+ if (kb1 <= p1) {
+ // Entirely on left side
+ r = p0 - 1;
+ kb1 = r < kb1 ? ka1 : kb1;
+ continue;
+ }
+ introselect(part, a, l, p0 - 1, ka1, ka1, maxDepth);
+ // Here we must process middle and possibly right
+ ka1 = kb1;
+ }
+ // Recurse middle if required
+ // Check the either k is in the range (p1, p2)
+ // p0 p1 p2 p3
+ // |l|-----------------|P|------------------|P|----|r|
+ if (ka1 < p2 && ka1 > p1 || kb1 < p2 && kb1 > p1) {
+ // Advance lower bound
+ l = p1 + 1;
+ ka1 = ka1 < l ? kb1 : ka1;
+ if (kb1 <= p3) {
+ // Entirely in middle
+ r = p2 - 1;
+ kb1 = r < kb1 ? ka1 : kb1;
+ continue;
+ }
+ introselect(part, a, l, p2 - 1, ka1, ka1, maxDepth);
+ // Here we must process right
+ ka1 = kb1;
+ }
+ if (kb1 <= p3) {
+ // No right side
+ return;
+ }
+ // Continue right
+ l = p3 + 1;
+ ka1 = ka1 < l ? kb1 : ka1;
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code k} and any index {@code i}:
+ *
+ * This function accepts a {@link SearchableInterval} of indices {@code k} and the
+ * first index {@code ka} and last index {@code kb} that define the range of indices
+ * to partition. The {@link SearchableInterval} is used to search for keys in {@code [ka, kb]}
+ * to create {@code [ka, kb1]} and {@code [ka1, kb]} if partitioning splits the range.
+ *
+ * Uses an introselect variant. The dual pivot quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is a heapselect.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Interval of indices to partition (ordered).
+ * @param ka First key.
+ * @param kb Last key.
+ * @param maxDepth Maximum depth for recursion.
+ */
+ // package-private for benchmarking
+ void introselect(DPPartition part, double[] a, int left, int right,
+ SearchableInterval k, int ka, int kb, int maxDepth) {
+ // If partitioning splits the interval then recursion is used for left and/or
+ // right sides and the middle remains within this function. If partitioning does
+ // not split the interval then it remains within this function.
+ int l = left;
+ int r = right;
+ int ka1 = ka;
+ int kb1 = kb;
+ final int[] upper = {0, 0, 0};
+ while (true) {
+ // length - 1
+ final int n = r - l;
+
+ if (n < minQuickSelectSize) {
+ // Sort selection on small data
+ sortSelectRange(a, l, r, ka1, kb1);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // It is possible to use heapselect when ka1 and kb1 are close to the same end
+ // |l|-----|ka1|--------|kb1|------|r|
+ // ---------s2-----------
+ // ----------s4-----------
+ if (Math.min(kb1 - l, r - ka1) < edgeSelectConstant) {
+ edgeSelection.partition(a, l, r, ka1, kb1);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ if (maxDepth == 0) {
+ // Too much recursion
+ heapSelectRange(a, l, r, ka1, kb1);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // Pick 2 pivots and partition
+ int p0 = dualPivotingStrategy.pivotIndex(a, l, r, upper);
+ p0 = part.partition(a, l, r, p0, upper[0], upper);
+ final int p1 = upper[0];
+ final int p2 = upper[1];
+ final int p3 = upper[2];
+
+ // Recursion to max depth
+ // Note: Here we possibly branch left, middle and right with multiple keys.
+ // It is possible that the partition has split the keys
+ // and the recursion proceeds with a reduced set in each region.
+ // p0 p1 p2 p3
+ // |l|--|ka1|--k----k--|P|------k--|kb1|----|P|----|r|
+ // kb1 | ka1
+ // Search previous/next is bounded at ka1/kb1
+ maxDepth--;
+ // Recurse left side if required
+ if (ka1 < p0) {
+ if (kb1 <= p1) {
+ // Entirely on left side
+ r = p0 - 1;
+ if (r < kb1) {
+ kb1 = k.previousIndex(r);
+ }
+ continue;
+ }
+ introselect(part, a, l, p0 - 1, k, ka1, k.split(p0, p1, upper), maxDepth);
+ ka1 = upper[0];
+ }
+ // Recurse right side if required
+ if (kb1 > p3) {
+ if (ka1 >= p2) {
+ // Entirely on right-side
+ l = p3 + 1;
+ if (ka1 < l) {
+ ka1 = k.nextIndex(l);
+ }
+ continue;
+ }
+ final int lo = k.split(p2, p3, upper);
+ introselect(part, a, p3 + 1, r, k, upper[0], kb1, maxDepth);
+ kb1 = lo;
+ }
+ // Check the interval overlaps the middle; and the middle exists.
+ // p0 p1 p2 p3
+ // |l|-----------------|P|------------------|P|----|r|
+ // Eliminate: ----kb1 ka1----
+ if (kb1 <= p1 || p2 <= ka1 || p2 - p1 <= 2) {
+ // No middle
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+ l = p1 + 1;
+ r = p2 - 1;
+ // Interval [ka1, kb1] overlaps the middle but there may be nothing in the interval.
+ // |l|-----------------|P|------------------|P|----|r|
+ // Eliminate: ka1 kb1
+ // Detect this if ka1 is advanced too far.
+ if (ka1 < l) {
+ ka1 = k.nextIndex(l);
+ if (ka1 > r) {
+ // No middle
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+ }
+ if (r < kb1) {
+ kb1 = k.previousIndex(r);
+ }
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code k} and any index {@code i}:
+ *
+ * This function accepts a {@link UpdatingInterval} of indices {@code k} that define the
+ * range of indices to partition. The {@link UpdatingInterval} can be narrowed or split as
+ * partitioning divides the range.
+ *
+ * Uses an introselect variant. The dual pivot quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is a heapselect.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Interval of indices to partition (ordered).
+ * @param maxDepth Maximum depth for recursion.
+ */
+ // package-private for benchmarking
+ void introselect(DPPartition part, double[] a, int left, int right,
+ UpdatingInterval k, int maxDepth) {
+ // If partitioning splits the interval then recursion is used for left and/or
+ // right sides and the middle remains within this function. If partitioning does
+ // not split the interval then it remains within this function.
+ int l = left;
+ int r = right;
+ int ka = k.left();
+ int kb = k.right();
+ final int[] upper = {0, 0, 0};
+ while (true) {
+ // length - 1
+ final int n = r - l;
+
+ if (n < minQuickSelectSize) {
+ // Sort selection on small data
+ sortSelectRange(a, l, r, ka, kb);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // It is possible to use heapselect when ka and kb are close to the same end
+ // |l|-----|ka|--------|kb|------|r|
+ // ---------s2-----------
+ // ----------s4-----------
+ if (Math.min(kb - l, r - ka) < edgeSelectConstant) {
+ edgeSelection.partition(a, l, r, ka, kb);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ if (maxDepth == 0) {
+ // Too much recursion
+ heapSelectRange(a, l, r, ka, kb);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // Pick 2 pivots and partition
+ int p0 = dualPivotingStrategy.pivotIndex(a, l, r, upper);
+ p0 = part.partition(a, l, r, p0, upper[0], upper);
+ final int p1 = upper[0];
+ final int p2 = upper[1];
+ final int p3 = upper[2];
+
+ // Recursion to max depth
+ // Note: Here we possibly branch left, middle and right with multiple keys.
+ // It is possible that the partition has split the keys
+ // and the recursion proceeds with a reduced set in each region.
+ // p0 p1 p2 p3
+ // |l|--|ka|--k----k--|P|------k--|kb|----|P|----|r|
+ // kb | ka
+ // Search previous/next is bounded at ka/kb
+ maxDepth--;
+ // Recurse left side if required
+ if (ka < p0) {
+ if (kb <= p1) {
+ // Entirely on left side
+ r = p0 - 1;
+ if (r < kb) {
+ kb = k.updateRight(r);
+ }
+ continue;
+ }
+ introselect(part, a, l, p0 - 1, k.splitLeft(p0, p1), maxDepth);
+ ka = k.left();
+ } else if (kb <= p1) {
+ // No middle/right side
+ return;
+ } else if (ka <= p1) {
+ // Advance lower bound
+ ka = k.updateLeft(p1 + 1);
+ }
+ // Recurse middle if required
+ if (ka < p2) {
+ l = p1 + 1;
+ if (kb <= p3) {
+ // Entirely in middle
+ r = p2 - 1;
+ if (r < kb) {
+ kb = k.updateRight(r);
+ }
+ continue;
+ }
+ introselect(part, a, l, p2 - 1, k.splitLeft(p2, p3), maxDepth);
+ ka = k.left();
+ } else if (kb <= p3) {
+ // No right side
+ return;
+ } else if (ka <= p3) {
+ ka = k.updateLeft(p3 + 1);
+ }
+ // Continue right
+ l = p3 + 1;
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code k} and any index {@code i}:
+ *
+ * This function accepts a {@link SplittingInterval} of indices {@code k} that define the
+ * range of indices to partition. The {@link SplittingInterval} is split as
+ * partitioning divides the range.
+ *
+ * Uses an introselect variant. The dual pivot quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is a heapselect.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Interval of indices to partition (ordered).
+ * @param maxDepth Maximum depth for recursion.
+ */
+ // package-private for benchmarking
+ void introselect(DPPartition part, double[] a, int left, int right,
+ SplittingInterval k, int maxDepth) {
+ // If partitioning splits the interval then recursion is used for left and/or
+ // right sides and the middle remains within this function. If partitioning does
+ // not split the interval then it remains within this function.
+ int l = left;
+ int r = right;
+ int ka = k.left();
+ int kb = k.right();
+ final int[] upper = {0, 0, 0};
+ while (true) {
+ // length - 1
+ final int n = r - l;
+
+ if (n < minQuickSelectSize) {
+ // Sort selection on small data
+ sortSelectRange(a, l, r, ka, kb);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // It is possible to use heapselect when ka and kb are close to the same end
+ // |l|-----|ka|--------|kb|------|r|
+ // ---------s2-----------
+ // ----------s4-----------
+ if (Math.min(kb - l, r - ka) < edgeSelectConstant) {
+ edgeSelection.partition(a, l, r, ka, kb);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ if (maxDepth == 0) {
+ // Too much recursion
+ heapSelectRange(a, l, r, ka, kb);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // Pick 2 pivots and partition
+ int p0 = dualPivotingStrategy.pivotIndex(a, l, r, upper);
+ p0 = part.partition(a, l, r, p0, upper[0], upper);
+ final int p1 = upper[0];
+ final int p2 = upper[1];
+ final int p3 = upper[2];
+
+ // Recursion to max depth
+ // Note: Here we possibly branch left, middle and right with multiple keys.
+ // It is possible that the partition has split the keys
+ // and the recursion proceeds with a reduced set in each region.
+ // p0 p1 p2 p3
+ // |l|--|ka|--k----k--|P|------k--|kb|----|P|----|r|
+ // kb | ka
+ // Search previous/next is bounded at ka/kb
+ maxDepth--;
+ SplittingInterval lk = k.split(p0, p1);
+ // Recurse left side if required
+ if (lk != null) {
+ // Avoid recursive method calls
+ if (k.empty()) {
+ // Entirely on left side
+ r = p0 - 1;
+ kb = lk.right();
+ k = lk;
+ continue;
+ }
+ introselect(part, a, l, p0 - 1, lk, maxDepth);
+ }
+ if (k.empty()) {
+ // No middle/right side
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+ lk = k.split(p2, p3);
+ // Recurse middle side if required
+ if (lk != null) {
+ // Avoid recursive method calls
+ if (k.empty()) {
+ // Entirely in middle side
+ l = p1 + 1;
+ r = p2 - 1;
+ ka = lk.left();
+ kb = lk.right();
+ k = lk;
+ continue;
+ }
+ introselect(part, a, p1 + 1, p2 - 1, lk, maxDepth);
+ }
+ if (k.empty()) {
+ // No right side
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+ // Continue right
+ l = p3 + 1;
+ ka = k.left();
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code k} and any index {@code i}:
+ *
+ * This function accepts an {@link IndexIterator} of indices {@code k}; for
+ * convenience the lower and upper indices of the current interval are passed as the
+ * first index {@code ka} and last index {@code kb} of the closed interval of indices
+ * to partition. These may be within the lower and upper indices if the interval was
+ * split during recursion: {@code lower <= ka <= kb <= upper}.
+ *
+ * The data is recursively partitioned using left-most ordering. When the current
+ * interval has been partitioned the {@link IndexIterator} is used to advance to the
+ * next interval to partition.
+ *
+ * Uses an introselect variant. The dual pivot quickselect is provided as an argument;
+ * the fall-back on poor convergence of the quickselect is a heapselect.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param k Interval of indices to partition (ordered).
+ * @param ka First key.
+ * @param kb Last key.
+ * @param maxDepth Maximum depth for recursion.
+ */
+ // package-private for benchmarking
+ void introselect(DPPartition part, double[] a, int left, int right,
+ IndexIterator k, int ka, int kb, int maxDepth) {
+ // If partitioning splits the interval then recursion is used for left and/or
+ // right sides and the middle remains within this function. If partitioning does
+ // not split the interval then it remains within this function.
+ int l = left;
+ final int r = right;
+ int lo = ka;
+ int hi = kb;
+ final int[] upper = {0, 0, 0};
+ while (true) {
+ if (maxDepth == 0) {
+ // Too much recursion.
+ // Advance the iterator to the end of the current range.
+ // Note: heapSelectRange handles hi > right.
+ // Single API method: advanceBeyond(right): return hi <= right
+ while (hi < right && k.next()) {
+ hi = k.right();
+ }
+ heapSelectRange(a, l, right, lo, hi);
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // length - 1
+ final int n = right - l;
+
+ // If interval is close to one end then heapselect.
+ // Only heapselect left if there are no further indices in the range.
+ // |l|-----|lo|--------|hi|------|right|
+ // ---------d1----------
+ // --------------d2-----------
+ if (Math.min(hi - l, right - lo) < edgeSelectConstant) {
+ if (hi - l > right - lo) {
+ // Right end. Do not check above hi, just select to the end
+ edgeSelection.partition(a, l, right, lo, right);
+ recursionConsumer.accept(maxDepth);
+ return;
+ } else if (k.nextAfter(right)) {
+ // Left end
+ // Only if no further indices in the range.
+ // If false this branch will continue to be triggered until
+ // a partition is made to separate the next indices.
+ edgeSelection.partition(a, l, right, lo, hi);
+ recursionConsumer.accept(maxDepth);
+ // Advance iterator
+ l = hi + 1;
+ if (!k.positionAfter(hi) || Math.max(k.left(), l) > right) {
+ // No more keys, or keys beyond the current bounds
+ return;
+ }
+ lo = Math.max(k.left(), l);
+ hi = Math.min(right, k.right());
+ // Continue right (allows a second heap select for the right side)
+ continue;
+ }
+ }
+
+ // If interval is close to both ends then sort
+ // |l|-----|lo|--------|hi|------|right|
+ // ---d1----
+ // ----d2--------
+ // (lo - l) + (right - hi) == (right - l) - (hi - lo)
+ if (n - (hi - lo) < minQuickSelectSize) {
+ // Handle small data. This is done as the JDK sort will
+ // use insertion sort for small data. For double data it
+ // will also pre-process the data for NaN and signed
+ // zeros which is an overhead to avoid.
+ if (n < minQuickSelectSize) {
+ // Must not use sortSelectRange in [lo, hi] as the iterator
+ // has not been advanced to check after hi
+ sortSelectRight(a, l, right, lo);
+ } else {
+ // Note: This disregards the current level of recursion
+ // but can exploit the JDK's more advanced sort algorithm.
+ Arrays.sort(a, l, right + 1);
+ }
+ recursionConsumer.accept(maxDepth);
+ return;
+ }
+
+ // Here: l <= lo <= hi <= right
+ // Pick 2 pivots and partition
+ int p0 = dualPivotingStrategy.pivotIndex(a, l, r, upper);
+ p0 = part.partition(a, l, r, p0, upper[0], upper);
+ final int p1 = upper[0];
+ final int p2 = upper[1];
+ final int p3 = upper[2];
+
+ maxDepth--;
+ // Recursion left
+ if (lo < p0) {
+ introselect(part, a, l, p0 - 1, k, lo, Math.min(hi, p0 - 1), maxDepth);
+ // Advance iterator
+ if (!k.positionAfter(p1) || k.left() > right) {
+ // No more keys, or keys beyond the current bounds
+ return;
+ }
+ lo = k.left();
+ hi = Math.min(right, k.right());
+ }
+ if (hi <= p1) {
+ // Advance iterator
+ if (!k.positionAfter(p1) || k.left() > right) {
+ // No more keys, or keys beyond the current bounds
+ return;
+ }
+ lo = k.left();
+ hi = Math.min(right, k.right());
+ }
+
+ // Recursion middle
+ l = p1 + 1;
+ lo = Math.max(lo, l);
+ if (lo < p2) {
+ introselect(part, a, l, p2 - 1, k, lo, Math.min(hi, p2 - 1), maxDepth);
+ // Advance iterator
+ if (!k.positionAfter(p3) || k.left() > right) {
+ // No more keys, or keys beyond the current bounds
+ return;
+ }
+ lo = k.left();
+ hi = Math.min(right, k.right());
+ }
+ if (hi <= p3) {
+ // Advance iterator
+ if (!k.positionAfter(p3) || k.left() > right) {
+ // No more keys, or keys beyond the current bounds
+ return;
+ }
+ lo = k.left();
+ hi = Math.min(right, k.right());
+ }
+
+ // Continue right
+ l = p3 + 1;
+ lo = Math.max(lo, l);
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * Uses a Bentley-McIlroy quicksort partition method by Sedgewick.
+ *
+ * @param data Values.
+ * @param k Indices (may be destructively modified).
+ * @param n Count of indices.
+ */
+ void partitionSBM(double[] data, int[] k, int n) {
+ // Handle NaN (this does assume n > 0)
+ final int right = sortNaN(data);
+ partition((SPEPartitionFunction) this::partitionSBMWithZeros, data, right, k, n);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * The method assumes all {@code k} are valid indices into the data.
+ * It handles NaN and signed zeros in the data.
+ *
+ * Uses an introselect variant. Uses the configured single-pivot quicksort method;
+ * the fall-back on poor convergence of the quickselect is controlled by
+ * current configuration.
+ *
+ * @param data Values.
+ * @param k Indices (may be destructively modified).
+ * @param n Count of indices.
+ */
+ void partitionISP(double[] data, int[] k, int n) {
+ introselect(getSPFunction(), data, k, n);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * The method assumes all {@code k} are valid indices into the data.
+ * It handles NaN and signed zeros in the data.
+ *
+ * Uses an introselect variant. The quickselect is a dual-pivot quicksort
+ * partition method by Vladimir Yaroslavskiy; the fall-back on poor convergence of
+ * the quickselect is controlled by current configuration.
+ *
+ * @param data Values.
+ * @param k Indices (may be destructively modified).
+ * @param n Count of indices.
+ */
+ void partitionIDP(double[] data, int[] k, int n) {
+ introselect((DPPartition) Partition::partitionDP, data, k, n);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * The method assumes all {@code k} are valid indices into the data.
+ * It handles NaN and signed zeros in the data.
+ *
+ * Uses the
+ * Floyd-Rivest Algorithm (Wikipedia)
+ *
+ * WARNING: Currently this only supports a single {@code k}. For parity with other
+ * select methods this accepts an array {@code k} and pre/post processes the data for
+ * NaN and signed zeros.
+ *
+ * @param a Values.
+ * @param k Indices (may be destructively modified).
+ * @param count Count of indices.
+ */
+ void partitionFR(double[] a, int[] k, int count) {
+ // Handle NaN / signed zeros
+ final DoubleDataTransformer t = SORT_TRANSFORMER.get();
+ // Assume this is in-place
+ t.preProcess(a);
+ final int end = t.length();
+ int n = count;
+ if (end > 1) {
+ // Filter indices invalidated by NaN check
+ if (end < a.length) {
+ for (int i = n; --i >= 0;) {
+ final int v = k[i];
+ if (v >= end) {
+ // swap(k, i, --n)
+ k[i] = k[--n];
+ k[n] = v;
+ }
+ }
+ }
+ // Only handles a single k
+ if (n != 0) {
+ selectFR(a, 0, end - 1, k[0], controlFlags);
+ }
+ }
+ // Restore signed zeros
+ t.postProcess(a, k, n);
+ }
+
+ /**
+ * Select the k-th element of the array.
+ *
+ * Uses the
+ * Floyd-Rivest Algorithm (Wikipedia).
+ *
+ * This code has been adapted from:
+ * The method assumes all {@code k} are valid indices into the data.
+ * It handles NaN and signed zeros in the data.
+ *
+ * Uses the
+ * Floyd-Rivest Algorithm (Wikipedia), modified by Kiwiel.
+ *
+ * WARNING: Currently this only supports a single {@code k}. For parity with other
+ * select methods this accepts an array {@code k} and pre/post processes the data for
+ * NaN and signed zeros.
+ *
+ * @param a Values.
+ * @param k Indices (may be destructively modified).
+ * @param count Count of indices.
+ */
+ void partitionKFR(double[] a, int[] k, int count) {
+ // Handle NaN / signed zeros
+ final DoubleDataTransformer t = SORT_TRANSFORMER.get();
+ // Assume this is in-place
+ t.preProcess(a);
+ final int end = t.length();
+ int n = count;
+ if (end > 1) {
+ // Filter indices invalidated by NaN check
+ if (end < a.length) {
+ for (int i = n; --i >= 0;) {
+ final int v = k[i];
+ if (v >= end) {
+ // swap(k, i, --n)
+ k[i] = k[--n];
+ k[n] = v;
+ }
+ }
+ }
+ // Only handles a single k
+ if (n != 0) {
+ final int[] bounds = new int[5];
+ selectKFR(a, 0, end - 1, k[0], bounds, null);
+ }
+ }
+ // Restore signed zeros
+ t.postProcess(a, k, n);
+ }
+
+ /**
+ * Select the k-th element of the array.
+ *
+ * Uses the
+ * Floyd-Rivest Algorithm (Wikipedia), modified by Kiwiel.
+ *
+ * References:
+ * This is a specialisation of {@link #vectorSwap(double[], int, int, int)}
+ * where the current left-most value is a constant {@code v}.
+ *
+ * @param x Array.
+ * @param a Index.
+ * @param b Index.
+ * @param c Index.
+ * @param v Constant value in [a, b]
+ */
+ private static void vectorSwapL(double[] x, int a, int b, int c, double v) {
+ for (int i = a - 1, j = c + 1, m = Math.min(b + 1 - a, c - b); --m >= 0;) {
+ x[++i] = x[--j];
+ x[j] = v;
+ }
+ }
+
+ /**
+ * Vector swap x[a:b] <-> x[b+1:c] means the first m = min(b+1-a, c-b)
+ * elements of the array x[a:c] are exchanged with its last m elements.
+ *
+ * This is a specialisation of {@link #vectorSwap(double[], int, int, int)}
+ * where the current right-most value is a constant {@code v}.
+ *
+ * @param x Array.
+ * @param a Index.
+ * @param b Index.
+ * @param c Index.
+ * @param v Constant value in (b, c]
+ */
+ private static void vectorSwapR(double[] x, int a, int b, int c, double v) {
+ for (int i = a - 1, j = c + 1, m = Math.min(b + 1 - a, c - b); --m >= 0;) {
+ x[--j] = x[++i];
+ x[i] = v;
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * The method assumes all {@code k} are valid indices into the data in {@code [0, length)}.
+ * It assumes no NaNs or signed zeros in the data. Data must be pre- and post-processed.
+ *
+ * Uses the configured single-pivot quicksort method;
+ * and median-of-medians algorithm for pivot selection with medians-of-5.
+ *
+ * Note:
+ * This method is not configurable with the exception of the single-pivot quickselect method
+ * and the size to stop quickselect recursion and finish using sort select. It has been superceded by
+ * {@link #partitionLinear(double[], int[], int)} which has configurable deterministic
+ * pivot selection including those using partition expansion in-place of full partitioning.
+ *
+ * @param data Values.
+ * @param k Indices (may be destructively modified).
+ * @param n Count of indices.
+ */
+ void partitionLSP(double[] data, int[] k, int n) {
+ linearSelect(getSPFunction(), data, k, n);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * The method assumes all {@code k} are valid indices into the data.
+ * It handles NaN and signed zeros in the data.
+ *
+ * Uses the median-of-medians algorithm for pivot selection.
+ *
+ * WARNING: Currently this only supports a single or range of {@code k}.
+ * For parity with other select methods this accepts an array {@code k} and pre/post
+ * processes the data for NaN and signed zeros.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param k Indices (may be destructively modified).
+ * @param count Count of indices.
+ */
+ private void linearSelect(SPEPartition part, double[] a, int[] k, int count) {
+ // Handle NaN / signed zeros
+ final DoubleDataTransformer t = SORT_TRANSFORMER.get();
+ // Assume this is in-place
+ t.preProcess(a);
+ final int end = t.length();
+ int n = count;
+ if (end > 1) {
+ // Filter indices invalidated by NaN check
+ if (end < a.length) {
+ for (int i = n; --i >= 0;) {
+ final int v = k[i];
+ if (v >= end) {
+ // swap(k, i, --n)
+ k[i] = k[--n];
+ k[n] = v;
+ }
+ }
+ }
+ if (n != 0) {
+ final int ka = Math.min(k[0], k[n - 1]);
+ final int kb = Math.max(k[0], k[n - 1]);
+ linearSelect(part, a, 0, end - 1, ka, kb, new int[2]);
+ }
+ }
+ // Restore signed zeros
+ t.postProcess(a, k, n);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code [ka, kb]} and any index {@code i}:
+ *
+ * This function accepts indices {@code [ka, kb]} that define the
+ * range of indices to partition. It is expected that the range is small.
+ *
+ * Uses quickselect with median-of-medians pivot selection to provide Order(n)
+ * performance.
+ *
+ * Returns the bounds containing {@code [ka, kb]}. These may be lower/higher
+ * than the keys if equal values are present in the data. This is to be used by
+ * {@link #pivotMedianOfMedians(SPEPartition, double[], int, int, int[])} to identify
+ * the equal value range of the pivot.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param ka First key of interest.
+ * @param kb Last key of interest.
+ * @param bounds Bounds of the range containing {@code [ka, kb]} (inclusive).
+ * @see Median of medians (Wikipedia)
+ */
+ private void linearSelect(SPEPartition part, double[] a, int left, int right, int ka, int kb,
+ int[] bounds) {
+ int l = left;
+ int r = right;
+ while (true) {
+ // Select when ka and kb are close to the same end
+ // |l|-----|ka|kkkkkkkk|kb|------|r|
+ // Optimal value for this is much higher than standard quickselect due
+ // to the high cost of median-of-medians pivot computation and reuse via
+ // mutual recursion so we have a different value.
+ if (Math.min(kb - l, r - ka) < linearSortSelectSize) {
+ sortSelectRange(a, l, r, ka, kb);
+ // We could scan left/right to extend the bounds here after the sort.
+ // Since the move_sample strategy is not generally useful we do not bother.
+ bounds[0] = ka;
+ bounds[1] = kb;
+ return;
+ }
+ int p0 = pivotMedianOfMedians(part, a, l, r, bounds);
+ if ((controlFlags & FLAG_MOVE_SAMPLE) != 0) {
+ // Note: medians with 5 elements creates a sample size of 20%.
+ // Avoid partitioning the sample known to be above the pivot.
+ // The pivot identified the lower pivot (lp) and upper pivot (p).
+ // This strategy is not faster unless there are a large number of duplicates
+ // (e.g. less than 10 unique values).
+ // On random data with no duplicates this is slower.
+ // Note: The methods based on quickselect adaptive create the sample in
+ // a region corresponding to expected k and expand the partition (faster).
+ //
+ // |l |lp p0| rr| r|
+ // | < | == | > | ??? |
+ //
+ // Move region above P to r
+ //
+ // |l |pp p0| r|
+ // | < | == | ??? | > |
+ final int lp = bounds[0];
+ final int rr = bounds[1];
+ vectorSwap(a, p0 + 1, rr, r);
+ // 20% less to partition
+ final int p = part.partition(a, p0, r - rr + p0, p0, bounds);
+ // |l |pp |p0 |p u| r|
+ // | < | == | < | == | > |
+ //
+ // Move additional equal pivot region to the centre:
+ // |l |p0 u| r|
+ // | < | == | > |
+ vectorSwapL(a, lp, p0 - 1, p - 1, a[p]);
+ p0 = p - p0 + lp;
+ } else {
+ p0 = part.partition(a, l, r, p0, bounds);
+ }
+ final int p1 = bounds[0];
+
+ // Note: Here we expect [ka, kb] to be small and splitting is unlikely.
+ // p0 p1
+ // |l|--|ka|kkkk|kb|--|P|-------------------|r|
+ // |l|----------------|P|--|ka|kkk|kb|------|r|
+ // |l|-----------|ka|k|P|k|kb|--------------|r|
+ if (kb < p0) {
+ // Entirely on left side
+ r = p0 - 1;
+ } else if (ka > p1) {
+ // Entirely on right side
+ l = p1 + 1;
+ } else {
+ // Pivot splits [ka, kb]. Expect ends to be close to the pivot and finish.
+ // Here we set the bounds for use after median-of-medians pivot selection.
+ // In the event there are many equal values this allows collecting those
+ // known to be equal together when moving around the medians sample.
+ bounds[0] = p0;
+ bounds[1] = p1;
+ if (ka < p0) {
+ sortSelectRight(a, l, p0 - 1, ka);
+ bounds[0] = ka;
+ }
+ if (kb > p1) {
+ sortSelectLeft(a, p1 + 1, r, kb);
+ bounds[1] = kb;
+ }
+ return;
+ }
+ }
+ }
+
+ /**
+ * Compute the median-of-medians pivot. Divides the length {@code n} into groups
+ * of at most 5 elements, computes the median of each group, and the median of the
+ * {@code n/5} medians. Assumes {@code l <= r}.
+ *
+ * The median-of-medians in computed in-place at the left end. The range containing
+ * the medians is {@code [l, rr]} with the right bound {@code rr} returned.
+ * In the event the pivot is a region of equal values, the range of the pivot values
+ * is {@code [lp, p]}, with the {@code p} returned and {@code lp} set in the output bounds.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param l Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param r Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param bounds Bounds {@code [lp, rr]}.
+ * @return the pivot index {@code p}
+ */
+ private int pivotMedianOfMedians(SPEPartition part, double[] a, int l, int r, int[] bounds) {
+ // Process blocks of 5.
+ // Moves the median of each block to the left of the array.
+ int rr = l - 1;
+ for (int e = l + 5;; e += 5) {
+ if (e > r) {
+ // Final block of size 1-5
+ Sorting.sort(a, e - 5, r);
+ final int m = (e - 5 + r) >>> 1;
+ final double v = a[m];
+ a[m] = a[++rr];
+ a[rr] = v;
+ break;
+ }
+
+ // Various methods for time-critical step.
+ // Each must be compiled and run on the same benchmark data.
+ // Decision tree is fastest.
+ //final int m = Sorting.median5(a, e - 5);
+ //final int m = Sorting.median5(a, e - 5, e - 4, e - 3, e - 2, e - 1);
+ // Bigger decision tree (same as median5)
+ //final int m = Sorting.median5b(a, e - 5);
+ // Sorting network of 4 + insertion (3-4% slower)
+ //final int m = Sorting.median5c(a, e - 5);
+ // In-place median: Sorting of 5, or median of 5
+ final int m = e - 3;
+ //Sorting.sort(a, e - 5, e - 1); // insertion sort
+ //Sorting.sort5(a, e - 5, e - 4, e - 3, e - 2, e - 1);
+ Sorting.median5d(a, e - 5, e - 4, e - 3, e - 2, e - 1);
+
+ final double v = a[m];
+ a[m] = a[++rr];
+ a[rr] = v;
+ }
+
+ int m = (l + rr + 1) >>> 1;
+ // mutual recursion
+ linearSelect(part, a, l, rr, m, m, bounds);
+ // bounds contains the range of the pivot.
+ // return the upper pivot and record the end of the range.
+ m = bounds[1];
+ bounds[1] = rr;
+ return m;
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * The method assumes all {@code k} are valid indices into the data in {@code [0, length)}.
+ * It assumes no NaNs or signed zeros in the data. Data must be pre- and post-processed.
+ *
+ * Uses the median-of-medians algorithm to provide Order(n) performance.
+ * This method has configurable deterministic pivot selection including those using
+ * partition expansion in-place of full partitioning. The methods are based on the
+ * QuickselectAdaptive method of Alexandrescu.
+ *
+ * @param data Values.
+ * @param k Indices (may be destructively modified).
+ * @param n Count of indices.
+ * @see #setLinearStrategy(LinearStrategy)
+ */
+ void partitionLinear(double[] data, int[] k, int n) {
+ quickSelect(linearSpFunction, data, k, n);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * The method assumes all {@code k} are valid indices into the data.
+ * It handles NaN and signed zeros in the data.
+ *
+ * This method assumes that the partition function can compute a pivot.
+ * It is used for variants of the median-of-medians algorithm which use mutual
+ * recursion for pivot selection.
+ *
+ * WARNING: Currently this only supports a single or range of {@code k}.
+ * For parity with other select methods this accepts an array {@code k} and pre/post
+ * processes the data for NaN and signed zeros.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param k Indices (may be destructively modified).
+ * @param count Count of indices.
+ * @see #setLinearStrategy(LinearStrategy)
+ */
+ private void quickSelect(SPEPartition part, double[] a, int[] k, int count) {
+ // Handle NaN / signed zeros
+ final DoubleDataTransformer t = SORT_TRANSFORMER.get();
+ // Assume this is in-place
+ t.preProcess(a);
+ final int end = t.length();
+ int n = count;
+ if (end > 1) {
+ // Filter indices invalidated by NaN check
+ if (end < a.length) {
+ for (int i = n; --i >= 0;) {
+ final int v = k[i];
+ if (v >= end) {
+ // swap(k, i, --n)
+ k[i] = k[--n];
+ k[n] = v;
+ }
+ }
+ }
+ if (n != 0) {
+ final int ka = Math.min(k[0], k[n - 1]);
+ final int kb = Math.max(k[0], k[n - 1]);
+ quickSelect(part, a, 0, end - 1, ka, kb, new int[2]);
+ }
+ }
+ // Restore signed zeros
+ t.postProcess(a, k, n);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code [ka, kb]} and any index {@code i}:
+ *
+ * This function accepts indices {@code [ka, kb]} that define the
+ * range of indices to partition. It is expected that the range is small.
+ *
+ * This method assumes that the partition function can compute a pivot.
+ * It is used for variants of the median-of-medians algorithm which use mutual
+ * recursion for pivot selection. This method is based on the improvements
+ * for median-of-medians algorithms in Alexandrescu (2016) (median-of-medians
+ * and median-of-median-of-medians).
+ *
+ * Returns the bounds containing {@code [ka, kb]}. These may be lower/higher
+ * than the keys if equal values are present in the data.
+ *
+ * @param part Partition function.
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param ka First key of interest.
+ * @param kb Last key of interest.
+ * @param bounds Bounds of the range containing {@code [ka, kb]} (inclusive).
+ * @see #setLinearStrategy(LinearStrategy)
+ */
+ private void quickSelect(SPEPartition part, double[] a, int left, int right, int ka, int kb,
+ int[] bounds) {
+ int l = left;
+ int r = right;
+ while (true) {
+ // Select when ka and kb are close to the same end
+ // |l|-----|ka|kkkkkkkk|kb|------|r|
+ // Optimal value for this is much higher than standard quickselect due
+ // to the high cost of median-of-medians pivot computation and reuse via
+ // mutual recursion so we have a different value.
+ // Note: Use of this will not break the Order(n) performance for worst
+ // case data, i.e. data where all values require full insertion.
+ // This will be Order(n * k) == Order(n); k becomes a multiplier as long as
+ // k << n; otherwise worst case is Order(n^2 / 2) when k=n/2.
+ if (Math.min(kb - l, r - ka) < linearSortSelectSize) {
+ sortSelectRange(a, l, r, ka, kb);
+ // We could scan left/right to extend the bounds here after the sort.
+ // Attempts to do this were not measurable in benchmarking.
+ bounds[0] = ka;
+ bounds[1] = kb;
+ return;
+ }
+ // Only target ka; kb is assumed to be close
+ final int p0 = part.partition(a, l, r, ka, bounds);
+ final int p1 = bounds[0];
+
+ // Note: Here we expect [ka, kb] to be small and splitting is unlikely.
+ // p0 p1
+ // |l|--|ka|kkkk|kb|--|P|-------------------|r|
+ // |l|----------------|P|--|ka|kkk|kb|------|r|
+ // |l|-----------|ka|k|P|k|kb|--------------|r|
+ if (kb < p0) {
+ // Entirely on left side
+ r = p0 - 1;
+ } else if (ka > p1) {
+ // Entirely on right side
+ l = p1 + 1;
+ } else {
+ // Pivot splits [ka, kb]. Expect ends to be close to the pivot and finish.
+ // Here we set the bounds for use after median-of-medians pivot selection.
+ // In the event there are many equal values this allows collecting those
+ // known to be equal together when moving around the medians sample.
+ bounds[0] = p0;
+ bounds[1] = p1;
+ if (ka < p0) {
+ sortSelectRight(a, l, p0 - 1, ka);
+ bounds[0] = ka;
+ }
+ if (kb > p1) {
+ sortSelectLeft(a, p1 + 1, r, kb);
+ bounds[1] = kb;
+ }
+ return;
+ }
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * The method assumes all {@code k} are valid indices into the data in {@code [0, length)}.
+ * It assumes no NaNs or signed zeros in the data. Data must be pre- and post-processed.
+ *
+ * Uses the QuickselectAdaptive method of Alexandrescu. This is based on the
+ * median-of-medians algorithm. The median sample strategy is chosen based on
+ * the target index.
+ *
+ * @param data Values.
+ * @param k Indices (may be destructively modified).
+ * @param n Count of indices.
+ */
+ void partitionQA(double[] data, int[] k, int n) {
+ quickSelectAdaptive(data, k, n);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * The method assumes all {@code k} are valid indices into the data.
+ * It handles NaN and signed zeros in the data.
+ *
+ * WARNING: Currently this only supports a single or range of {@code k}.
+ * For parity with other select methods this accepts an array {@code k} and pre/post
+ * processes the data for NaN and signed zeros.
+ *
+ * @param a Values.
+ * @param k Indices (may be destructively modified).
+ * @param count Count of indices.
+ */
+ private void quickSelectAdaptive(double[] a, int[] k, int count) {
+ // Handle NaN / signed zeros
+ final DoubleDataTransformer t = SORT_TRANSFORMER.get();
+ // Assume this is in-place
+ t.preProcess(a);
+ final int end = t.length();
+ int n = count;
+ if (end > 1) {
+ // Filter indices invalidated by NaN check
+ if (end < a.length) {
+ for (int i = n; --i >= 0;) {
+ final int v = k[i];
+ if (v >= end) {
+ // swap(k, i, --n)
+ k[i] = k[--n];
+ k[n] = v;
+ }
+ }
+ }
+ if (n != 0) {
+ final int ka = Math.min(k[0], k[n - 1]);
+ final int kb = Math.max(k[0], k[n - 1]);
+ quickSelectAdaptive(a, 0, end - 1, ka, kb, new int[1], adaptMode);
+ }
+ }
+ // Restore signed zeros
+ t.postProcess(a, k, n);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code [ka, kb]} and any index {@code i}:
+ *
+ * This function accepts indices {@code [ka, kb]} that define the
+ * range of indices to partition. It is expected that the range is small.
+ *
+ * Uses the QuickselectAdaptive method of Alexandrescu. This is based on the
+ * median-of-medians algorithm. The median sample is strategy is chosen based on
+ * the target index.
+ *
+ * The adaption {@code mode} is used to control the sampling mode and adaption of
+ * the index within the sample.
+ *
+ * Returns the bounds containing {@code [ka, kb]}. These may be lower/higher
+ * than the keys if equal values are present in the data.
+ *
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param ka First key of interest.
+ * @param kb Last key of interest.
+ * @param bounds Upper bound of the range containing {@code [ka, kb]} (inclusive).
+ * @param mode Adaption mode.
+ * @return Lower bound of the range containing {@code [ka, kb]} (inclusive).
+ */
+ private int quickSelectAdaptive(double[] a, int left, int right, int ka, int kb,
+ int[] bounds, AdaptMode mode) {
+ int l = left;
+ int r = right;
+ AdaptMode m = mode;
+ while (true) {
+ // Select when ka and kb are close to the same end
+ // |l|-----|ka|kkkkkkkk|kb|------|r|
+ // Optimal value for this is much higher than standard quickselect due
+ // to the high cost of median-of-medians pivot computation and reuse via
+ // mutual recursion so we have a different value.
+ if (Math.min(kb - l, r - ka) < linearSortSelectSize) {
+ sortSelectRange(a, l, r, ka, kb);
+ bounds[0] = kb;
+ return ka;
+ }
+
+ // Only target ka; kb is assumed to be close
+ int p0;
+ int n = r - l;
+ // f in [0, 1]
+ final double f = (double) (ka - l) / n;
+ // Note: Margins for fraction left/right of pivot L : R.
+ // Subtract the larger margin to create the estimated size
+ // after partitioning. If the new size subtracted from
+ // the estimated size is negative (partition did not meet
+ // the margin guarantees) then adaption mode is changed (to
+ // a method more likely to achieve the margins).
+ if (f <= STEP_LEFT) {
+ if (m.isSampleMode() && n > subSamplingSize) {
+ // Floyd-Rivest sampling. Expect to eliminate the same as QA steps.
+ if (f <= STEP_FAR_LEFT) {
+ n -= (n >> 2) + (n >> 5) + (n >> 6);
+ } else {
+ n -= (n >> 2) + (n >> 3);
+ }
+ p0 = sampleStep(a, l, r, ka, bounds, m);
+ } else if (f <= STEP_FAR_LEFT) {
+ if ((controlFlags & FLAG_QA_FAR_STEP) != 0) {
+ // 1/12 : 1/3 (use 1/4 + 1/32 + 1/64 ~ 0.328)
+ n -= (n >> 2) + (n >> 5) + (n >> 6);
+ p0 = repeatedStepFarLeft(a, l, r, ka, bounds, m);
+ } else {
+ // 1/12 : 3/8
+ n -= (n >> 2) + (n >> 3);
+ p0 = repeatedStepLeft(a, l, r, ka, bounds, m, true);
+ }
+ } else {
+ // 1/6 : 1/4
+ n -= n >> 2;
+ p0 = repeatedStepLeft(a, l, r, ka, bounds, m, false);
+ }
+ } else if (f >= STEP_RIGHT) {
+ if (m.isSampleMode() && n > subSamplingSize) {
+ // Floyd-Rivest sampling. Expect to eliminate the same as QA steps.
+ if (f >= STEP_FAR_RIGHT) {
+ n -= (n >> 2) + (n >> 5) + (n >> 6);
+ } else {
+ n -= (n >> 2) + (n >> 3);
+ }
+ p0 = sampleStep(a, l, r, ka, bounds, m);
+ } else if (f >= STEP_FAR_RIGHT) {
+ if ((controlFlags & FLAG_QA_FAR_STEP) != 0) {
+ // 1/12 : 1/3 (use 1/4 + 1/32 + 1/64 ~ 0.328)
+ n -= (n >> 2) + (n >> 5) + (n >> 6);
+ p0 = repeatedStepFarRight(a, l, r, ka, bounds, m);
+ } else {
+ // 3/8 : 1/12
+ n -= (n >> 2) + (n >> 3);
+ p0 = repeatedStepRight(a, l, r, ka, bounds, m, true);
+ }
+ } else {
+ // 1/4 : 1/6
+ n -= n >> 2;
+ p0 = repeatedStepRight(a, l, r, ka, bounds, m, false);
+ }
+ } else {
+ if (m.isSampleMode() && n > subSamplingSize) {
+ // Floyd-Rivest sampling. Expect to eliminate the same as QA steps.
+ p0 = sampleStep(a, l, r, ka, bounds, m);
+ } else {
+ p0 = repeatedStep(a, l, r, ka, bounds, m);
+ }
+ // 2/9 : 2/9 (use 1/4 - 1/32 ~ 0.219)
+ n -= (n >> 2) - (n >> 5);
+ }
+
+ // Note: Here we expect [ka, kb] to be small and splitting is unlikely.
+ // p0 p1
+ // |l|--|ka|kkkk|kb|--|P|-------------------|r|
+ // |l|----------------|P|--|ka|kkk|kb|------|r|
+ // |l|-----------|ka|k|P|k|kb|--------------|r|
+ final int p1 = bounds[0];
+ if (kb < p0) {
+ // Entirely on left side
+ r = p0 - 1;
+ } else if (ka > p1) {
+ // Entirely on right side
+ l = p1 + 1;
+ } else {
+ // Pivot splits [ka, kb]. Expect ends to be close to the pivot and finish.
+ // Here we set the bounds for use after median-of-medians pivot selection.
+ // In the event there are many equal values this allows collecting those
+ // known to be equal together when moving around the medians sample.
+ if (kb > p1) {
+ sortSelectLeft(a, p1 + 1, r, kb);
+ bounds[0] = kb;
+ }
+ if (ka < p0) {
+ sortSelectRight(a, l, p0 - 1, ka);
+ p0 = ka;
+ }
+ return p0;
+ }
+ // Update sampling mode
+ m = m.update(n, l, r);
+ }
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * The method assumes all {@code k} are valid indices into the data in {@code [0, length)}.
+ * It assumes no NaNs or signed zeros in the data. Data must be pre- and post-processed.
+ *
+ * Uses the QuickselectAdaptive method of Alexandrescu. This is based on the
+ * median-of-medians algorithm. The median sample is strategy is chosen based on
+ * the target index.
+ *
+ * Differences to QA
+ *
+ * This function is not as configurable as {@link #partitionQA(double[], int[], int)};
+ * it is composed of the best performing configuration from benchmarking.
+ *
+ * A key difference is that this method allows starting with Floyd-Rivest sub-sampling,
+ * then progression to QuickselectAdaptive sampling, before disabling of sampling. This
+ * method can thus have two attempts at sampling (FR, then QA) before disabling sampling. The
+ * method can also be configured to start at QA sampling, or skip QA sampling when starting
+ * with FR sampling depending on the configured starting mode and increment
+ * (see {@link #configureQaAdaptive(int, int)}).
+ *
+ * @param data Values.
+ * @param k Indices (may be destructively modified).
+ * @param n Count of indices.
+ */
+ static void partitionQA2(double[] data, int[] k, int n) {
+ quickSelectAdaptive2(data, k, n, qaMode);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k} and
+ * any index {@code i}:
+ *
+ * The method assumes all {@code k} are valid indices into the data. It handles NaN
+ * and signed zeros in the data.
+ *
+ * WARNING: Currently this only supports a single or range of {@code k}. For parity
+ * with other select methods this accepts an array {@code k} and pre/post processes
+ * the data for NaN and signed zeros.
+ *
+ * @param a Values.
+ * @param k Indices (may be destructively modified).
+ * @param count Count of indices.
+ * @param flags Adaption flags.
+ */
+ static void quickSelectAdaptive2(double[] a, int[] k, int count, int flags) {
+ // Handle NaN / signed zeros
+ final DoubleDataTransformer t = SORT_TRANSFORMER.get();
+ // Assume this is in-place
+ t.preProcess(a);
+ final int end = t.length();
+ int n = count;
+ if (end > 1) {
+ // Filter indices invalidated by NaN check
+ if (end < a.length) {
+ for (int i = n; --i >= 0;) {
+ final int v = k[i];
+ if (v >= end) {
+ // swap(k, i, --n)
+ k[i] = k[--n];
+ k[n] = v;
+ }
+ }
+ }
+ if (n != 0) {
+ final int ka = Math.min(k[0], k[n - 1]);
+ final int kb = Math.max(k[0], k[n - 1]);
+ quickSelectAdaptive2(a, 0, end - 1, ka, kb, new int[1], flags);
+ }
+ }
+ // Restore signed zeros
+ t.postProcess(a, k, n);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their
+ * correctly sorted value in the equivalent fully sorted array.
+ *
+ * For all indices {@code [ka, kb]} and any index {@code i}:
+ *
+ * This function accepts indices {@code [ka, kb]} that define the
+ * range of indices to partition. It is expected that the range is small.
+ *
+ * Uses the QuickselectAdaptive method of Alexandrescu. This is based on the
+ * median-of-medians algorithm. The median sample is strategy is chosen based on
+ * the target index.
+ *
+ * The control {@code flags} are used to control the sampling mode and adaption of
+ * the index within the sample.
+ *
+ * Returns the bounds containing {@code [ka, kb]}. These may be lower/higher
+ * than the keys if equal values are present in the data.
+ *
+ * @param a Values.
+ * @param left Lower bound of data (inclusive, assumed to be strictly positive).
+ * @param right Upper bound of data (inclusive, assumed to be strictly positive).
+ * @param ka First key of interest.
+ * @param kb Last key of interest.
+ * @param bounds Upper bound of the range containing {@code [ka, kb]} (inclusive).
+ * @param flags Adaption flags.
+ * @return Lower bound of the range containing {@code [ka, kb]} (inclusive).
+ */
+ private static int quickSelectAdaptive2(double[] a, int left, int right, int ka, int kb,
+ int[] bounds, int flags) {
+ int l = left;
+ int r = right;
+ int m = flags;
+ while (true) {
+ // Select when ka and kb are close to the same end
+ // |l|-----|ka|kkkkkkkk|kb|------|r|
+ if (Math.min(kb - l, r - ka) < LINEAR_SORTSELECT_SIZE) {
+ sortSelectRange(a, l, r, ka, kb);
+ bounds[0] = kb;
+ return ka;
+ }
+
+ // Only target ka; kb is assumed to be close
+ int p0;
+ final int n = r - l;
+ // f in [0, 1]
+ final double f = (double) (ka - l) / n;
+ // Record the larger margin (start at 1/4) to create the estimated size.
+ // step L R
+ // far left 1/12 1/3 (use 1/4 + 1/32 + 1/64 ~ 0.328)
+ // left 1/6 1/4
+ // middle 2/9 2/9 (use 1/4 - 1/32 ~ 0.219)
+ int margin = n >> 2;
+ if (m < MODE_SAMPLING && r - l > SELECT_SUB_SAMPLING_SIZE) {
+ // Floyd-Rivest sample step uses the same margins
+ p0 = sampleStep(a, l, r, ka, bounds, flags);
+ if (f <= STEP_FAR_LEFT || f >= STEP_FAR_RIGHT) {
+ margin += (n >> 5) + (n >> 6);
+ } else if (f > STEP_LEFT && f < STEP_RIGHT) {
+ margin -= n >> 5;
+ }
+ } else if (f <= STEP_LEFT) {
+ if (f <= STEP_FAR_LEFT) {
+ margin += (n >> 5) + (n >> 6);
+ p0 = repeatedStepFarLeft(a, l, r, ka, bounds, m);
+ } else {
+ p0 = repeatedStepLeft(a, l, r, ka, bounds, m);
+ }
+ } else if (f >= STEP_RIGHT) {
+ if (f >= STEP_FAR_RIGHT) {
+ margin += (n >> 5) + (n >> 6);
+ p0 = repeatedStepFarRight(a, l, r, ka, bounds, m);
+ } else {
+ p0 = repeatedStepRight(a, l, r, ka, bounds, m);
+ }
+ } else {
+ margin -= n >> 5;
+ p0 = repeatedStep(a, l, r, ka, bounds, m);
+ }
+
+ // Note: Here we expect [ka, kb] to be small and splitting is unlikely.
+ // p0 p1
+ // |l|--|ka|kkkk|kb|--|P|-------------------|r|
+ // |l|----------------|P|--|ka|kkk|kb|------|r|
+ // |l|-----------|ka|k|P|k|kb|--------------|r|
+ final int p1 = bounds[0];
+ if (kb < p0) {
+ // Entirely on left side
+ r = p0 - 1;
+ } else if (ka > p1) {
+ // Entirely on right side
+ l = p1 + 1;
+ } else {
+ // Pivot splits [ka, kb]. Expect ends to be close to the pivot and finish.
+ // Here we set the bounds for use after median-of-medians pivot selection.
+ // In the event there are many equal values this allows collecting those
+ // known to be equal together when moving around the medians sample.
+ if (kb > p1) {
+ sortSelectLeft(a, p1 + 1, r, kb);
+ bounds[0] = kb;
+ }
+ if (ka < p0) {
+ sortSelectRight(a, l, p0 - 1, ka);
+ p0 = ka;
+ }
+ return p0;
+ }
+ // Update mode based on target partition size
+ m += r - l > n - margin ? qaIncrement : 0;
+ }
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Implements {@link SPEPartitionFunction}. This method is not static as the
+ * pivot strategy and minimum quick select size are used within the method.
+ *
+ * Note: Handles signed zeros.
+ *
+ * Uses a Bentley-McIlroy quicksort partition method by Sedgewick.
+ *
+ * @param data Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @param leftInner Flag to indicate {@code left - 1} is a pivot.
+ * @param rightInner Flag to indicate {@code right + 1} is a pivot.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private int partitionSBMWithZeros(double[] data, int left, int right, int[] upper,
+ boolean leftInner, boolean rightInner) {
+ // Single-pivot Bentley-McIlroy quicksort handling equal keys (Sedgewick's algorithm).
+ //
+ // Partition data using pivot P into less-than, greater-than or equal.
+ // P is placed at the end to act as a sentinel.
+ // k traverses the unknown region ??? and values moved if equal (l) or greater (g):
+ //
+ // left p i j q right
+ // | ==P | P | ==P |P|
+ //
+ // At the end P and additional equal values are swapped back to the centre.
+ //
+ // | P |
+ //
+ // Adapted from Sedgewick "Quicksort is optimal"
+ // https://sedgewick.io/wp-content/themes/sedgewick/talks/2002QuicksortIsOptimal.pdf
+ //
+ // The algorithm has been changed so that:
+ // - A pivot point must be provided.
+ // - An edge case where the search meets in the middle is handled.
+ // - Equal value data is not swapped to the end. Since the value is fixed then
+ // only the less than / greater than value must be moved from the end inwards.
+ // The end is then assumed to be the equal value. This would not work with
+ // object references. Equivalent swap calls are commented.
+ // - Added a fast-forward over initial range containing the pivot.
+
+ // Switch to insertion sort for small range
+ if (right - left <= minQuickSelectSize) {
+ Sorting.sort(data, left, right, leftInner);
+ fixContinuousSignedZeros(data, left, right);
+ upper[0] = right;
+ return left;
+ }
+
+ final int l = left;
+ final int r = right;
+
+ int p = l;
+ int q = r;
+
+ // Use the pivot index to set the upper sentinel value.
+ // Pass -1 as the target k (should trigger an IOOBE if strategy uses it).
+ final int pivot = pivotingStrategy.pivotIndex(data, left, right, -1);
+ final double v = data[pivot];
+ data[pivot] = data[r];
+ data[r] = v;
+
+ // Special case: count signed zeros
+ int c = 0;
+ if (v == 0) {
+ c = countMixedSignedZeros(data, left, right);
+ }
+
+ // Fast-forward over equal regions to reduce swaps
+ while (data[p] == v) {
+ if (++p == q) {
+ // Edge-case: constant value
+ if (c != 0) {
+ sortZero(data, left, right);
+ }
+ upper[0] = right;
+ return left;
+ }
+ }
+ // Cannot overrun as the prior scan using p stopped before the end
+ while (data[q - 1] == v) {
+ q--;
+ }
+
+ int i = p - 1;
+ int j = q;
+
+ for (;;) {
+ do {
+ ++i;
+ } while (data[i] < v);
+ while (v < data[--j]) {
+ if (j == l) {
+ break;
+ }
+ }
+ if (i >= j) {
+ // Edge-case if search met on an internal pivot value
+ // (not at the greater equal region, i.e. i < q).
+ // Move this to the lower-equal region.
+ if (i == j && v == data[i]) {
+ //swap(data, i++, p++)
+ data[i] = data[p];
+ data[p] = v;
+ i++;
+ p++;
+ }
+ break;
+ }
+ //swap(data, i, j)
+ final double vi = data[j];
+ final double vj = data[i];
+ data[i] = vi;
+ data[j] = vj;
+ // Move the equal values to the ends
+ if (vi == v) {
+ //swap(data, i, p++)
+ data[i] = data[p];
+ data[p] = v;
+ p++;
+ }
+ if (vj == v) {
+ //swap(data, j, --q)
+ data[j] = data[--q];
+ data[q] = v;
+ }
+ }
+ // i is at the end (exclusive) of the less-than region
+
+ // Place pivot value in centre
+ //swap(data, r, i)
+ data[r] = data[i];
+ data[i] = v;
+
+ // Move equal regions to the centre.
+ // Set the pivot range [j, i) and move this outward for equal values.
+ j = i++;
+
+ // less-equal:
+ // for (int k = l; k < p; k++):
+ // swap(data, k, --j)
+ // greater-equal:
+ // for (int k = r; k-- > q; i++) {
+ // swap(data, k, i)
+
+ // Move the minimum of less-equal or less-than
+ int move = Math.min(p - l, j - p);
+ final int lower = j - (p - l);
+ for (int k = l; move-- > 0; k++) {
+ data[k] = data[--j];
+ data[j] = v;
+ }
+ // Move the minimum of greater-equal or greater-than
+ move = Math.min(r - q, q - i);
+ upper[0] = i + (r - q) - 1;
+ for (int k = r; move-- > 0; i++) {
+ data[--k] = data[i];
+ data[i] = v;
+ }
+
+ // Special case: fixed signed zeros
+ if (c != 0) {
+ p = lower;
+ while (c-- > 0) {
+ data[p++] = -0.0;
+ }
+ while (p <= upper[0]) {
+ data[p++] = 0.0;
+ }
+ }
+
+ // Equal in [lower, upper]
+ return lower;
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Uses a single pivot partition method. This method does not handle equal values
+ * at the pivot location: {@code lower == upper}. The method conforms to the
+ * {@link SPEPartition} interface to allow use with the single-pivot introselect method.
+ *
+ * @param data Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param pivot Pivot index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private static int partitionSP(double[] data, int l, int r, int pivot, int[] upper) {
+ // Partition data using pivot P into less-than or greater-than.
+ //
+ // Adapted from Floyd and Rivest (1975)
+ // Algorithm 489: The Algorithm SELECT—for Finding the ith Smallest of n elements.
+ // Comm. ACM. 18 (3): 173.
+ //
+ // Sub-script range checking has been eliminated by appropriate placement
+ // of values at the ends to act as sentinels.
+ //
+ // left i j right
+ // |<=P| P |>=P|
+ //
+ // At the end P is swapped back to the centre.
+ //
+ // | P |
+ final double v = data[pivot];
+ // swap(left, pivot)
+ data[pivot] = data[l];
+ if (data[r] > v) {
+ // swap(right, left)
+ data[l] = data[r];
+ data[r] = v;
+ // Here after the first swap: a[l] = v; a[r] > v
+ } else {
+ data[l] = v;
+ // Here after the first swap: a[l] <= v; a[r] = v
+ }
+ int i = l;
+ int j = r;
+ while (i < j) {
+ // swap(i, j)
+ final double temp = data[i];
+ data[i] = data[j];
+ data[j] = temp;
+ do {
+ ++i;
+ } while (data[i] < v);
+ do {
+ --j;
+ } while (data[j] > v);
+ }
+ // Move pivot back to the correct location from either l or r
+ if (data[l] == v) {
+ // data[j] <= v : swap(left, j)
+ data[l] = data[j];
+ data[j] = v;
+ } else {
+ // data[j+1] > v : swap(j+1, right)
+ data[r] = data[++j];
+ data[j] = v;
+ }
+ upper[0] = j;
+ return j;
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Uses a Bentley-McIlroy quicksort partition method.
+ *
+ * @param data Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param pivot Initial index of the pivot.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private static int partitionBM(double[] data, int l, int r, int pivot, int[] upper) {
+ // Single-pivot Bentley-McIlroy quicksort handling equal keys.
+ //
+ // Adapted from program 7 in Bentley-McIlroy (1993)
+ // Engineering a sort function
+ // SOFTWARE—PRACTICE AND EXPERIENCE, VOL.23(11), 1249–1265
+ //
+ // 3-way partition of the data using a pivot value into
+ // less-than, equal or greater-than.
+ //
+ // First partition data into 4 reqions by scanning the unknown region from
+ // left (i) and right (j) and moving equal values to the ends:
+ // i-> <-j
+ // l p | | q r
+ // | == | < | ??? | > | == |
+ //
+ // <-j
+ // l p i q r
+ // | == | < | > | == |
+ //
+ // Then the equal values are copied from the ends to the centre:
+ // | less | equal | greater |
+
+ int i = l;
+ int j = r;
+ int p = l;
+ int q = r;
+
+ final double v = data[pivot];
+
+ for (;;) {
+ while (i <= j && data[i] <= v) {
+ if (data[i] == v) {
+ //swap(data, i, p++)
+ data[i] = data[p];
+ data[p] = v;
+ p++;
+ }
+ i++;
+ }
+ while (j >= i && data[j] >= v) {
+ if (v == data[j]) {
+ //swap(data, j, q--)
+ data[j] = data[q];
+ data[q] = v;
+ q--;
+ }
+ j--;
+ }
+ if (i > j) {
+ break;
+ }
+ //swap(data, i++, j--)
+ final double tmp = data[j];
+ data[j] = data[i];
+ data[i] = tmp;
+ }
+
+ // Move equal regions to the centre.
+ int s = Math.min(p - l, i - p);
+ for (int k = l; s > 0; k++, s--) {
+ //swap(data, k, i - s)
+ data[k] = data[i - s];
+ data[i - s] = v;
+ }
+ s = Math.min(q - j, r - q);
+ for (int k = i; --s >= 0; k++) {
+ //swap(data, r - s, k)
+ data[r - s] = data[k];
+ data[k] = v;
+ }
+
+ // Set output range
+ i = i - p + l;
+ j = j - q + r;
+ upper[0] = j;
+
+ return i;
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Uses a Bentley-McIlroy quicksort partition method by Sedgewick.
+ *
+ * @param data Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param pivot Pivot index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ static int partitionSBM(double[] data, int l, int r, int pivot, int[] upper) {
+ // Single-pivot Bentley-McIlroy quicksort handling equal keys (Sedgewick's algorithm).
+ //
+ // Partition data using pivot P into less-than, greater-than or equal.
+ // P is placed at the end to act as a sentinel.
+ // k traverses the unknown region ??? and values moved if equal (l) or greater (g):
+ //
+ // left p i j q right
+ // | ==P | P | ==P |P|
+ //
+ // At the end P and additional equal values are swapped back to the centre.
+ //
+ // | P |
+ //
+ // Adapted from Sedgewick "Quicksort is optimal"
+ // https://sedgewick.io/wp-content/themes/sedgewick/talks/2002QuicksortIsOptimal.pdf
+ //
+ // Note: The difference between this and the original BM partition is the use of
+ // < or > rather than <= and >=. This allows the pivot to act as a sentinel and removes
+ // the requirement for checks on i; and j can be checked against an unlikely condition.
+ // This method will swap runs of equal values.
+ //
+ // The algorithm has been changed so that:
+ // - A pivot point must be provided.
+ // - An edge case where the search meets in the middle is handled.
+ // - Added a fast-forward over any initial range containing the pivot.
+ // - Changed the final move to perform the minimum moves.
+
+ // Use the pivot index to set the upper sentinel value
+ final double v = data[pivot];
+ data[pivot] = data[r];
+ data[r] = v;
+
+ int p = l;
+ int q = r;
+
+ // Fast-forward over equal regions to reduce swaps
+ while (data[p] == v) {
+ if (++p == q) {
+ // Edge-case: constant value
+ upper[0] = r;
+ return l;
+ }
+ }
+ // Cannot overrun as the prior scan using p stopped before the end
+ while (data[q - 1] == v) {
+ q--;
+ }
+
+ int i = p - 1;
+ int j = q;
+
+ for (;;) {
+ do {
+ ++i;
+ } while (data[i] < v);
+ while (v < data[--j]) {
+ // Cannot use j == i in the event that i == q (already passed j)
+ if (j == l) {
+ break;
+ }
+ }
+ if (i >= j) {
+ // Edge-case if search met on an internal pivot value
+ // (not at the greater equal region, i.e. i < q).
+ // Move this to the lower-equal region.
+ if (i == j && v == data[i]) {
+ //swap(data, i++, p++)
+ data[i] = data[p];
+ data[p] = v;
+ i++;
+ p++;
+ }
+ break;
+ }
+ //swap(data, i, j)
+ final double vi = data[j];
+ final double vj = data[i];
+ data[i] = vi;
+ data[j] = vj;
+ // Move the equal values to the ends
+ if (vi == v) {
+ //swap(data, i, p++)
+ data[i] = data[p];
+ data[p] = v;
+ p++;
+ }
+ if (vj == v) {
+ //swap(data, j, --q)
+ data[j] = data[--q];
+ data[q] = v;
+ }
+ }
+ // i is at the end (exclusive) of the less-than region
+
+ // Place pivot value in centre
+ //swap(data, r, i)
+ data[r] = data[i];
+ data[i] = v;
+
+ // Move equal regions to the centre.
+ // Set the pivot range [j, i) and move this outward for equal values.
+ j = i++;
+
+ // less-equal:
+ // for k = l; k < p; k++
+ // swap(data, k, --j)
+ // greater-equal:
+ // for k = r; k-- > q; i++
+ // swap(data, k, i)
+
+ // Move the minimum of less-equal or less-than
+ int move = Math.min(p - l, j - p);
+ final int lower = j - (p - l);
+ for (int k = l; --move >= 0; k++) {
+ data[k] = data[--j];
+ data[j] = v;
+ }
+ // Move the minimum of greater-equal or greater-than
+ move = Math.min(r - q, q - i);
+ upper[0] = i + (r - q) - 1;
+ for (int k = r; --move >= 0; i++) {
+ data[--k] = data[i];
+ data[i] = v;
+ }
+
+ // Equal in [lower, upper]
+ return lower;
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Uses a Bentley-McIlroy quicksort partition method by Kiwiel.
+ *
+ * @param x Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param pivot Pivot index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ static int partitionKBM(double[] x, int l, int r, int pivot, int[] upper) {
+ // Single-pivot Bentley-McIlroy quicksort handling equal keys.
+ //
+ // Partition data using pivot v into less-than, greater-than or equal.
+ // The basic idea is to work with the 5 inner parts of the array [ll, rr]
+ // by positioning sentinels at l and r:
+ //
+ // |l |ll p| |i j| |q rr| r| (6.1)
+ // | Uses a Dutch-National-Flag method handling equal keys (version 1).
+ *
+ * @param data Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param pivot Pivot index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private static int partitionDNF1(double[] data, int left, int right, int pivot, int[] upper) {
+ // Dutch National Flag partitioning:
+ // https://www.baeldung.com/java-sorting-arrays-with-repeated-entries
+ // https://en.wikipedia.org/wiki/Dutch_national_flag_problem
+
+ // Partition data using pivot P into less-than, greater-than or equal.
+ // i traverses the unknown region ??? and values moved to the correct end.
+ //
+ // left lt i gt right
+ // | < P | P | ??? | > P |
+ //
+ // We can delay filling in [lt, gt) with P until the end and only
+ // move values in the wrong place.
+
+ final double value = data[pivot];
+
+ // Fast-forward initial less-than region
+ int lt = left;
+ while (data[lt] < value) {
+ lt++;
+ }
+
+ // Pointers positioned to use pre-increment/decrement
+ lt--;
+ int gt = right + 1;
+
+ // DNF partitioning which inspects one position per loop iteration
+ for (int i = lt; ++i < gt;) {
+ final double v = data[i];
+ if (v < value) {
+ data[++lt] = v;
+ } else if (v > value) {
+ data[i] = data[--gt];
+ data[gt] = v;
+ // Ensure data[i] is inspected next time
+ i--;
+ }
+ // else v == value and is in the central region to fill at the end
+ }
+
+ // Equal in (lt, gt) so adjust to [lt, gt]
+ ++lt;
+ upper[0] = --gt;
+
+ // Fill the equal values gap
+ for (int i = lt; i <= gt; i++) {
+ data[i] = value;
+ }
+
+ return lt;
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Uses a Dutch-National-Flag method handling equal keys (version 2).
+ *
+ * @param data Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param pivot Pivot index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private static int partitionDNF2(double[] data, int left, int right, int pivot, int[] upper) {
+ // Dutch National Flag partitioning:
+ // https://www.baeldung.com/java-sorting-arrays-with-repeated-entries
+ // https://en.wikipedia.org/wiki/Dutch_national_flag_problem
+
+ // Partition data using pivot P into less-than, greater-than or equal.
+ // i traverses the unknown region ??? and values moved to the correct end.
+ //
+ // left lt i gt right
+ // | < P | P | ??? | > P |
+ //
+ // We can delay filling in [lt, gt) with P until the end and only
+ // move values in the wrong place.
+
+ final double value = data[pivot];
+
+ // Fast-forward initial less-than region
+ int lt = left;
+ while (data[lt] < value) {
+ lt++;
+ }
+
+ // Pointers positioned to use pre-increment/decrement: ++x / --x
+ lt--;
+ int gt = right + 1;
+
+ // Modified DNF partitioning with fast-forward of the greater-than
+ // pointer. Note the fast-forward must check bounds.
+ for (int i = lt; ++i < gt;) {
+ final double v = data[i];
+ if (v < value) {
+ data[++lt] = v;
+ } else if (v > value) {
+ // Fast-forward here:
+ do {
+ --gt;
+ } while (gt > i && data[gt] > value);
+ // here data[gt] <= value
+ // if data[gt] == value we can skip over it
+ if (data[gt] < value) {
+ data[++lt] = data[gt];
+ }
+ // Move v to the >P side
+ data[gt] = v;
+ }
+ // else v == value and is in the central region to fill at the end
+ }
+
+ // Equal in (lt, gt) so adjust to [lt, gt]
+ ++lt;
+ upper[0] = --gt;
+
+ // Fill the equal values gap
+ for (int i = lt; i <= gt; i++) {
+ data[i] = value;
+ }
+
+ return lt;
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Uses a Dutch-National-Flag method handling equal keys (version 3).
+ *
+ * @param data Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param pivot Pivot index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private static int partitionDNF3(double[] data, int left, int right, int pivot, int[] upper) {
+ // Dutch National Flag partitioning:
+ // https://www.baeldung.com/java-sorting-arrays-with-repeated-entries
+ // https://en.wikipedia.org/wiki/Dutch_national_flag_problem
+
+ // Partition data using pivot P into less-than, greater-than or equal.
+ // i traverses the unknown region ??? and values moved to the correct end.
+ //
+ // left lt i gt right
+ // | < P | P | ??? | > P |
+ //
+ // This version writes in the value of P as it traverses. Any subsequent
+ // less-than values will overwrite P values trailing behind i.
+
+ final double value = data[pivot];
+
+ // Fast-forward initial less-than region
+ int lt = left;
+ while (data[lt] < value) {
+ lt++;
+ }
+
+ // Pointers positioned to use pre-increment/decrement: ++x / --x
+ lt--;
+ int gt = right + 1;
+
+ // Note:
+ // This benchmarks as faster than DNF1 and equal to DNF2 on random data.
+ // On data with (many) repeat values it is faster than DNF2.
+ // Both DNF2 & 3 have fast-forward of the gt pointer.
+
+ // Modified DNF partitioning with fast-forward of the greater-than
+ // pointer. Here we write in the pivot value at i during the sweep.
+ // This acts as a sentinel when fast-forwarding greater-than.
+ // It is over-written by any future pivot
+ for (int i = lt; ++i < gt;) {
+ final double v = data[i];
+ if (v != value) {
+ // Overwrite with the pivot value
+ data[i] = value;
+ if (v < value) {
+ // Move v to the value)
+ do {
+ --gt;
+ } while (data[gt] > value);
+ // Now data[gt] <= value
+ // if data[gt] == value we can skip over it
+ if (data[gt] < value) {
+ data[++lt] = data[gt];
+ }
+ // Move v to the >P side
+ data[gt] = v;
+ }
+ }
+ }
+
+ // Equal in (lt, gt) so adjust to [lt, gt]
+ ++lt;
+ upper[0] = --gt;
+
+ // In contrast to version 1 and 2 there is no requirement to fill the central
+ // region with the pivot value as it was filled during the sweep
+
+ return lt;
+ }
+
+ /**
+ * Partition an array slice around 2 pivots. Partitioning exchanges array elements
+ * such that all elements smaller than pivot are before it and all elements larger
+ * than pivot are after it.
+ *
+ * Uses a dual-pivot quicksort method by Vladimir Yaroslavskiy.
+ *
+ * This method assumes {@code a[pivot1] <= a[pivot2]}.
+ * If {@code pivot1 == pivot2} this triggers a switch to a single-pivot method.
+ * It is assumed this indicates that choosing two pivots failed due to many equal
+ * values. In this case the single-pivot method uses a Dutch National Flag algorithm
+ * suitable for many equal values.
+ *
+ * This method returns 4 points describing the pivot ranges of equal values.
+ *
+ * P |
+ * } Bounds are set so {@code i < k0}, {@code i > k3} and {@code k1 < i < k2} are
+ * unsorted. When the range {@code [k0, k3]} contains fully sorted elements the result
+ * is set to {@code k1 = k3; k2 == k0}. This can occur if
+ * {@code P1 == P2} or there are zero or 1 value between the pivots
+ * {@code P1 < v < P2}. Any sort/partition of ranges [left, k0-1], [k1+1, k2-1] and
+ * [k3+1, right] must check the length is {@code > 1}.
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param bounds Points [k1, k2, k3].
+ * @param pivot1 Pivot1 location.
+ * @param pivot2 Pivot2 location.
+ * @return Lower bound (inclusive) of the pivot range [k0].
+ */
+ static int partitionDP(double[] a, int left, int right, int pivot1, int pivot2, int[] bounds) {
+ // Allow caller to choose a single-pivot
+ if (pivot1 == pivot2) {
+ // Switch to a single pivot sort. This is used when there are
+ // estimated to be many equal values so use the fastest equal
+ // value single pivot method.
+ final int lower = partitionDNF3(a, left, right, pivot1, bounds);
+ // Set dual pivot range
+ bounds[2] = bounds[0];
+ // No unsorted internal region (set k1 = k3; k2 = k0)
+ // Note: It is extra work for the caller to detect that this region can be skipped.
+ bounds[1] = lower;
+ return lower;
+ }
+
+ // Dual-pivot quicksort method by Vladimir Yaroslavskiy.
+ //
+ // Partition data using pivots P1 and P2 into less-than, greater-than or between.
+ // Pivot values P1 & P2 are placed at the end. If P1 < P2, P2 acts as a sentinel.
+ // k traverses the unknown region ??? and values moved if less-than (lt) or
+ // greater-than (gt):
+ //
+ // left lt k gt right
+ // |P1| P | ??? |
+ * } This method requires that {@code left < start && end < right}. It supports
+ * {@code start == end}.
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param start Start of the partition range (inclusive).
+ * @param end End of the partitioned range (inclusive).
+ * @param pivot0 Lower pivot location (inclusive).
+ * @param pivot1 Upper pivot location (inclusive).
+ * @param upper Upper bound (inclusive) of the pivot range [k1].
+ * @return Lower bound (inclusive) of the pivot range [k0].
+ */
+ private static int expandPartitionT1(double[] a, int left, int right, int start, int end,
+ int pivot0, int pivot1, int[] upper) {
+ // 3-way partition of the data using a pivot value into
+ // less-than, equal or greater-than.
+ // Based on Sedgewick's Bentley-McIroy partitioning: always swap i<->j then
+ // check for equal to the pivot and move again.
+ //
+ // Move sentinels from start and end to left and right. Scan towards the
+ // sentinels until >=,<=. Swap then move == to the pivot region.
+ // <-i j->
+ // |l | | |p0 p1| | | r|
+ // |>=| ??? | < | == | > | ??? |<=|
+ //
+ // When either i or j reach the edge perform finishing loop.
+ // Finish loop for a[j] <= v replaces j with p1+1, moves value
+ // to p0 for < and updates the pivot range p1 (and optionally p0):
+ // j->
+ // |l |p0 p1| | | r|
+ // | < | == | > | ??? |<=|
+
+ // Positioned for pre-in/decrement to write to pivot region
+ int p0 = pivot0;
+ int p1 = pivot1;
+ final double v = a[p0];
+ if (a[left] < v) {
+ // a[left] is not a sentinel
+ final double w = a[left];
+ if (a[right] > v) {
+ // Most likely case: ends can be sentinels
+ a[left] = a[right];
+ a[right] = w;
+ } else {
+ // a[right] is a sentinel; use pivot for left
+ a[left] = v;
+ a[p0] = w;
+ p0++;
+ }
+ } else if (a[right] > v) {
+ // a[right] is not a sentinel; use pivot
+ a[p1] = a[right];
+ p1--;
+ a[right] = v;
+ }
+
+ // Required to avoid index bound error first use of i/j
+ assert left < start && end < right;
+ int i = start;
+ int j = end;
+ while (true) {
+ do {
+ --i;
+ } while (a[i] < v);
+ do {
+ ++j;
+ } while (a[j] > v);
+ final double vj = a[i];
+ final double vi = a[j];
+ a[i] = vi;
+ a[j] = vj;
+ // Move the equal values to pivot region
+ if (vi == v) {
+ a[i] = a[--p0];
+ a[p0] = v;
+ }
+ if (vj == v) {
+ a[j] = a[++p1];
+ a[p1] = v;
+ }
+ // Termination check and finishing loops.
+ // Note: this works even if pivot region is zero length (p1 == p0-1)
+ // due to pivot use as a sentinel on one side because we pre-inc/decrement
+ // one side and post-inc/decrement the other side.
+ if (i == left) {
+ while (j < right) {
+ do {
+ ++j;
+ } while (a[j] > v);
+ final double w = a[j];
+ // Move upper bound of pivot region
+ a[j] = a[++p1];
+ a[p1] = v;
+ if (w != v) {
+ // Move lower bound of pivot region
+ a[p0] = w;
+ p0++;
+ }
+ }
+ break;
+ }
+ if (j == right) {
+ while (i > left) {
+ do {
+ --i;
+ } while (a[i] < v);
+ final double w = a[i];
+ // Move lower bound of pivot region
+ a[i] = a[--p0];
+ a[p0] = v;
+ if (w != v) {
+ // Move upper bound of pivot region
+ a[p1] = w;
+ p1--;
+ }
+ }
+ break;
+ }
+ }
+
+ upper[0] = p1;
+ return p0;
+ }
+
+ /**
+ * Expand a partition around a single pivot. Partitioning exchanges array
+ * elements such that all elements smaller than pivot are before it and all
+ * elements larger than pivot are after it. The central region is already
+ * partitioned.
+ *
+ * P | ??? |
+ * } This is similar to {@link #expandPartitionT1(double[], int, int, int, int, int, int, int[])}
+ * with a change to binary partitioning. It requires that {@code left < start && end < right}.
+ * It supports {@code start == end}.
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param start Start of the partition range (inclusive).
+ * @param end End of the partitioned range (inclusive).
+ * @param pivot0 Lower pivot location (inclusive).
+ * @param pivot1 Upper pivot location (inclusive).
+ * @param upper Upper bound (inclusive) of the pivot range [k1].
+ * @return Lower bound (inclusive) of the pivot range [k0].
+ */
+ private static int expandPartitionB1(double[] a, int left, int right, int start, int end,
+ int pivot0, int pivot1, int[] upper) {
+ // 2-way partition of the data using a pivot value into
+ // less-than, or greater-than.
+ //
+ // Move sentinels from start and end to left and right. Scan towards the
+ // sentinels until >=,<= then swap.
+ // <-i j->
+ // |l | | | p| | | r|
+ // |>=| ??? | < |==| > | ??? |<=|
+ //
+ // When either i or j reach the edge perform finishing loop.
+ // Finish loop for a[j] <= v replaces j with p1+1, moves value to p
+ // and moves the pivot up:
+ // j->
+ // |l | p| | | r|
+ // | < |==| > | ??? |<=|
+
+ // Pivot may be moved to use as a sentinel
+ int p = pivot0;
+ final double v = a[p];
+ if (a[left] < v) {
+ // a[left] is not a sentinel
+ final double w = a[left];
+ if (a[right] > v) {
+ // Most likely case: ends can be sentinels
+ a[left] = a[right];
+ a[right] = w;
+ } else {
+ // a[right] is a sentinel; use pivot for left
+ a[left] = v;
+ a[p] = w;
+ p++;
+ }
+ } else if (a[right] > v) {
+ // a[right] is not a sentinel; use pivot
+ a[p] = a[right];
+ p--;
+ a[right] = v;
+ }
+
+ // Required to avoid index bound error first use of i/j
+ assert left < start && end < right;
+ int i = start;
+ int j = end;
+ while (true) {
+ do {
+ --i;
+ } while (a[i] < v);
+ do {
+ ++j;
+ } while (a[j] > v);
+ final double vj = a[i];
+ final double vi = a[j];
+ a[i] = vi;
+ a[j] = vj;
+ // Termination check and finishing loops.
+ // These reset the pivot if it was moved then slide it as required.
+ if (i == left) {
+ // Reset the pivot and sentinel
+ if (p < pivot0) {
+ // Pivot is in right; a[p] <= v
+ a[right] = a[p];
+ a[p] = v;
+ } else if (p > pivot0) {
+ // Pivot was in left (now swapped to j); a[p] >= v
+ a[j] = a[p];
+ a[p] = v;
+ }
+ if (j == right) {
+ break;
+ }
+ while (j < right) {
+ do {
+ ++j;
+ } while (a[j] > v);
+ // Move pivot
+ a[p] = a[j];
+ a[j] = a[++p];
+ a[p] = v;
+ }
+ break;
+ }
+ if (j == right) {
+ // Reset the pivot and sentinel
+ if (p < pivot0) {
+ // Pivot was in right (now swapped to i); a[p] <= v
+ a[i] = a[p];
+ a[p] = v;
+ } else if (p > pivot0) {
+ // Pivot is in left; a[p] >= v
+ a[left] = a[p];
+ a[p] = v;
+ }
+ if (i == left) {
+ break;
+ }
+ while (i > left) {
+ do {
+ --i;
+ } while (a[i] < v);
+ // Move pivot
+ a[p] = a[i];
+ a[i] = a[--p];
+ a[p] = v;
+ }
+ break;
+ }
+ }
+
+ upper[0] = p;
+ return p;
+ }
+
+ /**
+ * Expand a partition around a single pivot. Partitioning exchanges array
+ * elements such that all elements smaller than pivot are before it and all
+ * elements larger than pivot are after it. The central region is already
+ * partitioned.
+ *
+ * P | ??? |
+ * } This is similar to {@link #expandPartitionT1(double[], int, int, int, int, int, int, int[])}
+ * with a change to how the end-point sentinels are created. It does not use the pivot
+ * but uses values at start and end. This increases the length of the lower/upper ranges
+ * by 1 for the main scan. It requires that {@code start != end}. However it handles
+ * {@code left == start} and/or {@code end == right}.
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param start Start of the partition range (inclusive).
+ * @param end End of the partitioned range (inclusive).
+ * @param pivot0 Lower pivot location (inclusive).
+ * @param pivot1 Upper pivot location (inclusive).
+ * @param upper Upper bound (inclusive) of the pivot range [k1].
+ * @return Lower bound (inclusive) of the pivot range [k0].
+ */
+ private static int expandPartitionT2(double[] a, int left, int right, int start, int end,
+ int pivot0, int pivot1, int[] upper) {
+ // 3-way partition of the data using a pivot value into
+ // less-than, equal or greater-than.
+ // Based on Sedgewick's Bentley-McIroy partitioning: always swap i<->j then
+ // check for equal to the pivot and move again.
+ //
+ // Move sentinels from start and end to left and right. Scan towards the
+ // sentinels until >=,<=. Swap then move == to the pivot region.
+ // <-i j->
+ // |l | | |p0 p1| | | r|
+ // |>=| ??? | < | == | > | ??? |<=|
+ //
+ // When either i or j reach the edge perform finishing loop.
+ // Finish loop for a[j] <= v replaces j with p1+1, optionally moves value
+ // to p0 for < and updates the pivot range p1 (and optionally p0):
+ // j->
+ // |l |p0 p1| | | r|
+ // | < | == | > | ??? |<=|
+
+ final double v = a[pivot0];
+ // Use start/end as sentinels.
+ // This requires start != end
+ assert start != end;
+ double vi = a[start];
+ double vj = a[end];
+ a[start] = a[left];
+ a[end] = a[right];
+ a[left] = vj;
+ a[right] = vi;
+
+ int i = start + 1;
+ int j = end - 1;
+
+ // Positioned for pre-in/decrement to write to pivot region
+ int p0 = pivot0 == start ? i : pivot0;
+ int p1 = pivot1 == end ? j : pivot1;
+
+ while (true) {
+ do {
+ --i;
+ } while (a[i] < v);
+ do {
+ ++j;
+ } while (a[j] > v);
+ vj = a[i];
+ vi = a[j];
+ a[i] = vi;
+ a[j] = vj;
+ // Move the equal values to pivot region
+ if (vi == v) {
+ a[i] = a[--p0];
+ a[p0] = v;
+ }
+ if (vj == v) {
+ a[j] = a[++p1];
+ a[p1] = v;
+ }
+ // Termination check and finishing loops.
+ // Note: this works even if pivot region is zero length (p1 == p0-1
+ // due to single length pivot region at either start/end) because we
+ // pre-inc/decrement one side and post-inc/decrement the other side.
+ if (i == left) {
+ while (j < right) {
+ do {
+ ++j;
+ } while (a[j] > v);
+ final double w = a[j];
+ // Move upper bound of pivot region
+ a[j] = a[++p1];
+ a[p1] = v;
+ // Move lower bound of pivot region
+ //p0 += w != v ? 1 : 0;
+ if (w != v) {
+ a[p0] = w;
+ p0++;
+ }
+ }
+ break;
+ }
+ if (j == right) {
+ while (i > left) {
+ do {
+ --i;
+ } while (a[i] < v);
+ final double w = a[i];
+ // Move lower bound of pivot region
+ a[i] = a[--p0];
+ a[p0] = v;
+ // Move upper bound of pivot region
+ //p1 -= w != v ? 1 : 0;
+ if (w != v) {
+ a[p1] = w;
+ p1--;
+ }
+ }
+ break;
+ }
+ }
+
+ upper[0] = p1;
+ return p0;
+ }
+
+ /**
+ * Expand a partition around a single pivot. Partitioning exchanges array
+ * elements such that all elements smaller than pivot are before it and all
+ * elements larger than pivot are after it. The central region is already
+ * partitioned.
+ *
+ * P | ??? |
+ * } This is similar to {@link #expandPartitionT2(double[], int, int, int, int, int, int, int[])}
+ * with a change to binary partitioning. It is simpler than
+ * {@link #expandPartitionB1(double[], int, int, int, int, int, int, int[])} as the pivot is
+ * not moved. It requires that {@code start != end}. However it handles
+ * {@code left == start} and/or {@code end == right}.
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param start Start of the partition range (inclusive).
+ * @param end End of the partitioned range (inclusive).
+ * @param pivot0 Lower pivot location (inclusive).
+ * @param pivot1 Upper pivot location (inclusive).
+ * @param upper Upper bound (inclusive) of the pivot range [k1].
+ * @return Lower bound (inclusive) of the pivot range [k0].
+ */
+ private static int expandPartitionB2(double[] a, int left, int right, int start, int end,
+ int pivot0, int pivot1, int[] upper) {
+ // 2-way partition of the data using a pivot value into
+ // less-than, or greater-than.
+ //
+ // Move sentinels from start and end to left and right. Scan towards the
+ // sentinels until >=,<= then swap.
+ // <-i j->
+ // |l | | | p| | | r|
+ // |>=| ??? | < |==| > | ??? |<=|
+ //
+ // When either i or j reach the edge perform finishing loop.
+ // Finish loop for a[j] <= v replaces j with p1+1, moves value to p
+ // and moves the pivot up:
+ // j->
+ // |l | p| | | r|
+ // | < |==| > | ??? |<=|
+
+ // Pivot
+ int p = pivot0;
+ final double v = a[p];
+ // Use start/end as sentinels.
+ // This requires start != end
+ assert start != end;
+ // Note: Must not move pivot as this invalidates the finishing loops.
+ // See logic in method B1 to see added complexity of pivot location.
+ // This method is not better than T2 for data with no repeat elements
+ // and is slower for repeat elements when used with the improved
+ // versions (e.g. linearBFPRTImproved). So for this edge case just use B1.
+ if (p == start || p == end) {
+ return expandPartitionB1(a, left, right, start, end, pivot0, pivot1, upper);
+ }
+ double vi = a[start];
+ double vj = a[end];
+ a[start] = a[left];
+ a[end] = a[right];
+ a[left] = vj;
+ a[right] = vi;
+
+ int i = start + 1;
+ int j = end - 1;
+ while (true) {
+ do {
+ --i;
+ } while (a[i] < v);
+ do {
+ ++j;
+ } while (a[j] > v);
+ vj = a[i];
+ vi = a[j];
+ a[i] = vi;
+ a[j] = vj;
+ // Termination check and finishing loops
+ if (i == left) {
+ while (j < right) {
+ do {
+ ++j;
+ } while (a[j] > v);
+ // Move pivot
+ a[p] = a[j];
+ a[j] = a[++p];
+ a[p] = v;
+ }
+ break;
+ }
+ if (j == right) {
+ while (i > left) {
+ do {
+ --i;
+ } while (a[i] < v);
+ // Move pivot
+ a[p] = a[i];
+ a[i] = a[--p];
+ a[p] = v;
+ }
+ break;
+ }
+ }
+
+ upper[0] = p;
+ return p;
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * The index {@code k} is the target element. This method ignores this value.
+ * The value is included to match the method signature of the {@link SPEPartition} interface.
+ * Assumes the range {@code r - l >= 4}; the caller is responsible for selection on a smaller
+ * range.
+ *
+ * Uses the Blum, Floyd, Pratt, Rivest, and Tarjan (BFPRT) median-of-medians algorithm
+ * with medians of 5 with the sample medians computed in the first quintile.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private int linearBFPRTBaseline(double[] a, int l, int r, int k, int[] upper) {
+ // Adapted from Alexandrescu (2016), algorithm 3.
+ // Moves the responsibility for selection when r-l <= 4 to the caller.
+ // Compute the median of each contiguous set of 5 to the first quintile.
+ int rr = l - 1;
+ for (int e = l + 4; e <= r; e += 5) {
+ Sorting.median5d(a, e - 4, e - 3, e - 2, e - 1, e);
+ // Median to first quintile
+ final double v = a[e - 2];
+ a[e - 2] = a[++rr];
+ a[rr] = v;
+ }
+ final int m = (l + rr + 1) >>> 1;
+ // mutual recursion
+ quickSelect(this::linearBFPRTBaseline, a, l, rr, m, m, upper);
+ // Note: repartions already partitioned data [l, rr]
+ return spFunction.partition(a, l, r, m, upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * The index {@code k} is the target element. This method ignores this value.
+ * The value is included to match the method signature of the {@link SPEPartition} interface.
+ * Assumes the range {@code r - l >= 8}; the caller is responsible for selection on a smaller
+ * range.
+ *
+ * Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with medians of 3 with the samples computed in the first tertile and 9th-tile.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private int linearRepeatedStepBaseline(double[] a, int l, int r, int k, int[] upper) {
+ // Adapted from Alexandrescu (2016), algorithm 5.
+ // Moves the responsibility for selection when r-l <= 8 to the caller.
+ // Compute the median of each contiguous set of 3 to the first tertile, and repeat.
+ int j = l - 1;
+ for (int e = l + 2; e <= r; e += 3) {
+ Sorting.sort3(a, e - 2, e - 1, e);
+ // Median to first tertile
+ final double v = a[e - 1];
+ a[e - 1] = a[++j];
+ a[j] = v;
+ }
+ int rr = l - 1;
+ for (int e = l + 2; e <= j; e += 3) {
+ Sorting.sort3(a, e - 2, e - 1, e);
+ // Median to first 9th-tile
+ final double v = a[e - 1];
+ a[e - 1] = a[++rr];
+ a[rr] = v;
+ }
+ final int m = (l + rr + 1) >>> 1;
+ // mutual recursion
+ quickSelect(this::linearRepeatedStepBaseline, a, l, rr, m, m, upper);
+ // Note: repartions already partitioned data [l, rr]
+ return spFunction.partition(a, l, r, m, upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * The index {@code k} is the target element. This method ignores this value.
+ * The value is included to match the method signature of the {@link SPEPartition} interface.
+ * Assumes the range {@code r - l >= 4}; the caller is responsible for selection on a smaller
+ * range.
+ *
+ * Uses the Blum, Floyd, Pratt, Rivest, and Tarjan (BFPRT) median-of-medians algorithm
+ * with medians of 5 with the sample medians computed in the central quintile.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private int linearBFPRTImproved(double[] a, int l, int r, int k, int[] upper) {
+ // Adapted from Alexandrescu (2016), algorithm 6.
+ // Moves the responsibility for selection when r-l <= 4 to the caller.
+ // Compute the median of each non-contiguous set of 5 to the middle quintile.
+ final int f = (r - l + 1) / 5;
+ final int f3 = 3 * f;
+ // middle quintile: [2f:3f)
+ final int s = l + (f << 1);
+ final int e = s + f - 1;
+ for (int i = l, j = s; i < s; i += 2, j++) {
+ Sorting.median5d(a, i, i + 1, j, f3 + i, f3 + i + 1);
+ }
+ // Adaption to target kf/|A|
+ //final int p = s + mapDistance(k - l, l, r, f);
+ final int p = s + noSamplingAdapt.mapDistance(k - l, l, r, f);
+ // mutual recursion
+ quickSelect(this::linearBFPRTImproved, a, s, e, p, p, upper);
+ return expandFunction.partition(a, l, r, s, e, upper[0], upper[1], upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * The index {@code k} is the target element. This method ignores this value.
+ * The value is included to match the method signature of the {@link SPEPartition} interface.
+ * Assumes the range {@code r - l >= 8}; the caller is responsible for selection on a smaller
+ * range.
+ *
+ * Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with medians of 3 with the samples computed in the middle tertile and 9th-tile.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private int linearRepeatedStepImproved(double[] a, int l, int r, int k, int[] upper) {
+ // Adapted from Alexandrescu (2016), algorithm 7.
+ // Moves the responsibility for selection when r-l <= 8 to the caller.
+ // Compute the median of each non-contiguous set of 3 to the middle tertile, and repeat.
+ final int f = (r - l + 1) / 9;
+ final int f3 = 3 * f;
+ // i in middle tertile [3f:6f)
+ for (int i = l + f3, e = l + (f3 << 1); i < e; i++) {
+ Sorting.sort3(a, i - f3, i, i + f3);
+ }
+ // i in middle 9th-tile: [4f:5f)
+ final int s = l + (f << 2);
+ final int e = s + f - 1;
+ for (int i = s; i <= e; i++) {
+ Sorting.sort3(a, i - f, i, i + f);
+ }
+ // Adaption to target kf/|A|
+ //final int p = s + mapDistance(k - l, l, r, f);
+ final int p = s + noSamplingAdapt.mapDistance(k - l, l, r, f);
+ // mutual recursion
+ quickSelect(this::linearRepeatedStepImproved, a, s, e, p, p, upper);
+ return expandFunction.partition(a, l, r, s, e, upper[0], upper[1], upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Assumes the range {@code r - l >= 8}; the caller is responsible for selection on a smaller
+ * range. If using a 12th-tile for sampling then assumes {@code r - l >= 11}.
+ *
+ * Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with the median of 3 then median of 3; the final sample is placed in the
+ * 5th 9th-tile; the pivot chosen from the sample is adaptive using the input {@code k}.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @param mode Adaption mode.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private int repeatedStep(double[] a, int l, int r, int k, int[] upper, AdaptMode mode) {
+ // Adapted from Alexandrescu (2016), algorithm 8.
+ // Moves the responsibility for selection when r-l <= 8 to the caller.
+ int f;
+ int s;
+ int p;
+ if (!mode.isSampleMode()) {
+ // i in tertile [3f:6f)
+ f = (r - l + 1) / 9;
+ final int f3 = 3 * f;
+ for (int i = l + f3, end = l + (f3 << 1); i < end; i++) {
+ Sorting.sort3(a, i - f3, i, i + f3);
+ }
+ // 5th 9th-tile: [4f:5f)
+ s = l + (f << 2);
+ p = s + (mode.isAdapt() ? noSamplingAdapt.mapDistance(k - l, l, r, f) : (f >>> 1));
+ } else {
+ if ((controlFlags & FLAG_QA_MIDDLE_12) != 0) {
+ // Switch to a 12th-tile as used in the other methods.
+ f = (r - l + 1) / 12;
+ // middle - f/2
+ s = ((r + l) >>> 1) - (f >> 1);
+ } else {
+ f = (r - l + 1) / 9;
+ s = l + (f << 2);
+ }
+ // Adaption to target kf'/|A|
+ int kp = mode.isAdapt() ? samplingAdapt.mapDistance(k - l, l, r, f) : (f >>> 1);
+ // Centre the sample at k
+ if ((controlFlags & FLAG_QA_SAMPLE_K) != 0) {
+ s = k - kp;
+ }
+ p = s + kp;
+ }
+ final int e = s + f - 1;
+ for (int i = s; i <= e; i++) {
+ Sorting.sort3(a, i - f, i, i + f);
+ }
+ p = quickSelectAdaptive(a, s, e, p, p, upper,
+ (controlFlags & FLAG_QA_PROPAGATE) != 0 ? mode : adaptMode);
+ return expandFunction.partition(a, l, r, s, e, p, upper[0], upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Assumes the range {@code r - l >= 11}; the caller is responsible for selection on a smaller
+ * range.
+ *
+ * Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with the lower median of 4 then either median of 3 with the final sample placed in the
+ * 5th 12th-tile, or min of 3 with the final sample in the 4th 12th-tile;
+ * the pivot chosen from the sample is adaptive using the input {@code k}.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @param mode Adaption mode.
+ * @param far Set to {@code true} to perform repeatedStepFarLeft.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private int repeatedStepLeft(double[] a, int l, int r, int k, int[] upper, AdaptMode mode,
+ boolean far) {
+ // Adapted from Alexandrescu (2016), algorithm 9 and 10.
+ // Moves the responsibility for selection when r-l <= 11 to the caller.
+ final int f = (r - l + 1) >> 2;
+ if (!mode.isSampleMode()) {
+ // i in 2nd quartile
+ final int f2 = f + f;
+ for (int i = l + f, e = l + f2; i < e; i++) {
+ Sorting.lowerMedian4(a, i - f, i, i + f, i + f2);
+ }
+ }
+ int fp = f / 3;
+ int s;
+ int e;
+ int p;
+ if (far) {
+ // i in 4th 12th-tile
+ s = l + f;
+ // Variable adaption
+ int kp;
+ if (!mode.isSampleMode()) {
+ kp = mode.isAdapt() ? noSamplingEdgeAdapt.mapDistance(k - l, l, r, fp) : fp >>> 1;
+ } else {
+ kp = mode.isAdapt() ? samplingEdgeAdapt.mapDistance(k - l, l, r, fp) : fp >>> 1;
+ // Note: Not possible to centre the sample at k on the far step
+ }
+ e = s + fp - 1;
+ p = s + kp;
+ final int fp2 = fp << 1;
+ for (int i = s; i <= e; i++) {
+ // min into i
+ if (a[i] > a[i + fp]) {
+ final double u = a[i];
+ a[i] = a[i + fp];
+ a[i + fp] = u;
+ }
+ if (a[i] > a[i + fp2]) {
+ final double v = a[i];
+ a[i] = a[i + fp2];
+ a[i + fp2] = v;
+ }
+ }
+ } else {
+ // i in 5th 12th-tile
+ s = l + f + fp;
+ // Variable adaption
+ int kp;
+ if (!mode.isSampleMode()) {
+ kp = mode.isAdapt() ? noSamplingAdapt.mapDistance(k - l, l, r, fp) : fp >>> 1;
+ } else {
+ kp = mode.isAdapt() ? samplingAdapt.mapDistance(k - l, l, r, fp) : fp >>> 1;
+ // Centre the sample at k
+ if ((controlFlags & FLAG_QA_SAMPLE_K) != 0) {
+ // Avoid bounds error due to rounding as (k-l)/(r-l) -> 1/12
+ s = Math.max(k - kp, l + fp);
+ }
+ }
+ e = s + fp - 1;
+ p = s + kp;
+ for (int i = s; i <= e; i++) {
+ Sorting.sort3(a, i - fp, i, i + fp);
+ }
+ }
+ p = quickSelectAdaptive(a, s, e, p, p, upper,
+ (controlFlags & FLAG_QA_PROPAGATE) != 0 ? mode : adaptMode);
+ return expandFunction.partition(a, l, r, s, e, p, upper[0], upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Assumes the range {@code r - l >= 11}; the caller is responsible for selection on a smaller
+ * range.
+ *
+ * Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with the upper median of 4 then either median of 3 with the final sample placed in the
+ * 8th 12th-tile, or max of 3 with the final sample in the 9th 12th-tile;
+ * the pivot chosen from the sample is adaptive using the input {@code k}.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @param mode Adaption mode.
+ * @param far Set to {@code true} to perform repeatedStepFarRight.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private int repeatedStepRight(double[] a, int l, int r, int k, int[] upper, AdaptMode mode,
+ boolean far) {
+ // Mirror image repeatedStepLeft using upper median into 3rd quartile
+ final int f = (r - l + 1) >> 2;
+ if (!mode.isSampleMode()) {
+ // i in 3rd quartile
+ final int f2 = f + f;
+ for (int i = r - f, e = r - f2; i > e; i--) {
+ Sorting.upperMedian4(a, i - f2, i - f, i, i + f);
+ }
+ }
+ int fp = f / 3;
+ int s;
+ int e;
+ int p;
+ if (far) {
+ // i in 9th 12th-tile
+ e = r - f;
+ // Variable adaption
+ int kp;
+ if (!mode.isSampleMode()) {
+ kp = mode.isAdapt() ? noSamplingEdgeAdapt.mapDistance(r - k, l, r, fp) : fp >>> 1;
+ } else {
+ kp = mode.isAdapt() ? samplingEdgeAdapt.mapDistance(r - k, l, r, fp) : fp >>> 1;
+ // Note: Not possible to centre the sample at k on the far step
+ }
+ s = e - fp + 1;
+ p = e - kp;
+ final int fp2 = fp << 1;
+ for (int i = s; i <= e; i++) {
+ // max into i
+ if (a[i] < a[i - fp]) {
+ final double u = a[i];
+ a[i] = a[i - fp];
+ a[i - fp] = u;
+ }
+ if (a[i] < a[i - fp2]) {
+ final double v = a[i];
+ a[i] = a[i - fp2];
+ a[i - fp2] = v;
+ }
+ }
+ } else {
+ // i in 8th 12th-tile
+ e = r - f - fp;
+ // Variable adaption
+ int kp;
+ if (!mode.isSampleMode()) {
+ kp = mode.isAdapt() ? noSamplingAdapt.mapDistance(r - k, l, r, fp) : fp >>> 1;
+ } else {
+ kp = mode.isAdapt() ? samplingAdapt.mapDistance(r - k, l, r, fp) : fp >>> 1;
+ // Centre the sample at k
+ if ((controlFlags & FLAG_QA_SAMPLE_K) != 0) {
+ // Avoid bounds error due to rounding as (r-k)/(r-l) -> 11/12
+ e = Math.min(k + kp, r - fp);
+ }
+ }
+ s = e - fp + 1;
+ p = e - kp;
+ for (int i = s; i <= e; i++) {
+ Sorting.sort3(a, i - fp, i, i + fp);
+ }
+ }
+ p = quickSelectAdaptive(a, s, e, p, p, upper,
+ (controlFlags & FLAG_QA_PROPAGATE) != 0 ? mode : adaptMode);
+ return expandFunction.partition(a, l, r, s, e, p, upper[0], upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Assumes the range {@code r - l >= 11}; the caller is responsible for selection on a smaller
+ * range.
+ *
+ * Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with the minimum of 4 then median of 3; the final sample is placed in the
+ * 2nd 12th-tile; the pivot chosen from the sample is adaptive using the input {@code k}.
+ *
+ * Given a pivot in the middle of the sample this has margins of 1/12 and 1/3.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @param mode Adaption mode.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private int repeatedStepFarLeft(double[] a, int l, int r, int k, int[] upper, AdaptMode mode) {
+ // Moves the responsibility for selection when r-l <= 11 to the caller.
+ final int f = (r - l + 1) >> 2;
+ int fp = f / 3;
+ // 2nd 12th-tile
+ int s = l + fp;
+ final int e = s + fp - 1;
+ int p;
+ if (!mode.isSampleMode()) {
+ p = s + (mode.isAdapt() ? noSamplingEdgeAdapt.mapDistance(k - l, l, r, fp) : fp >>> 1);
+ // i in 2nd quartile; min into i-f (1st quartile)
+ final int f2 = f + f;
+ for (int i = l + f, end = l + f2; i < end; i++) {
+ if (a[i + f] < a[i - f]) {
+ final double u = a[i + f];
+ a[i + f] = a[i - f];
+ a[i - f] = u;
+ }
+ if (a[i + f2] < a[i]) {
+ final double v = a[i + f2];
+ a[i + f2] = a[i];
+ a[i] = v;
+ }
+ if (a[i] < a[i - f]) {
+ final double u = a[i];
+ a[i] = a[i - f];
+ a[i - f] = u;
+ }
+ }
+ } else {
+ int kp = mode.isAdapt() ? samplingEdgeAdapt.mapDistance(k - l, l, r, fp) : fp >>> 1;
+ p = s + kp;
+ }
+ for (int i = s; i <= e; i++) {
+ Sorting.sort3(a, i - fp, i, i + fp);
+ }
+ p = quickSelectAdaptive(a, s, e, p, p, upper,
+ (controlFlags & FLAG_QA_PROPAGATE) != 0 ? mode : adaptMode);
+ return expandFunction.partition(a, l, r, s, e, p, upper[0], upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Assumes the range {@code r - l >= 11}; the caller is responsible for selection on a smaller
+ * range.
+ *
+ * Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with the maximum of 4 then median of 3; the final sample is placed in the
+ * 11th 12th-tile; the pivot chosen from the sample is adaptive using the input {@code k}.
+ *
+ * Given a pivot in the middle of the sample this has margins of 1/3 and 1/12.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @param mode Adaption mode.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private int repeatedStepFarRight(double[] a, int l, int r, int k, int[] upper, AdaptMode mode) {
+ // Mirror image repeatedStepFarLeft
+ final int f = (r - l + 1) >> 2;
+ int fp = f / 3;
+ // 11th 12th-tile
+ int e = r - fp;
+ final int s = e - fp + 1;
+ int p;
+ if (!mode.isSampleMode()) {
+ p = e - (mode.isAdapt() ? noSamplingEdgeAdapt.mapDistance(r - k, l, r, fp) : fp >>> 1);
+ // i in 3rd quartile; max into i+f (4th quartile)
+ final int f2 = f + f;
+ for (int i = r - f, end = r - f2; i > end; i--) {
+ if (a[i - f] > a[i + f]) {
+ final double u = a[i - f];
+ a[i - f] = a[i + f];
+ a[i + f] = u;
+ }
+ if (a[i - f2] > a[i]) {
+ final double v = a[i - f2];
+ a[i - f2] = a[i];
+ a[i] = v;
+ }
+ if (a[i] > a[i + f]) {
+ final double u = a[i];
+ a[i] = a[i + f];
+ a[i + f] = u;
+ }
+ }
+ } else {
+ int kp = mode.isAdapt() ? samplingEdgeAdapt.mapDistance(r - k, l, r, fp) : fp >>> 1;
+ p = e - kp;
+ }
+ for (int i = s; i <= e; i++) {
+ Sorting.sort3(a, i - fp, i, i + fp);
+ }
+ p = quickSelectAdaptive(a, s, e, p, p, upper,
+ (controlFlags & FLAG_QA_PROPAGATE) != 0 ? mode : adaptMode);
+ return expandFunction.partition(a, l, r, s, e, p, upper[0], upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Partitions a Floyd-Rivest sample around a pivot offset so that the input {@code k} will
+ * fall in the smaller partition when the entire range is partitioned.
+ *
+ * Assumes the range {@code r - l} is large; the original Floyd-Rivest size for sampling
+ * was 600.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @param mode Adaption mode.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private int sampleStep(double[] a, int l, int r, int k, int[] upper, AdaptMode mode) {
+ // Floyd-Rivest: use SELECT recursively on a sample of size S to get an estimate
+ // for the (k-l+1)-th smallest element into a[k], biased slightly so that the
+ // (k-l+1)-th element is expected to lie in the smaller set after partitioning.
+ final int n = r - l + 1;
+ final int ith = k - l + 1;
+ final double z = Math.log(n);
+ // sample size = 0.5 * n^(2/3)
+ final double s = 0.5 * Math.exp(0.6666666666666666 * z);
+ final double sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * Integer.signum(ith - (n >> 1));
+ final int ll = Math.max(l, (int) (k - ith * s / n + sd));
+ final int rr = Math.min(r, (int) (k + (n - ith) * s / n + sd));
+ // Optional random sampling.
+ // Support two variants.
+ if ((controlFlags & FLAG_QA_RANDOM_SAMPLING) != 0) {
+ final IntUnaryOperator rng = createRNG(n, k);
+ if (ll == l) {
+ // Shuffle [l, rr] from [l, r]
+ for (int i = l - 1; i < rr;) {
+ // r - rand [0, r - i] : i is currently i-1
+ final int j = r - rng.applyAsInt(r - i);
+ final double t = a[++i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ } else if (rr == r) {
+ // Shuffle [ll, r] from [l, r]
+ for (int i = r + 1; i > ll;) {
+ // l + rand [0, i - l] : i is currently i+1
+ final int j = l + rng.applyAsInt(i - l);
+ final double t = a[--i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ } else {
+ // Sample range [ll, rr] is internal
+ // Shuffle [ll, k) from [l, k)
+ for (int i = k; i > ll;) {
+ // l + rand [0, i - l + 1) : i is currently i+1
+ final int j = l + rng.applyAsInt(i - l);
+ final double t = a[--i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ // Shuffle (k, rr] from (k, r]
+ for (int i = k; i < rr;) {
+ // r - rand [0, r - i + 1) : i is currently i-1
+ final int j = r - rng.applyAsInt(r - i);
+ final double t = a[++i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ } else if ((controlFlags & FLAG_RANDOM_SAMPLING) != 0) {
+ final IntUnaryOperator rng = createRNG(n, k);
+ // Shuffle [ll, k) from [l, k)
+ if (ll > l) {
+ for (int i = k; i > ll;) {
+ // l + rand [0, i - l + 1) : i is currently i+1
+ final int j = l + rng.applyAsInt(i - l);
+ final double t = a[--i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ // Shuffle (k, rr] from (k, r]
+ if (rr < r) {
+ for (int i = k; i < rr;) {
+ // r - rand [0, r - i + 1) : i is currently i-1
+ final int j = r - rng.applyAsInt(r - i);
+ final double t = a[++i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ }
+ // Sample recursion restarts from [ll, rr]
+ final int p = quickSelectAdaptive(a, ll, rr, k, k, upper,
+ (controlFlags & FLAG_QA_PROPAGATE) != 0 ? mode : adaptMode);
+
+ // Expect a small sample and repartition the entire range...
+ // Does not support a pivot range so use the centre
+ //return spFunction.partition(a, l, r, (p + upper[0]) >>> 1, upper);
+
+ return expandFunction.partition(a, l, r, ll, rr, p, upper[0], upper);
+ }
+
+ /**
+ * Map the distance from the edge of {@code [l, r]} to a new distance in {@code [0, n)}.
+ *
+ * The provides the adaption {@code kf'/|A|} from Alexandrescu (2016) where
+ * {@code k == d}, {@code f' == n} and {@code |A| == r-l+1}.
+ *
+ * For convenience this accepts the input range {@code [l, r]}.
+ *
+ * @param d Distance from the edge in {@code [0, r - l]}.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param n Size of the new range.
+ * @return the mapped distance in [0, n)
+ */
+ private static int mapDistance(int d, int l, int r, int n) {
+ return (int) (d * (n - 1.0) / (r - l));
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Assumes the range {@code r - l >= 8}; the caller is responsible for selection on a smaller
+ * range. If using a 12th-tile for sampling then assumes {@code r - l >= 11}.
+ *
+ * Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with the median of 3 then median of 3; the final sample is placed in the
+ * 5th 9th-tile; the pivot chosen from the sample is adaptive using the input {@code k}.
+ *
+ * Given a pivot in the middle of the sample this has margins of 2/9 and 2/9.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @param flags Control flags.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private static int repeatedStep(double[] a, int l, int r, int k, int[] upper, int flags) {
+ // Adapted from Alexandrescu (2016), algorithm 8.
+ int fp;
+ int s;
+ int p;
+ if (flags <= MODE_SAMPLING) {
+ // Median into a 12th-tile
+ fp = (r - l + 1) / 12;
+ // Position the sample around the target k
+ s = k - mapDistance(k - l, l, r, fp);
+ p = k;
+ } else {
+ // i in tertile [3f':6f')
+ fp = (r - l + 1) / 9;
+ final int f3 = 3 * fp;
+ for (int i = l + f3, end = l + (f3 << 1); i < end; i++) {
+ Sorting.sort3(a, i - f3, i, i + f3);
+ }
+ // 5th 9th-tile: [4f':5f')
+ s = l + (fp << 2);
+ // No adaption uses the middle to enforce strict margins
+ p = s + (flags == MODE_ADAPTION ? mapDistance(k - l, l, r, fp) : (fp >>> 1));
+ }
+ final int e = s + fp - 1;
+ for (int i = s; i <= e; i++) {
+ Sorting.sort3(a, i - fp, i, i + fp);
+ }
+ p = quickSelectAdaptive2(a, s, e, p, p, upper, qaMode);
+ return expandPartitionT2(a, l, r, s, e, p, upper[0], upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Assumes the range {@code r - l >= 11}; the caller is responsible for selection on a smaller
+ * range.
+ *
+ * Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with the lower median of 4 then either median of 3 with the final sample placed in the
+ * 5th 12th-tile, or min of 3 with the final sample in the 4th 12th-tile;
+ * the pivot chosen from the sample is adaptive using the input {@code k}.
+ *
+ * Given a pivot in the middle of the sample this has margins of 1/6 and 1/4.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @param flags Control flags.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private static int repeatedStepLeft(double[] a, int l, int r, int k, int[] upper, int flags) {
+ // Adapted from Alexandrescu (2016), algorithm 9.
+ int fp;
+ int s;
+ int p;
+ if (flags <= MODE_SAMPLING) {
+ // Median into a 12th-tile
+ fp = (r - l + 1) / 12;
+ // Position the sample around the target k
+ // Avoid bounds error due to rounding as (k-l)/(r-l) -> 1/12
+ s = Math.max(k - mapDistance(k - l, l, r, fp), l + fp);
+ p = k;
+ } else {
+ // i in 2nd quartile
+ final int f = (r - l + 1) >> 2;
+ final int f2 = f + f;
+ for (int i = l + f, end = l + f2; i < end; i++) {
+ Sorting.lowerMedian4(a, i - f, i, i + f, i + f2);
+ }
+ // i in 5th 12th-tile
+ fp = f / 3;
+ s = l + f + fp;
+ // No adaption uses the middle to enforce strict margins
+ p = s + (flags == MODE_ADAPTION ? mapDistance(k - l, l, r, fp) : (fp >>> 1));
+ }
+ final int e = s + fp - 1;
+ for (int i = s; i <= e; i++) {
+ Sorting.sort3(a, i - fp, i, i + fp);
+ }
+ p = quickSelectAdaptive2(a, s, e, p, p, upper, qaMode);
+ return expandPartitionT2(a, l, r, s, e, p, upper[0], upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Assumes the range {@code r - l >= 11}; the caller is responsible for selection on a smaller
+ * range.
+ *
+ * Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with the upper median of 4 then either median of 3 with the final sample placed in the
+ * 8th 12th-tile, or max of 3 with the final sample in the 9th 12th-tile;
+ * the pivot chosen from the sample is adaptive using the input {@code k}.
+ *
+ * Given a pivot in the middle of the sample this has margins of 1/4 and 1/6.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @param flags Control flags.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private static int repeatedStepRight(double[] a, int l, int r, int k, int[] upper, int flags) {
+ // Mirror image repeatedStepLeft using upper median into 3rd quartile
+ int fp;
+ int e;
+ int p;
+ if (flags <= MODE_SAMPLING) {
+ // Median into a 12th-tile
+ fp = (r - l + 1) / 12;
+ // Position the sample around the target k
+ // Avoid bounds error due to rounding as (r-k)/(r-l) -> 11/12
+ e = Math.min(k + mapDistance(r - k, l, r, fp), r - fp);
+ p = k;
+ } else {
+ // i in 3rd quartile
+ final int f = (r - l + 1) >> 2;
+ final int f2 = f + f;
+ for (int i = r - f, end = r - f2; i > end; i--) {
+ Sorting.upperMedian4(a, i - f2, i - f, i, i + f);
+ }
+ // i in 8th 12th-tile
+ fp = f / 3;
+ e = r - f - fp;
+ // No adaption uses the middle to enforce strict margins
+ p = e - (flags == MODE_ADAPTION ? mapDistance(r - k, l, r, fp) : (fp >>> 1));
+ }
+ final int s = e - fp + 1;
+ for (int i = s; i <= e; i++) {
+ Sorting.sort3(a, i - fp, i, i + fp);
+ }
+ p = quickSelectAdaptive2(a, s, e, p, p, upper, qaMode);
+ return expandPartitionT2(a, l, r, s, e, p, upper[0], upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Assumes the range {@code r - l >= 11}; the caller is responsible for selection on a smaller
+ * range.
+ *
+ * Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with the minimum of 4 then median of 3; the final sample is placed in the
+ * 2nd 12th-tile; the pivot chosen from the sample is adaptive using the input {@code k}.
+ *
+ * Given a pivot in the middle of the sample this has margins of 1/12 and 1/3.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @param flags Control flags.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private static int repeatedStepFarLeft(double[] a, int l, int r, int k, int[] upper, int flags) {
+ // Far step has been changed from the Alexandrescu (2016) step of lower-median-of-4, min-of-3
+ // into the 4th 12th-tile to a min-of-4, median-of-3 into the 2nd 12th-tile.
+ // The differences are:
+ // - The upper margin when not sampling is 8/24 vs. 9/24; the lower margin remains at 1/12.
+ // - The position of the sample is closer to the expected location of k < |A| / 12.
+ // - Sampling mode uses a median-of-3 with adaptive k, matching the other step methods.
+ // A min-of-3 sample can create a pivot too small if used with adaption of k leaving
+ // k in the larger partition and a wasted iteration.
+ // - Adaption is adjusted to force use of the lower margin when not sampling.
+ int fp;
+ int s;
+ int p;
+ if (flags <= MODE_SAMPLING) {
+ // 2nd 12th-tile
+ fp = (r - l + 1) / 12;
+ s = l + fp;
+ // Use adaption
+ p = s + mapDistance(k - l, l, r, fp);
+ } else {
+ // i in 2nd quartile; min into i-f (1st quartile)
+ final int f = (r - l + 1) >> 2;
+ final int f2 = f + f;
+ for (int i = l + f, end = l + f2; i < end; i++) {
+ if (a[i + f] < a[i - f]) {
+ final double u = a[i + f];
+ a[i + f] = a[i - f];
+ a[i - f] = u;
+ }
+ if (a[i + f2] < a[i]) {
+ final double v = a[i + f2];
+ a[i + f2] = a[i];
+ a[i] = v;
+ }
+ if (a[i] < a[i - f]) {
+ final double u = a[i];
+ a[i] = a[i - f];
+ a[i - f] = u;
+ }
+ }
+ // 2nd 12th-tile
+ fp = f / 3;
+ s = l + fp;
+ // Lower margin has 2(d+1) elements; d == (position in sample) - s
+ // Force k into the lower margin
+ p = s + ((k - l) >>> 1);
+ }
+ final int e = s + fp - 1;
+ for (int i = s; i <= e; i++) {
+ Sorting.sort3(a, i - fp, i, i + fp);
+ }
+ p = quickSelectAdaptive2(a, s, e, p, p, upper, qaMode);
+ return expandPartitionT2(a, l, r, s, e, p, upper[0], upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Assumes the range {@code r - l >= 11}; the caller is responsible for selection on a smaller
+ * range.
+ *
+ * Uses the Chen and Dumitrescu repeated step median-of-medians-of-medians algorithm
+ * with the maximum of 4 then median of 3; the final sample is placed in the
+ * 11th 12th-tile; the pivot chosen from the sample is adaptive using the input {@code k}.
+ *
+ * Given a pivot in the middle of the sample this has margins of 1/3 and 1/12.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @param flags Control flags.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private static int repeatedStepFarRight(double[] a, int l, int r, int k, int[] upper, int flags) {
+ // Mirror image repeatedStepFarLeft
+ int fp;
+ int e;
+ int p;
+ if (flags <= MODE_SAMPLING) {
+ // 11th 12th-tile
+ fp = (r - l + 1) / 12;
+ e = r - fp;
+ // Use adaption
+ p = e - mapDistance(r - k, l, r, fp);
+ } else {
+ // i in 3rd quartile; max into i+f (4th quartile)
+ final int f = (r - l + 1) >> 2;
+ final int f2 = f + f;
+ for (int i = r - f, end = r - f2; i > end; i--) {
+ if (a[i - f] > a[i + f]) {
+ final double u = a[i - f];
+ a[i - f] = a[i + f];
+ a[i + f] = u;
+ }
+ if (a[i - f2] > a[i]) {
+ final double v = a[i - f2];
+ a[i - f2] = a[i];
+ a[i] = v;
+ }
+ if (a[i] > a[i + f]) {
+ final double u = a[i];
+ a[i] = a[i + f];
+ a[i + f] = u;
+ }
+ }
+ // 11th 12th-tile
+ fp = f / 3;
+ e = r - fp;
+ // Upper margin has 2(d+1) elements; d == e - (position in sample)
+ // Force k into the upper margin
+ p = e - ((r - k) >>> 1);
+ }
+ final int s = e - fp + 1;
+ for (int i = s; i <= e; i++) {
+ Sorting.sort3(a, i - fp, i, i + fp);
+ }
+ p = quickSelectAdaptive2(a, s, e, p, p, upper, qaMode);
+ return expandPartitionT2(a, l, r, s, e, p, upper[0], upper);
+ }
+
+ /**
+ * Partition an array slice around a pivot. Partitioning exchanges array elements such
+ * that all elements smaller than pivot are before it and all elements larger than
+ * pivot are after it.
+ *
+ * Partitions a Floyd-Rivest sample around a pivot offset so that the input {@code k} will
+ * fall in the smaller partition when the entire range is partitioned.
+ *
+ * Assumes the range {@code r - l} is large.
+ *
+ * @param a Data array.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @param k Target index.
+ * @param upper Upper bound (inclusive) of the pivot range.
+ * @param flags Control flags.
+ * @return Lower bound (inclusive) of the pivot range.
+ */
+ private static int sampleStep(double[] a, int l, int r, int k, int[] upper, int flags) {
+ // Floyd-Rivest: use SELECT recursively on a sample of size S to get an estimate
+ // for the (k-l+1)-th smallest element into a[k], biased slightly so that the
+ // (k-l+1)-th element is expected to lie in the smaller set after partitioning.
+ final int n = r - l + 1;
+ final int ith = k - l + 1;
+ final double z = Math.log(n);
+ // sample size = 0.5 * n^(2/3)
+ final double s = 0.5 * Math.exp(0.6666666666666666 * z);
+ final double sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * Integer.signum(ith - (n >> 1));
+ final int ll = Math.max(l, (int) (k - ith * s / n + sd));
+ final int rr = Math.min(r, (int) (k + (n - ith) * s / n + sd));
+ // Note: Random sampling is not supported.
+ // Sample recursion restarts from [ll, rr]
+ final int p = quickSelectAdaptive2(a, ll, rr, k, k, upper, qaMode);
+ return expandPartitionT2(a, l, r, ll, rr, p, upper[0], upper);
+ }
+
+ /**
+ * Move NaN values to the end of the array.
+ * This allows all other values to be compared using {@code <, ==, >} operators (with
+ * the exception of signed zeros).
+ *
+ * @param data Values.
+ * @return index of last non-NaN value (or -1)
+ */
+ static int sortNaN(double[] data) {
+ int end = data.length;
+ // Find first non-NaN
+ while (--end >= 0) {
+ if (!Double.isNaN(data[end])) {
+ break;
+ }
+ }
+ for (int i = end; --i >= 0;) {
+ final double v = data[i];
+ if (Double.isNaN(v)) {
+ // swap(data, i, end--)
+ data[i] = data[end];
+ data[end] = v;
+ end--;
+ }
+ }
+ return end;
+ }
+
+ /**
+ * Move invalid indices to the end of the array.
+ *
+ * @param indices Values.
+ * @param right Upper bound of data (inclusive).
+ * @param count Count of indices.
+ * @return count of valid indices
+ */
+ static int countIndices(int[] indices, int count, int right) {
+ int end = count;
+ // Find first valid index
+ while (--end >= 0) {
+ if (indices[end] <= right) {
+ break;
+ }
+ }
+ for (int i = end; --i >= 0;) {
+ final int k = indices[i];
+ if (k > right) {
+ // swap(indices, i, end--)
+ indices[i] = indices[end];
+ indices[end] = k;
+ end--;
+ }
+ }
+ return end + 1;
+ }
+
+ /**
+ * Count the number of signed zeros (-0.0) if the range contains a mix of positive and
+ * negative zeros. If all positive, or all negative then this returns 0.
+ *
+ * This method can be used when a pivot value is zero during partitioning when the
+ * method uses the pivot value to replace values matched as equal using {@code ==}.
+ * This may destroy a mixture of signed zeros by overwriting them as all 0.0 or -0.0
+ * depending on the pivot value.
+ *
+ * @param data Values.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @return the count of signed zeros if some positive zeros are also present
+ */
+ static int countMixedSignedZeros(double[] data, int left, int right) {
+ // Count negative zeros
+ int c = 0;
+ int cn = 0;
+ for (int i = left; i <= right; i++) {
+ if (data[i] == 0) {
+ c++;
+ if (Double.doubleToRawLongBits(data[i]) < 0) {
+ cn++;
+ }
+ }
+ }
+ return c == cn ? 0 : cn;
+ }
+
+ /**
+ * Sort a range of all zero values.
+ * This orders -0.0 before 0.0.
+ *
+ * Warning: The range must contain only zeros.
+ *
+ * @param data Values.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void sortZero(double[] data, int left, int right) {
+ // Count negative zeros
+ int c = 0;
+ for (int i = left; i <= right; i++) {
+ if (Double.doubleToRawLongBits(data[i]) < 0) {
+ c++;
+ }
+ }
+ // Replace
+ if (c != 0) {
+ int i = left;
+ while (c-- > 0) {
+ data[i++] = -0.0;
+ }
+ while (i <= right) {
+ data[i++] = 0.0;
+ }
+ }
+ }
+
+ /**
+ * Detect and fix the sort order of signed zeros. Assumes the data may have been
+ * partially ordered around zero.
+ *
+ * Searches for zeros if {@code data[left] <= 0} and {@code data[right] >= 0}.
+ * If zeros are discovered in the range then they are assumed to be continuous.
+ *
+ * @param data Values.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ private static void fixContinuousSignedZeros(double[] data, int left, int right) {
+ int j;
+ if (data[left] <= 0 && data[right] >= 0) {
+ int i = left;
+ while (data[i] < 0) {
+ i++;
+ }
+ j = right;
+ while (data[j] > 0) {
+ j--;
+ }
+ sortZero(data, i, j);
+ }
+ }
+
+ /**
+ * Creates the maximum recursion depth for single-pivot quickselect recursion.
+ *
+ * Warning: A length of zero will create a negative recursion depth.
+ * In practice this does not matter as the sort / partition of a length
+ * zero array should ignore the data.
+ *
+ * @param n Length of data (must be strictly positive).
+ * @return the maximum recursion depth
+ */
+ private int createMaxDepthSinglePivot(int n) {
+ // Ideal single pivot recursion will take log2(n) steps as data is
+ // divided into length (n/2) at each iteration.
+ final int maxDepth = floorLog2(n);
+ // This factor should be tuned for practical performance
+ return (int) Math.floor(maxDepth * recursionMultiple) + recursionConstant;
+ }
+
+ /**
+ * Compute the maximum recursion depth for single pivot recursion.
+ * Uses {@code 2 * floor(log2 (x))}.
+ *
+ * @param x Value.
+ * @return {@code log3(x))}
+ */
+ private static int singlePivotMaxDepth(int x) {
+ return (31 - Integer.numberOfLeadingZeros(x)) << 1;
+ }
+
+ /**
+ * Compute {@code floor(log 2 (x))}. This is valid for all strictly positive {@code x}.
+ *
+ * Returns -1 for {@code x = 0} in place of -infinity.
+ *
+ * @param x Value.
+ * @return {@code floor(log 2 (x))}
+ */
+ static int floorLog2(int x) {
+ return 31 - Integer.numberOfLeadingZeros(x);
+ }
+
+ /**
+ * Convert {@code ln(n)} to the single-pivot max depth.
+ *
+ * @param x ln(n)
+ * @return the maximum recursion depth
+ */
+ private int lnNtoMaxDepthSinglePivot(double x) {
+ final double maxDepth = x * LOG2_E;
+ return (int) Math.floor(maxDepth * recursionMultiple) + recursionConstant;
+ }
+
+ /**
+ * Creates the maximum recursion depth for dual-pivot quickselect recursion.
+ *
+ * Warning: A length of zero will create a high recursion depth.
+ * In practice this does not matter as the sort / partition of a length
+ * zero array should ignore the data.
+ *
+ * @param n Length of data (must be strictly positive).
+ * @return the maximum recursion depth
+ */
+ private int createMaxDepthDualPivot(int n) {
+ // Ideal dual pivot recursion will take log3(n) steps as data is
+ // divided into length (n/3) at each iteration.
+ final int maxDepth = log3(n);
+ // This factor should be tuned for practical performance
+ return (int) Math.floor(maxDepth * recursionMultiple) + recursionConstant;
+ }
+
+ /**
+ * Compute an approximation to {@code log3 (x)}.
+ *
+ * The result is between {@code floor(log3(x))} and {@code ceil(log3(x))}.
+ * The result is correctly rounded when {@code x +/- 1} is a power of 3.
+ *
+ * @param x Value.
+ * @return {@code log3(x))}
+ */
+ static int log3(int x) {
+ // log3(2) ~ 1.5849625
+ // log3(x) ~ log2(x) * 0.630929753... ~ log2(x) * 323 / 512 (0.630859375)
+ // Use (floor(log2(x))+1) * 323 / 512
+ // This result is always between floor(log3(x)) and ceil(log3(x)).
+ // It is correctly rounded when x +/- 1 is a power of 3.
+ return ((32 - Integer.numberOfLeadingZeros(x)) * 323) >>> 9;
+ }
+
+ /**
+ * Search the data for the largest index {@code i} where {@code a[i]} is
+ * less-than-or-equal to the {@code key}; else return {@code left - 1}.
+ * The data is assumed to be in ascending order, otherwise the behaviour is undefined.
+ * If the range contains multiple elements with the {@code key} value, the result index
+ * may be any that match.
+ *
+ * This is similar to using {@link java.util.Arrays#binarySearch(int[], int, int, int)
+ * Arrays.binarySearch}. The method differs in:
+ * An equivalent use of binary search is:
+ * This specialisation avoids the caller checking the binary search result for the use
+ * case when the presence or absence of a key is not important; only that the returned
+ * index for an absence of a key is the largest index. When used on unique keys this
+ * method can be used to update an upper index so all keys are known to be below a key:
+ *
+ * The data is assumed to be in ascending order, otherwise the behaviour is undefined.
+ * If the range contains multiple elements with the {@code key} value, the result index
+ * may be any that match.
+ *
+ * This is similar to using {@link java.util.Arrays#binarySearch(int[], int, int, int)
+ * Arrays.binarySearch}. The method differs in:
+ * An equivalent use of binary search is:
+ * This specialisation avoids the caller checking the binary search result for the use
+ * case when the presence or absence of a key is not important; only that the returned
+ * index for an absence of a key is the smallest index. When used on unique keys this
+ * method can be used to update a lower index so all keys are known to be above a key:
+ *
+ * This uses a RNG based on a linear congruential generator with biased numbers
+ * in {@code [0, n)}, favouring speed over statitsical robustness.
+ *
+ * @param n Data length.
+ * @param k Target index.
+ * @return the RNG
+ */
+ static IntUnaryOperator createFastRNG(int n, int k) {
+ return new Gen(n * 31L + k);
+ }
+
+ /**
+ * Random generator for numbers in {@code [0, n)}.
+ * The random sample should be fast in preference to statistically robust.
+ * Here we implement a biased sampler for the range [0, n)
+ * as n * f with f a fraction with base 2^32.
+ * Source of randomness is a 64-bit LCG using the constants from MMIX by Donald Knuth.
+ * https://en.wikipedia.org/wiki/Linear_congruential_generator
+ */
+ private static final class Gen implements IntUnaryOperator {
+ /** LCG state. */
+ private long s;
+
+ /**
+ * @param seed Seed.
+ */
+ Gen(long seed) {
+ // Update state
+ this.s = seed * 6364136223846793005L + 1442695040888963407L;
+ }
+
+ @Override
+ public int applyAsInt(int n) {
+ final long x = s;
+ // Update state
+ s = s * 6364136223846793005L + 1442695040888963407L;
+ // Use the upper 32-bits from the state as the random 32-bit sample
+ // result = n * [0, 2^32) / 2^32
+ return (int) ((n * (x >>> Integer.SIZE)) >>> Integer.SIZE);
+ }
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/PartitionFactory.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/PartitionFactory.java
new file mode 100644
index 000000000..3ec33d64e
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/PartitionFactory.java
@@ -0,0 +1,420 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.numbers.examples.jmh.arrays.Partition.AdaptMode;
+import org.apache.commons.numbers.examples.jmh.arrays.Partition.EdgeSelectStrategy;
+import org.apache.commons.numbers.examples.jmh.arrays.Partition.ExpandStrategy;
+import org.apache.commons.numbers.examples.jmh.arrays.Partition.KeyStrategy;
+import org.apache.commons.numbers.examples.jmh.arrays.Partition.LinearStrategy;
+import org.apache.commons.numbers.examples.jmh.arrays.Partition.PairedKeyStrategy;
+import org.apache.commons.numbers.examples.jmh.arrays.Partition.SPStrategy;
+import org.apache.commons.numbers.examples.jmh.arrays.Partition.StopperStrategy;
+
+/**
+ * Create instances of partition algorithms. The configuration of the algorithm
+ * is obtained by harvesting parameters from the name.
+ *
+ * @see Partition
+ * @see KthSelector
+ * @since 1.2
+ */
+final class PartitionFactory {
+ /** Pattern for the minimum quickselect size. */
+ private static final Pattern QS_PATTERN = Pattern.compile("QS(\\d+)");
+ /** Pattern for the edgeselect constant. */
+ private static final Pattern EC_PATTERN = Pattern.compile("EC(\\d+)");
+ /** Pattern for the edgeselect constant for linear select. */
+ private static final Pattern LC_PATTERN = Pattern.compile("LC(\\d+)");
+ /** Pattern for the sub-sampling size. */
+ private static final Pattern SU_PATTERN = Pattern.compile("SU(\\d+)");
+ /** Pattern for the recursion multiple (simple float format). */
+ private static final Pattern RM_PATTERN = Pattern.compile("RM(\\d+\\.?\\d*)");
+ /** Pattern for the recursion constant. */
+ private static final Pattern RC_PATTERN = Pattern.compile("RC(\\d+)");
+ /** Pattern for the compression level. */
+ private static final Pattern CL_PATTERN = Pattern.compile("CL(\\d+)");
+ /** Pattern for the control flags. Allow negative flags. */
+ private static final Pattern CF_PATTERN = Pattern.compile("CF(-?\\d+)");
+ /** Pattern for the option flags. */
+ private static final Pattern OF_PATTERN = Pattern.compile("OF(-?\\d+)");
+
+ /** No instances. */
+ private PartitionFactory() {}
+
+ /**
+ * Creates the {@link KthSelector}. Parameters are derived from the {@code name}.
+ *
+ * After parameters are harvested the only allowed characters are underscores,
+ * otherwise an exception is thrown. This ensures the parameters in the name were
+ * correct.
+ *
+ * @param name Name.
+ * @param prefix Method prefix.
+ * @return the {@link KthSelector} instance
+ */
+ static KthSelector createKthSelector(String name, String prefix) {
+ return createKthSelector(name, prefix, 0);
+ }
+
+ /**
+ * Creates the {@link KthSelector}. Parameters are derived from the {@code name}. This
+ * uses regex matching or enum name matching. Regex uses a prefix of two characters
+ * and then a number. Enum name matching finds the longest enum name match from all
+ * enum values. Ideally enum names from different enums that can be used together
+ * should be distinct. Enum names in the {@code name} must be prefixed using an underscore.
+ *
+ * Any matches are removed from the {@code name}. After parameters are harvested
+ * the only allowed characters are underscores, otherwise an exception is thrown. This
+ * ensures the parameters in the {@code name} were correct.
+ *
+ * Harvests:
+ * After parameters are harvested the only allowed characters are underscores,
+ * otherwise an exception is thrown. This ensures the parameters in the name were
+ * correct.
+ *
+ * @param name Name.
+ * @param prefix Method prefix.
+ * @return the {@link Partition} instance
+ * @see #createPartition(String, String, int, int)
+ */
+ static Partition createPartition(String name, String prefix) {
+ return createPartition(name, prefix, 0, 0);
+ }
+
+ /**
+ * Creates the {@link Partition}. Parameters are derived from the {@code name}. This
+ * uses regex matching or enum name matching. Regex uses a prefix of two characters
+ * and then a number. Enum name matching finds the longest enum name match from all
+ * enum values. Ideally enum names from different enums that can be used together
+ * should be distinct. Enum names in the {@code name} must be prefixed using an underscore.
+ *
+ * Any matches are removed from the {@code name}. After parameters are harvested
+ * the only allowed characters are underscores, otherwise an exception is thrown. This
+ * ensures the parameters in the {@code name} were correct.
+ *
+ * Harvests:
+ * A pivot is an index position that contains a value equal to the value in a fully
+ * sorted array.
+ *
+ * For a pivot {@code p}:
+ *
+ * Partitioning moves data in a range {@code [lower, upper]} so that the pivot
+ * {@code p} partitions the data. During this process many pivots may be found
+ * before the search ends. A pivot cache supports storing these pivots so that
+ * they can be used to bracket further searches in the array.
+ *
+ * A pivot cache supports finding a search bracket to partition an index {@code k}
+ * within an array of length {@code n}. The bracket should be an enclosing bound
+ * of known pivots. All data can be rearranged within this bracket without destroying
+ * other regions of the partitioned array. The support for {@code k} is provided within
+ * an inclusive range {@code [left, right]} where {@code 0 <= left <= right < n}.
+ * Thus {@code [left, right]} denotes the region containing all target indices {@code k}
+ * for multi-region partitioning.
+ *
+ * The cache provides the following functionality:
+ *
+ * Note that searching with the bound {@code [lower, upper]} will reorder data
+ * and pivots within this range may be invalidated by moving of data. To prevent
+ * error the bound provided by a cache must use the closest bracketing pivots.
+ *
+ * At least two strategies can be used:
+ *
+ * Implementations may assume indices are positive.
+ *
+ * @since 1.2
+ */
+interface PivotCache extends PivotStore {
+ /**
+ * The start (inclusive) of the range of indices supported.
+ *
+ * @return start of the supported range
+ */
+ int left();
+
+ /**
+ * The end (inclusive) of the range of indices supported.
+ *
+ * @return end of the supported range
+ */
+ int right();
+
+ /**
+ * Returns {@code true} if the cache supports storing some of the pivots in the supported
+ * range. A sparse cache can provide approximate bounds for partitioning. These bounds may be
+ * smaller than using the bounds of the entire array. Note that partitioning may destroy
+ * previous pivots within a range. Thus a sparse cache should be used to partition indices
+ * in sorted order so that bounds generated by each iteration do not overlap the bounds
+ * of a previous partition. This can be done by using the previous {@code k} as the left
+ * bound.
+ *
+ * A sparse cache can be created to store 1 pivot between all {@code k} of interest
+ * after the first {@code k}, and optionally two pivots that bracket the entire supported
+ * range. In the following example the partition of {@code k1} stores pivots {@code p}.
+ * These can be used to bracket {@code k2, k3}. An alternative scheme
+ * where no pivots are stored is shown for comparison:
+ *
+ * If false then the cache will store all pivots within the supported range and
+ * ideally provide the closest bounding pivot around the supported range.
+ *
+ * @return true if sparse
+ */
+ boolean sparse();
+
+ /**
+ * Test if the index {@code k} is a pivot.
+ *
+ * If {@code index < left} or {@code index > right} the behavior is not
+ * defined. If the range contains internal indices, the {@link PivotCache} will not
+ * store them and will be {@link PivotCache#sparse() sparse}.
+ *
+ * The range returned instance may implement {@link ScanningPivotCache}.
+ * It should only be cast to a {@link ScanningPivotCache} and used for scanning
+ * if it reports itself as non-{@link PivotCache#sparse() sparse}.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @return the pivot cache
+ * @see #ofFullRange(int, int)
+ */
+ static PivotCache ofRange(int left, int right) {
+ validateRange(left, right);
+ return left == right ?
+ new PointPivotCache(left) :
+ new RangePivotCache(left, right);
+ }
+
+ /**
+ * Return a {@link PivotCache} for the full-range {@code [left, right]}.
+ * The returned implementation will be non-{@link PivotCache#sparse() sparse}.
+ *
+ * The range returned instance may implement {@link ScanningPivotCache}.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @return the pivot cache
+ */
+ static PivotCache ofFullRange(int left, int right) {
+ validateRange(left, right);
+ if (right - left <= 1) {
+ return left == right ?
+ new PointPivotCache(left) :
+ new RangePivotCache(left, right);
+ }
+ return IndexSet.ofRange(left, right);
+ }
+
+ /**
+ * Validate the range {@code left <= right}.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ private static void validateRange(int left, int right) {
+ if (right < left) {
+ throw new IllegalArgumentException("Invalid range");
+ }
+ }
+
+ /**
+ * PivotCache for range {@code [left, right]} consisting of a single point.
+ */
+ private static class PointPivotCache implements ScanningPivotCache {
+ /** The target point. */
+ private final int target;
+ /** The upstream pivot closest to the left bound of the support.
+ * Provides a lower search bound for the range [left, right]. */
+ private int lowerPivot = -1;
+ /** The downstream pivot closest to the right bound of the support.
+ * Provides an upper search bound for the range [left, right]. */
+ private int upperPivot = UPPER_DEFAULT;
+
+ /**
+ * @param index Index defining {@code [left, right]}.
+ */
+ PointPivotCache(int index) {
+ this.target = index;
+ }
+
+ @Override
+ public void add(int index) {
+ // Update the floating pivots
+ if (index <= target) {
+ // This does not update upperPivot if index == target.
+ // This case is checked in nextPivot(int).
+ lowerPivot = Math.max(index, lowerPivot);
+ } else {
+ upperPivot = Math.min(index, upperPivot);
+ }
+ }
+
+ @Override
+ public void add(int fromIndex, int toIndex) {
+ // Update the floating pivots
+ if (toIndex <= target) {
+ // This does not update upperPivot if toIndex == target.
+ // This case is checked in nextPivot(int).
+ lowerPivot = Math.max(toIndex, lowerPivot);
+ } else if (fromIndex > target) {
+ upperPivot = Math.min(fromIndex, upperPivot);
+ } else {
+ // Range brackets the target
+ lowerPivot = upperPivot = target;
+ }
+ }
+
+ @Override
+ public int left() {
+ return target;
+ }
+
+ @Override
+ public int right() {
+ return target;
+ }
+
+ @Override
+ public boolean sparse() {
+ // Not sparse between [left, right]
+ return false;
+ }
+
+ @Override
+ public boolean moveLeft(int newLeft) {
+ // Unsupported
+ return false;
+ }
+
+ @Override
+ public boolean contains(int k) {
+ return lowerPivot == k;
+ }
+
+ @Override
+ public int previousPivot(int k) {
+ // Only support scanning within [left, right] => assume k == target
+ return lowerPivot;
+ }
+
+ // Do not override: int nextPivot(int k)
+
+ @Override
+ public int nextPivotOrElse(int k, int other) {
+ // Only support scanning within [left, right]
+ // assume lowerPivot <= left <= k <= right <= upperPivot
+ if (lowerPivot == target) {
+ return target;
+ }
+ return upperPivot == UPPER_DEFAULT ? other : upperPivot;
+ }
+
+ @Override
+ public int nextNonPivot(int k) {
+ // Only support scanning within [left, right] => assume k == target
+ return lowerPivot == target ? target + 1 : target;
+ }
+
+ @Override
+ public int previousNonPivot(int k) {
+ // Only support scanning within [left, right] => assume k == target
+ return lowerPivot == target ? target - 1 : target;
+ }
+ }
+
+ /**
+ * PivotCache for range {@code [left, right]} consisting of a bracketing range
+ * {@code lower <= left < right <= upper}.
+ *
+ * Behaviour is undefined if {@code left == right}. This cache is intended to
+ * bracket a range [left, right] that can be entirely sorted, e.g. if the separation
+ * between left and right is small.
+ */
+ private static class RangePivotCache implements ScanningPivotCache {
+ /** Left bound of the support. */
+ private final int left;
+ /** Right bound of the support. */
+ private final int right;
+ /** The upstream pivot closest to the left bound of the support.
+ * Provides a lower search bound for the range [left, right]. */
+ private int lowerPivot = -1;
+ /** The downstream pivot closest to the right bound of the support.
+ * Provides an upper search bound for the range [left, right]. */
+ private int upperPivot = UPPER_DEFAULT;
+
+ /**
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ RangePivotCache(int left, int right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ @Override
+ public void add(int index) {
+ // Update the floating pivots
+ if (index <= left) {
+ lowerPivot = Math.max(index, lowerPivot);
+ } else if (index >= right) {
+ upperPivot = Math.min(index, upperPivot);
+ }
+ }
+
+ @Override
+ public void add(int fromIndex, int toIndex) {
+ // Update the floating pivots
+ if (toIndex <= left) {
+ // l-------------r
+ // f---t
+ lowerPivot = Math.max(toIndex, lowerPivot);
+ } else if (fromIndex >= right) {
+ // l-------------r
+ // f---t
+ upperPivot = Math.min(fromIndex, upperPivot);
+ } else {
+ // Range [left, right] overlaps [from, to]
+ // toIndex > left && fromIndex < right
+ // l-------------r
+ // f---t
+ // f----t
+ // f----t
+ if (fromIndex <= left) {
+ lowerPivot = left;
+ }
+ if (toIndex >= right) {
+ upperPivot = right;
+ }
+ }
+ }
+
+ @Override
+ public int left() {
+ return left;
+ }
+
+ @Override
+ public int right() {
+ return right;
+ }
+
+ @Override
+ public boolean sparse() {
+ // Sparse if there are internal points between [left, right]
+ return right - left > 1;
+ }
+
+ @Override
+ public boolean moveLeft(int newLeft) {
+ // Unsupported
+ return false;
+ }
+
+ @Override
+ public boolean contains(int k) {
+ return lowerPivot == k || upperPivot == k;
+ }
+
+ @Override
+ public int previousPivot(int k) {
+ // Only support scanning within [left, right]
+ // assume lowerPivot <= left <= k <= right <= upperPivot
+ return k == upperPivot ? k : lowerPivot;
+ }
+
+ // Do not override: int nextPivot(int k)
+
+ @Override
+ public int nextPivotOrElse(int k, int other) {
+ // Only support scanning within [left, right]
+ // assume lowerPivot <= left <= k <= right <= upperPivot
+ if (k == lowerPivot) {
+ return k;
+ }
+ return upperPivot == UPPER_DEFAULT ? other : upperPivot;
+ }
+
+ @Override
+ public int nextNonPivot(int k) {
+ // Only support scanning within [left, right]
+ // assume lowerPivot <= left <= k <= right <= upperPivot
+ if (sparse()) {
+ throw new UnsupportedOperationException();
+ }
+ // range of size 2
+ // scan right
+ int i = k;
+ if (i == left) {
+ if (lowerPivot != left) {
+ return left;
+ }
+ i++;
+ }
+ if (i == right && upperPivot == right) {
+ i++;
+ }
+ return i;
+ }
+
+ @Override
+ public int previousNonPivot(int k) {
+ // Only support scanning within [left, right]
+ // assume lowerPivot <= left <= k <= right <= upperPivot
+ if (sparse()) {
+ throw new UnsupportedOperationException();
+ }
+ // range of size 2
+ // scan left
+ int i = k;
+ if (i == right) {
+ if (upperPivot != right) {
+ return right;
+ }
+ i--;
+ }
+ if (i == left && lowerPivot == left) {
+ i--;
+ }
+ return i;
+ }
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/PivotStore.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/PivotStore.java
new file mode 100644
index 000000000..60ad416f7
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/PivotStore.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * Storage for pivot indices used for partitioning an array into multiple regions.
+ *
+ * A pivot is an index position that contains a value equal to the value in a fully
+ * sorted array.
+ *
+ * For a pivot {@code p}:
+ *
+ * Implementations may assume indices are positive. Implementations are not required to
+ * store all indices, and may discard previously stored indices during operation. Behaviour
+ * should be documented.
+ *
+ * This interface is used by methods that create pivots. Methods that use pivots should
+ * use the {@link PivotCache} interface.
+ *
+ * @since 1.2
+ */
+interface PivotStore {
+ /**
+ * Add the pivot index to the store.
+ *
+ * @param index Index.
+ */
+ void add(int index);
+
+ /**
+ * Add a range of pivot indices to the store.
+ *
+ * If {@code fromIndex == toIndex} this is equivalent to {@link #add(int)}.
+ *
+ * If {@code fromIndex > toIndex} the behavior is not defined. An ideal strategy will pick [1/2, 1/2] across a variety of data.
+ *
+ * @since 1.2
+ */
+enum PivotingStrategy {
+ /**
+ * Pivot around the centre of the range.
+ */
+ CENTRAL {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int ignored) {
+ return med(left, right);
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right, int ignored) {
+ return new int[] {med(left, right)};
+ }
+
+ @Override
+ int samplingEffect() {
+ return UNCHANGED;
+ }
+ },
+ /**
+ * Pivot around the median of 3 values within the range: the first; the centre; and the last.
+ */
+ MEDIAN_OF_3 {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int ignored) {
+ return med3(data, left, med(left, right), right);
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right, int ignored) {
+ return new int[] {left, med(left, right), right};
+ }
+
+ @Override
+ int samplingEffect() {
+ return UNCHANGED;
+ }
+ },
+ /**
+ * Pivot around the median of 9 values within the range.
+ * Uses the median of 3 medians of 3. The returned value
+ * is ranked 4, 5, or 6 out of the 9 values.
+ * This is also known in the literature as Tukey’s "ninther" pivot.
+ */
+ MEDIAN_OF_9 {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int ignored) {
+ final int s = (right - left) >>> 3;
+ final int m = med(left, right);
+ final int x = med3(data, left, left + s, left + (s << 1));
+ final double a = data[x];
+ final int y = med3(data, m - s, m, m + s);
+ final double b = data[y];
+ final int z = med3(data, right - (s << 1), right - s, right);
+ return med3(a, b, data[z], x, y, z);
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right, int ignored) {
+ final int s = (right - left) >>> 3;
+ final int m = med(left, right);
+ return new int[] {
+ left, left + s, left + (s << 1),
+ m - s, m, m + s,
+ right - (s << 1), right - s, right
+ };
+ }
+
+ @Override
+ int samplingEffect() {
+ return UNCHANGED;
+ }
+ },
+ /**
+ * Pivot around the median of 3 or 9 values within the range.
+ *
+ * Note: Bentley & McIlroy (1993) choose a size of 40 to pivot around 9 values;
+ * and a lower size of 7 to use the central; otherwise the median of 3.
+ * This method does not switch to the central method for small sizes.
+ */
+ DYNAMIC {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int ignored) {
+ if (right - left >= MED_9) {
+ return MEDIAN_OF_9.pivotIndex(data, left, right, ignored);
+ }
+ return MEDIAN_OF_3.pivotIndex(data, left, right, ignored);
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right, int ignored) {
+ if (right - left >= MED_9) {
+ return MEDIAN_OF_9.getSampledIndices(left, right, ignored);
+ }
+ return MEDIAN_OF_3.getSampledIndices(left, right, ignored);
+ }
+
+ @Override
+ int samplingEffect() {
+ return UNCHANGED;
+ }
+ },
+ /**
+ * Pivot around the median of 5 values within the range.
+ * Requires that {@code right - left >= 4}.
+ *
+ * Warning: This has the side effect that the 5 values are also partially sorted.
+ *
+ * Uses the same spacing as {@link DualPivotingStrategy#SORT_5}.
+ */
+ MEDIAN_OF_5 {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int ignored) {
+ // 1/6 = 5/30 ~ 1/8 + 1/32 + 1/64 : 0.1666 ~ 0.1719
+ // Ensure the value is above zero to choose different points!
+ // This is safe if len >= 4.
+ final int len = right - left;
+ final int sixth = 1 + (len >>> 3) + (len >>> 5) + (len >>> 6);
+ // Note: No use of median(left, right). This is not targeted by median of 3 killer
+ // input as it does not use the end points left and right.
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - sixth;
+ final int p1 = p2 - sixth;
+ final int p4 = p3 + sixth;
+ final int p5 = p4 + sixth;
+ return Sorting.median5(data, p1, p2, p3, p4, p5);
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right, int ignored) {
+ final int len = right - left;
+ final int sixth = 1 + (len >>> 3) + (len >>> 5) + (len >>> 6);
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - sixth;
+ final int p1 = p2 - sixth;
+ final int p4 = p3 + sixth;
+ final int p5 = p4 + sixth;
+ return new int[] {p1, p2, p3, p4, p5};
+ }
+
+ @Override
+ int samplingEffect() {
+ return PARTIAL_SORT;
+ }
+ },
+ /**
+ * Pivot around the median of 5 values within the range.
+ * Requires that {@code right - left >= 4}.
+ *
+ * Warning: This has the side effect that the 5 values are also partially sorted.
+ *
+ * Uses the same spacing as {@link DualPivotingStrategy#SORT_5B}.
+ */
+ MEDIAN_OF_5B {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int ignored) {
+ // 1/7 = 5/35 ~ 1/8 + 1/64 : 0.1429 ~ 0.1406
+ // Ensure the value is above zero to choose different points!
+ // This is safe if len >= 4.
+ final int len = right - left;
+ final int seventh = 1 + (len >>> 3) + (len >>> 6);
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - seventh;
+ final int p1 = p2 - seventh;
+ final int p4 = p3 + seventh;
+ final int p5 = p4 + seventh;
+ Sorting.sort4(data, p1, p2, p4, p5);
+ // p2 and p4 are sorted: check if p3 is between them
+ if (data[p3] < data[p2]) {
+ return p2;
+ }
+ return data[p3] > data[p4] ? p4 : p3;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right, int ignored) {
+ final int len = right - left;
+ final int seventh = 1 + (len >>> 3) + (len >>> 6);
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - seventh;
+ final int p1 = p2 - seventh;
+ final int p4 = p3 + seventh;
+ final int p5 = p4 + seventh;
+ return new int[] {p1, p2, p3, p4, p5};
+ }
+
+ @Override
+ int samplingEffect() {
+ return PARTIAL_SORT;
+ }
+ },
+ /**
+ * Pivot around the target index.
+ */
+ TARGET {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int k) {
+ return k;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right, int k) {
+ return new int[] {k};
+ }
+
+ @Override
+ int samplingEffect() {
+ return UNCHANGED;
+ }
+ };
+
+ /** Sampled points are unchanged. */
+ static final int UNCHANGED = 0;
+ /** Sampled points are partially sorted. */
+ static final int PARTIAL_SORT = 0x1;
+ /** Sampled points are sorted. */
+ static final int SORT = 0x2;
+ /** Size to pivot around the median of 9. */
+ private static final int MED_9 = 40;
+
+ /**
+ * Compute the median index.
+ *
+ * Note: This intentionally uses the median as {@code left + (right - left + 1) / 2}.
+ * If the median is {@code left + (right - left) / 2} then the median is 1 position lower
+ * for even length due to using an inclusive right bound. This median is not as affected
+ * by median-of-3 killer sequences. For benchmarking it is useful to maintain the classic
+ * median-of-3 behaviour to be able to trigger worst case performance on input
+ * used in the literature.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @return the median index
+ */
+ private static int med(int left, int right) {
+ return (left + right + 1) >>> 1;
+ }
+
+ /**
+ * Find the median index of 3.
+ *
+ * @param data Values.
+ * @param i Index.
+ * @param j Index.
+ * @param k Index.
+ * @return the median index
+ */
+ private static int med3(double[] data, int i, int j, int k) {
+ return med3(data[i], data[j], data[k], i, j, k);
+ }
+
+ /**
+ * Find the median index of 3 values.
+ *
+ * @param a Value.
+ * @param b Value.
+ * @param c Value.
+ * @param ia Index of a.
+ * @param ib Index of b.
+ * @param ic Index of c.
+ * @return the median index
+ */
+ private static int med3(double a, double b, double c, int ia, int ib, int ic) {
+ if (a < b) {
+ if (b < c) {
+ return ib;
+ }
+ return a < c ? ic : ia;
+ }
+ if (b > c) {
+ return ib;
+ }
+ return a > c ? ic : ia;
+ }
+
+ /**
+ * Find a pivot index of the array so that partitioning into 2-regions can be made.
+ *
+ * The argument {@code k} is the target index in {@code [left, right]}. Strategies
+ * may use this to help select the pivot index. If not available (e.g. selecting a pivot
+ * for quicksort) then choose a value in {@code [left, right]} to be safe.
+ *
+ * @param data Array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param k Target index.
+ * @return pivot
+ */
+ abstract int pivotIndex(double[] data, int left, int right, int k);
+
+ // The following methods allow the strategy and side effects to be tested
+
+ /**
+ * Get the indices of points that will be sampled.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param k Target index.
+ * @return the indices
+ */
+ abstract int[] getSampledIndices(int left, int right, int k);
+
+ /**
+ * Get the effect on the sampled points.
+ * The scan is fast when the number of keys is small.
+ *
+ * @since 1.2
+ */
+final class ScanningKeyInterval implements SearchableInterval, SearchableInterval2 {
+ // Note:
+ // Using 4 markers into the data allows this class to return the same
+ // performance as using a binary search within the data when n < 1600.
+ // Benchmarked by searching once for next and previous from median points between k.
+
+ /** The ordered keys. */
+ private final int[] keys;
+ /** The original number of keys. */
+ private final int n;
+ /** Index into the keys (used for fast-forward). */
+ private final int i1;
+ /** Index into the keys (used for fast-forward). */
+ private final int i2;
+ /** Index into the keys (used for fast-forward). */
+ private final int i3;
+
+ /**
+ * Create an instance with the provided keys.
+ *
+ * @param indices Indices.
+ * @param n Number of indices.
+ */
+ ScanningKeyInterval(int[] indices, int n) {
+ keys = indices;
+ this.n = n;
+ // Divide into quarters for fast-forward
+ i1 = n >>> 2;
+ i2 = n >>> 1;
+ i3 = i1 + i2;
+ }
+
+ /**
+ * Initialise an instance with {@code n} initial {@code indices}. The indices are used in place.
+ *
+ * @param indices Indices.
+ * @param n Number of indices.
+ * @return the interval
+ * @throws IllegalArgumentException if the indices are not unique and ordered; or not
+ * in the range {@code [0, 2^31-1)}; or {@code n <= 0}
+ */
+ static ScanningKeyInterval of(int[] indices, int n) {
+ // Check the indices are uniquely ordered
+ if (n <= 0) {
+ throw new IllegalArgumentException("No indices to define the range");
+ }
+ int p = indices[0];
+ for (int i = 0; ++i < n;) {
+ final int c = indices[i];
+ if (c <= p) {
+ throw new IllegalArgumentException("Indices are not unique and ordered");
+ }
+ p = c;
+ }
+ if (indices[0] < 0) {
+ throw new IllegalArgumentException("Unsupported min value: " + indices[0]);
+ }
+ if (indices[n - 1] == Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("Unsupported max value: " + Integer.MAX_VALUE);
+ }
+ return new ScanningKeyInterval(indices, n);
+ }
+
+ @Override
+ public int left() {
+ return keys[0];
+ }
+
+ @Override
+ public int right() {
+ return keys[n - 1];
+ }
+
+ @Override
+ public int previousIndex(int k) {
+ return keys[previous(k)];
+ }
+
+ @Override
+ public int nextIndex(int k) {
+ return keys[next(k)];
+ }
+
+ @Override
+ public int split(int ka, int kb, int[] upper) {
+ int i = next(kb + 1);
+ upper[0] = keys[i];
+ // Find the lower
+ do {
+ --i;
+ } while (keys[i] >= ka);
+ return keys[i];
+ }
+
+ /**
+ * Find the key index {@code i} of {@code keys[i] <= k}.
+ *
+ * @param k Target key.
+ * @return the key index
+ */
+ private int previous(int k) {
+ // Scan the sorted keys from the end.
+ // Assume left <= k <= right thus no index checks required.
+ // IndexOutOfBoundsException indicates incorrect usage by the caller.
+
+ // Attempt fast-forward
+ int i;
+ if (keys[i2] > k) {
+ i = keys[i1] > k ? i1 : i2;
+ } else {
+ i = keys[i3] > k ? i3 : n;
+ }
+ do {
+ --i;
+ } while (keys[i] > k);
+ return i;
+ }
+
+ /**
+ * Find the key index {@code i} of {@code keys[i] >= k}.
+ *
+ * @param k Target key.
+ * @return the key index
+ */
+ private int next(int k) {
+ // Scan the sorted keys from the start.
+ // Assume left <= k <= right thus no index checks required.
+ // IndexOutOfBoundsException indicates incorrect usage by the caller.
+
+ // Attempt fast-forward
+ int i;
+ if (keys[i2] < k) {
+ i = keys[i3] < k ? i3 : i2;
+ } else {
+ i = keys[i1] < k ? i1 : -1;
+ }
+ do {
+ ++i;
+ } while (keys[i] < k);
+ return i;
+ }
+
+ @Override
+ public int start() {
+ return 0;
+ }
+
+ @Override
+ public int end() {
+ return n - 1;
+ }
+
+ @Override
+ public int index(int i) {
+ return keys[i];
+ }
+
+ @Override
+ public int previous(int i, int k) {
+ // index(start) <= k < index(i)
+ int j = i;
+ do {
+ --j;
+ } while (keys[j] > k);
+ return j;
+ }
+
+ @Override
+ public int next(int i, int k) {
+ // index(i) < k <= index(end)
+ int j = i;
+ do {
+ ++j;
+ } while (keys[j] < k);
+ return j;
+ }
+
+ @Override
+ public int split(int lo, int hi, int ka, int kb, int[] upper) {
+ // index(lo) < ka <= kb < index(hi)
+
+ // We could test if ka/kb is above or below the
+ // median (keys[lo] + keys[hi]) >>> 1 to pick the side to search
+
+ int j = hi;
+ do {
+ --j;
+ } while (keys[j] > kb);
+ upper[0] = j + 1;
+ // Find the lower
+ while (keys[j] >= ka) {
+ --j;
+ }
+ return j;
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/ScanningPivotCache.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/ScanningPivotCache.java
new file mode 100644
index 000000000..01cb3aa68
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/ScanningPivotCache.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * A cache of pivot indices used for partitioning an array into multiple regions.
+ *
+ * This extends the {@link PivotCache} interface to add support to traverse
+ * a region between pivot and non-pivot indices. It is intended to be used to
+ * allow unsorted gaps between pivots to be targeted using a full sort when
+ * the partition function is configured to partition several regions [ka, kb].
+ *
+ * In the following example the partition algorithm must fully sort [k1, k2]
+ * and [k3, k4]. The first iteration detects pivots downstream from [k1, k2].
+ * The second iteration can fill in the gaps when processing [k3, k4]:
+ *
+ * Implementations may discard previously stored indices during this operation, for
+ * example all indices below {@code newLeft}.
+ *
+ * This method can be used when partitioning keys {@code k1, k2, ...} in ascending
+ * order to indicate the next {@code k} that will be processed. The cache can optimise
+ * pivot storage to help partition downstream keys.
+ *
+ * Note: If {@code newLeft < left} then the updated range is outside the current
+ * support. Implementations may choose to: move {@code left} so the new support is
+ * {@code [newLeft, right]}; return {@code false} to indicate the support was not changed;
+ * or to throw an exception (which should be documented).
+ *
+ * Note: If {@code newLeft > right} then the updated range is outside the current
+ * support. Implementations may choose to: move {@code right} so the new support is
+ * {@code [newLeft, newleft]}; return {@code false} to indicate the support was not changed;
+ * or to throw an exception (which should be documented).
+ *
+ * @param newLeft Start of the supported range.
+ * @return true if the support was successfully modified
+ */
+ boolean moveLeft(int newLeft);
+
+ /**
+ * Returns the nearest non-pivot index that occurs on or after the specified starting
+ * index within the supported range. If no such
+ * indices exists then {@code right + n} is returned, where {@code n} is strictly positive.
+ *
+ * If the starting index is less than the supported range {@code left}
+ * the result is undefined.
+ *
+ * This method is intended to allow traversing the unsorted ranges between sorted
+ * pivot regions within the range {@code [left, right]}.
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the index of the next non-pivot, or {@code right + n} if there is no index
+ */
+ int nextNonPivot(int k);
+
+ /**
+ * Returns the nearest non-pivot index that occurs on or before the specified starting
+ * index within the supported range. If no such
+ * indices exists then {@code left - n} is returned, where {@code n} is strictly positive.
+ *
+ * If the starting index is greater than the supported range {@code right}
+ * the result is undefined.
+ *
+ * This method is intended to allow traversing the unsorted ranges between sorted
+ * pivot regions within the range {@code [left, right]}.
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the index of the next non-pivot, or {@code left - n} if there is no index
+ */
+ int previousNonPivot(int k);
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/SearchableInterval.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/SearchableInterval.java
new file mode 100644
index 000000000..541fac7c5
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/SearchableInterval.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * A searchable interval that contains indices used for partitioning an array into multiple regions.
+ *
+ * The interval provides the following functionality:
+ *
+ * Note that the interval provides the supported bounds. If a search begins outside
+ * the supported bounds the result is undefined.
+ *
+ * Implementations may assume indices are positive.
+ *
+ * @see SearchableInterval2
+ * @since 1.2
+ */
+interface SearchableInterval {
+ /**
+ * The start (inclusive) of the range of indices supported.
+ *
+ * @return start of the supported range
+ */
+ int left();
+
+ /**
+ * The end (inclusive) of the range of indices supported.
+ *
+ * @return end of the supported range
+ */
+ int right();
+
+ /**
+ * Returns the nearest index that occurs on or before the specified starting
+ * index.
+ *
+ * If {@code k < left} or {@code k > right} the result is undefined.
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the previous index
+ */
+ int previousIndex(int k);
+
+ /**
+ * Returns the nearest index that occurs on or after the specified starting
+ * index.
+ *
+ * If {@code k < left} or {@code k > right} the result is undefined.
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the next index
+ */
+ int nextIndex(int k);
+
+ /**
+ * Split the interval using two splitting indices. Returns the nearest index that occurs
+ * before the specified split index {@code ka}, and the nearest index that occurs after the
+ * specified split index {@code kb}.
+ *
+ * Note: Requires {@code left < ka <= kb < right}, i.e. there exists a valid interval
+ * above and below the split indices.
+ *
+ * The default implementation uses:
+ *
+ * Implementations may override this method if both indices can be obtained together.
+ *
+ * If {@code ka <= left} or {@code kb >= right} the result is undefined.
+ *
+ * @param ka Split index.
+ * @param kb Split index.
+ * @param upper Upper index.
+ * @return the lower index
+ */
+ default int split(int ka, int kb, int[] upper) {
+ upper[0] = nextIndex(kb + 1);
+ return previousIndex(ka - 1);
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/SearchableInterval2.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/SearchableInterval2.java
new file mode 100644
index 000000000..13ae9c3e2
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/SearchableInterval2.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * A searchable interval that contains indices used for partitioning an array into multiple regions.
+ *
+ * The interval provides pointers to indices in the interval. These pointers are used
+ * to assist in searching the interval and can be used to access the index value.
+ *
+ * The interval provides the following functionality:
+ *
+ * Note that the interval provides the supported bounds. If a search begins outside
+ * the supported bounds the result is undefined.
+ *
+ * Implementations may assume indices are positive.
+ *
+ * This differs from {@link SearchableInterval} by providing pointers into the interval to
+ * assist the search.
+ *
+ * @see SearchableInterval
+ * @since 1.2
+ */
+interface SearchableInterval2 {
+ /**
+ * Start pointer of the interval.
+ *
+ * @return the start pointer
+ */
+ int start();
+
+ /**
+ * End pointer of the interval.
+ *
+ * @return the end pointer
+ */
+ int end();
+
+ /**
+ * Return the index value {@code k} for the pointer.
+ *
+ * If {@code i < start} or {@code i > end} the result is undefined.
+ *
+ * @param i Pointer.
+ * @return the index value of {@code i}
+ */
+ int index(int i);
+
+ /**
+ * Returns a pointer to the nearest index that occurs on or before the specified starting index.
+ *
+ * Assumes {@code index(start) <= k < index(i)}.
+ *
+ * @param i Pointer.
+ * @param k Index to start checking from (inclusive).
+ * @return the previous pointer
+ */
+ int previous(int i, int k);
+
+ /**
+ * Returns a pointer to the nearest index that occurs on or after the specified starting
+ * index.
+ *
+ * Assumes {@code index(i) < k <= index(end)}.
+ *
+ * @param i Pointer.
+ * @param k Index to start checking from (inclusive).
+ * @return the next index
+ */
+ int next(int i, int k);
+
+ /**
+ * Split the interval using two splitting indices. Returns a pointer to the the
+ * nearest index that occurs before the specified split index {@code ka}, and a
+ * pointer to the nearest index that occurs after the specified split index
+ * {@code kb}.
+ *
+ * Requires {@code index(lo) < ka <= kb < index(hi)}, i.e. there exists a
+ * valid interval above and below the split indices.
+ *
+ * The default implementation uses:
+ *
+ * Implementations may override this method if both pointers can be obtained
+ * together.
+ *
+ * If {@code lo < start} or {@code hi > end} the result is undefined.
+ *
+ * @param lo Lower pointer.
+ * @param hi Upper pointer.
+ * @param ka Split index.
+ * @param kb Split index.
+ * @param upper Upper pointer.
+ * @return the lower pointer
+ */
+ default int split(int lo, int hi, int ka, int kb, int[] upper) {
+ upper[0] = next(hi, kb + 1);
+ return previous(lo, ka - 1);
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/SelectionPerformance.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/SelectionPerformance.java
new file mode 100644
index 000000000..ade2484ea
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/SelectionPerformance.java
@@ -0,0 +1,3198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import org.apache.commons.numbers.arrays.Selection;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.sampling.PermutationSampler;
+import org.apache.commons.rng.sampling.distribution.DiscreteUniformSampler;
+import org.apache.commons.rng.sampling.distribution.SharedStateDiscreteSampler;
+import org.apache.commons.rng.simple.RandomSource;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+/**
+ * Executes a benchmark of the selection of indices from {@code double} array data.
+ */
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@State(Scope.Benchmark)
+@Fork(value = 1, jvmArgs = {"-server", "-Xms512M", "-Xmx8192M"})
+public class SelectionPerformance {
+ /** Use the JDK sort function. */
+ private static final String JDK = "JDK";
+ /** Use a sort function. */
+ private static final String SORT = "Sort";
+ /** Baseline for the benchmark. */
+ private static final String BASELINE = "Baseline";
+ /** Selection method using a heap. */
+ private static final String HEAP_SELECT = "HeapSelect";
+ /** Selection method using a sort. */
+ private static final String SORT_SELECT = "SortSelect";
+
+ // First generation partition functions.
+ // These are based on the KthSelector class used in Commons Math:
+ // - Single k or a pair of indices (k,k+1) are selected in a single
+ // call; multiple indices cache pivots in a heap structure or use a BitSet.
+ // - They dynamically correct signed zeros when they are encountered.
+
+ /** Single-pivot partitioning. This method uses a special comparison of double
+ * values similar to {@link Double#compare(double, double)}. This handles
+ * NaN and signed zeros. */
+ private static final String SP = "SP";
+ /** Single-pivot partitioning; uses a BitSet to cache pivots. */
+ private static final String SPN = "SPN";
+ /** Single-pivot partitioning using a heap to cache pivots.
+ * This method is copied from Commons Math. */
+ private static final String SPH = "SPH";
+ /** Bentley-McIlroy partitioning (Sedgewick); uses a BitSet to cache pivots. */
+ private static final String SBM = "SBM";
+ /** Bentley-McIlroy partitioning (original); uses a BitSet to cache pivots. */
+ private static final String BM = "BM";
+ /** Dual-pivot partitioning; uses a BitSet to cache pivots. */
+ private static final String DP = "DP";
+ /** Dual-pivot partitioning with 5 sorted points to choose pivots; uses a BitSet to cache pivots. */
+ private static final String DP5 = "5DP";
+
+ // Second generation partition functions.
+ // These pre-process data to sort NaN to the end and count signed zeros;
+ // post-processing is performed to restore signed zeros in order.
+ // The exception is SBM2 which dynamically corrects signed zeros.
+
+ /** Bentley-McIlroy partitioning (Sedgewick). This second generation function
+ * dynamically corrects signed zeros when they are encountered. It is based on
+ * the fastest first generation method with changes to allow different pivot
+ * store strategies: SEQUENTIAL, INDEX_SET, PIVOT_CACHE. */
+ private static final String SBM2 = "2SBM";
+
+ /** Floyd-Rivest partitioning. Only for single k. */
+ private static final String FR = "FR";
+ /** Floyd-Rivest partitioning (Kiwiel). Only for a single k. */
+ private static final String KFR = "KFR";
+
+ // Introselect functions - switch to a stopper function when progress is poor.
+ // Allow specification of configuration using the name parameter:
+ // single-pivot strategy (Partition.SPStrategy);
+ // multiple key strategy (Partition.KeyStrategy);
+ // paired (close) keys strategy (Partition.PairedKeyStrategy);
+ // edge-selection strategy (Partition.EdgeSelectStrategy);
+ // stopper strategy (Partition.StopperStrategy).
+ // Parameters to control strategies and introspection are set using the name parameter.
+ // See PartitionFactory for details.
+
+ /** Introselect implementation with single-pivot partitioning. */
+ private static final String ISP = "ISP";
+ /** Introselect implementation with dual-pivot partitioning. */
+ private static final String IDP = "IDP";
+
+ // Single k selection using various methods which provide linear runtime (Order(n)).
+
+ /** Linearselect implementation with single pivot partitioning using median-of-medians-of-5
+ * for pivot selection. */
+ private static final String LSP = "LSP";
+ /** Linearselect implementation with single pivot partitioning using optimised
+ * median-of-medians. */
+ private static final String LINEAR = "Linear";
+ /** Quickselect adaptive implementation. Has configuration of the far-step method and some
+ * adaption modes. */
+ private static final String QA = "QA";
+ /** Quickselect adaptive implementation. Uses the best performing far-step method and
+ * has configurable adaption control allowing starting at and skipping over adaption modes. */
+ private static final String QA2 = "QA2";
+
+ /** Commons Numbers select implementation. This method is built using the best performing
+ * select function across a range of input data. This algorithm cannot be configured. */
+ private static final String SELECT = "SELECT";
+
+ /** Random source. */
+ private static final RandomSource RANDOM_SOURCE = RandomSource.XO_RO_SHI_RO_128_PP;
+
+ /**
+ * Source of {@code double} array data.
+ *
+ * By default this uses the adverse input test suite from figure 1 in Bentley and McIlroy
+ * (1993) Engineering a sort function, Software, practice and experience, Vol.23(11),
+ * 1249–1265.
+ *
+ * An alternative set of data is from Valois (2000) Introspective sorting and selection
+ * revisited, Software, practice and experience, Vol.30(6), 617-638.
+ *
+ * Note
+ *
+ * This class has setter methods to allow re-use in unit testing without requiring
+ * use of reflection to set fields. Parameters set by JMH are initialized to their
+ * defaults for convenience. Re-use requires:
+ *
+ * Random distribution mode
+ *
+ * The default BM configuration includes random samples generated as a family of
+ * single samples created from ranges that are powers of two [0, 2^i). This small set
+ * of samples is only a small representation of randomness. For small lengths this may
+ * only be a few random samples.
+ *
+ * The data source can be changed to generate a fixed number of random samples
+ * using a uniform distribution [0, n]. For this purpose the distribution must be set
+ * to {@link Distribution#RANDOM} and the {@link #setSamples(int) samples} set above
+ * zero. The inclusive upper bound {@code n} is set using the {@link #setSeed(int) seed}.
+ * If this is zero then the default is {@link Integer#MAX_VALUE}.
+ *
+ * Order
+ *
+ * Data are created in distribution families. If these are passed in order to a
+ * partition method the JVM can change behaviour of the algorithm as branch prediction
+ * statistics stabilise for the family. To mitigate this effect the order is permuted
+ * per invocation of the benchmark (see {@link #createOrder()}. This stabilises the
+ * average timing results from JMH. Using per-invocation data generation requires
+ * the benchmark execution time is higher than 1 millisecond. Benchmarks that use
+ * tiny data (e.g. sort 5 elements) must use several million samples.
+ */
+ @State(Scope.Benchmark)
+ public abstract static class AbstractDataSource {
+ /** All distributions / modifications. */
+ private static final String ALL = "all";
+ /** All distributions / modifications in the Bentley and McIlroy test suite. */
+ private static final String BM = "bm";
+ /** All distributions in the Valois test suite. These currently ignore the seed.
+ * To replicate Valois used a fixed seed and the copy modification. */
+ private static final String VALOIS = "valois";
+ /** Flag to determine if the data size should be logged. This is useful to be
+ * able to determine the execution time per sample when the number of samples
+ * is dynamically created based on the data length, range and seed. */
+ private static final AtomicInteger LOG_SIZE = new AtomicInteger();
+
+ /**
+ * The type of distribution.
+ */
+ enum Distribution {
+ // B&M (1993)
+
+ /** Sawtooth distribution. Ascending data from 0 to m, that repeats. */
+ SAWTOOTH,
+ /** Random distribution. Uniform random data in [0, m] */
+ RANDOM,
+ /** Stagger distribution. Multiple interlaced ascending sequences. */
+ STAGGER,
+ /** Plateau distribution. Ascending data from 0 to m, then constant. */
+ PLATEAU,
+ /** Shuffle distribution. Two randomly interlaced ascending sequences of different lengths. */
+ SHUFFLE,
+
+ /** Sharktooth distribution. Alternating ascending then descending data from 0
+ * to m and back. This is an addition to the original suite of BM
+ * and is not included in the test suite by default and must be specified.
+ *
+ * An ascending then descending sequence is also known as organpipe in
+ * Valois (2000). This version allows multiple ascending/descending runs in the
+ * same length. */
+ SHARKTOOTH,
+
+ // Valois (2000)
+
+ /** Sorted. */
+ SORTED,
+ /** Permutation of ones and zeros. */
+ ONEZERO,
+ /** Musser's median-of-3 killer. This elicits worst case performance for a median-of-3
+ * pivot selection strategy. */
+ M3KILLER,
+ /** A sorted sequence rotated left once. */
+ ROTATED,
+ /** Musser's two-faced sequence (the median-of-3 killer with two random permutations). */
+ TWOFACED,
+ /** An ascending then descending sequence. */
+ ORGANPIPE;
+ }
+
+ /**
+ * The type of data modification.
+ */
+ enum Modification {
+ /** Copy modification. */
+ COPY,
+ /** Reverse modification. */
+ REVERSE,
+ /** Reverse front-half modification. */
+ REVERSE_FRONT,
+ /** Reverse back-half modification. */
+ REVERSE_BACK,
+ /** Sort modification. */
+ SORT,
+ /** Descending modification (this is an addition to the original suite of BM).
+ * It is useful for testing worst case performance, e.g. insertion sort performs
+ * poorly on descending data. Heapselect using a max heap (to find k minimum elements)
+ * would perform poorly if data is processed in the forward direction as all elements
+ * must be inserted.
+ *
+ * This is not included in the test suite by default and must be specified.
+ * Note that the Shuffle distribution with a very large seed 'm' is effectively an
+ * ascending sequence and will be reversed to descending as part of the original
+ * B&M suite of data. */
+ DESCENDING,
+ /** Dither modification. Add i % 5 to the data element i. */
+ DITHER;
+ }
+
+ /**
+ * Sample information. Used to obtain information about samples that may be slow
+ * for a particular partition method, e.g. they use excessive recursion during quickselect.
+ * This is used for testing: each sample from the data source can provide the
+ * information to create the sample distribution.
+ */
+ public static final class SampleInfo {
+ /** Distribution. */
+ private final Distribution dist;
+ /** Modification. */
+ private final Modification mod;
+ /** Length. */
+ private final int n;
+ /** Seed. */
+ private final int m;
+ /** Offset. */
+ private final int o;
+
+ /**
+ * @param dist Distribution.
+ * @param mod Modification.
+ * @param n Length.
+ * @param m Seed.
+ * @param o Offset.
+ */
+ SampleInfo(Distribution dist, Modification mod, int n, int m, int o) {
+ this.dist = dist;
+ this.mod = mod;
+ this.n = n;
+ this.m = m;
+ this.o = o;
+ }
+
+ /**
+ * Create an instance with the specified distribution.
+ *
+ * @param v Value.
+ * @return the instance
+ */
+ SampleInfo with(Distribution v) {
+ return new SampleInfo(v, mod, n, m, o);
+ }
+
+ /**
+ * Create an instance with the specified modification.
+ *
+ * @param v Value.
+ * @return the instance
+ */
+ SampleInfo with(Modification v) {
+ return new SampleInfo(dist, v, n, m, o);
+ }
+
+ /**
+ * @return the distribution
+ */
+ Distribution getDistribution() {
+ return dist;
+ }
+
+ /**
+ * @return the modification
+ */
+ Modification getModification() {
+ return mod;
+ }
+
+ /**
+ * @return the data length
+ */
+ int getN() {
+ return n;
+ }
+
+ /**
+ * @return the distribution seed
+ */
+ int getM() {
+ return m;
+ }
+
+ /**
+ * @return the distribution offset
+ */
+ int getO() {
+ return o;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s, %s, n=%d, m=%d, o=%d", dist, mod, n, m, o);
+ }
+ }
+
+ /** Order. This is randomized to ensure that successive calls do not partition
+ * similar distributions. Randomized per invocation to avoid the JVM 'learning'
+ * branch decisions on small data sets. */
+ protected int[] order;
+ /** Cached source of randomness. */
+ protected UniformRandomProvider rng;
+
+ /** Type of data. Multiple types can be specified in the same string using
+ * lower/upper case, delimited using ':'. */
+ @Param({BM})
+ private String distribution = BM;
+
+ /** Type of data modification. Multiple types can be specified in the same string using
+ * lower/upper case, delimited using ':'. */
+ @Param({BM})
+ private String modification = BM;
+
+ /** Extra range to add to the data length.
+ * E.g. Use 1 to force use of odd and even length samples. */
+ @Param({"1"})
+ private int range = 1;
+
+ /** Sample 'seed'. This is {@code m} in Bentley and McIlroy's test suite.
+ * If set to zero the default is to use powers of 2 based on sample size. */
+ @Param({"0"})
+ private int seed;
+
+ /** Sample offset. This is used to shift each distribution to create different data.
+ * It is advanced on each invocation of {@link #setup()}. */
+ @Param({"0"})
+ private int offset;
+
+ /** Number of samples. Applies only to the random distribution. In this case
+ * the length of the data is randomly chosen in {@code [length, length + range)}. */
+ @Param({"0"})
+ private int samples;
+
+ /** RNG seed. Created using ThreadLocalRandom.current().nextLong(). This is advanced
+ * for the random distribution mode per iteration. Each benchmark executed by
+ * JMH will use the same random data, even across JVMs.
+ *
+ * If this is zero then a random seed is chosen. */
+ @Param({"-7450238124206088695"})
+ private long rngSeed = -7450238124206088695L;
+
+ /** Data. This is stored as integer data which saves memory. Note that when ranking
+ * data it is not necessary to have the full range of the double data type; the same
+ * number of unique values can be recorded in an array using an integer type.
+ * Returning a double[] forces a copy to be generated for destructive sorting /
+ * partitioning methods. */
+ private int[][] data;
+
+ /** Sample information. */
+ private List This is returned in a randomized order per iteration.
+ *
+ * @param index Index.
+ * @return the data sample
+ */
+ public double[] getData(int index) {
+ return getDataSample(order[index]);
+ }
+
+ /**
+ * Gets the sample for the given {@code index}.
+ *
+ * @param index Index.
+ * @return the data sample
+ */
+ protected double[] getDataSample(int index) {
+ final int[] a = data[index];
+ final double[] x = new double[a.length];
+ for (int i = -1; ++i < a.length;) {
+ x[i] = a[i];
+ }
+ return x;
+ }
+
+ /**
+ * Gets the sample size for the given {@code index}.
+ *
+ * @param index Index.
+ * @return the data sample size
+ */
+ public int getDataSize(int index) {
+ return data[index].length;
+ }
+
+ /**
+ * Gets the sample information for the given {@code index}.
+ * Matches the (native) order returned by {@link #getDataSample(int)}.
+ *
+ * @param index Index.
+ * @return the data sample information
+ */
+ SampleInfo getDataSampleInfo(int index) {
+ return sampleInfo.get(index);
+ }
+
+ /**
+ * Get the number of data samples.
+ *
+ * Note: This data source will create a permutation order per invocation based on
+ * this size. Per-invocation control in JMH is recommended for methods that take
+ * more than 1 millisecond to execute. For very small data and/or fast methods
+ * this may not be achievable. Child classes may override this value to create
+ * a large number of repeats of the same data per invocation. Any class performing
+ * this should also override {@link #getData(int)} to prevent index out of bound errors.
+ * This can be done by mapping the index to the original index using the number of repeats
+ * e.g. {@code original index = index / repeats}.
+ *
+ * @return the number of samples
+ */
+ public int size() {
+ return data.length;
+ }
+
+ /**
+ * Create the data.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ Objects.requireNonNull(distribution);
+ Objects.requireNonNull(modification);
+
+ // Set-up using parameters (may throw)
+ final EnumSet JMH recommends that invocations should take at
+ * least 1 millisecond for timings to be usable. In practice there should be
+ * enough data that processing takes much longer than a few milliseconds.
+ */
+ @Setup(Level.Invocation)
+ public void createOrder() {
+ if (order == null) {
+ // First call, create objects
+ order = PermutationSampler.natural(size());
+ }
+ PermutationSampler.shuffle(rng, order);
+ }
+
+ /**
+ * @return the distributions
+ */
+ private EnumSet This can be pasted into a JShell terminal to verify it works for any size
+ * {@code 1 <= n < 2^31}. With the default behaviour all seeds {@code m} are unsigned
+ * strictly positive powers of 2 and the highest seed should be below {@code 2*n}.
+ *
+ * @param seed Seed (use 0 for default; or provide a strictly positive {@code 1 <= m <= 2^31}).
+ * @param n Sample size.
+ * @return the seeds
+ */
+ private static int[] createSeeds(int seed, int n) {
+ // Allow [1, 2^31] (note 2^31 is negative but handled as a power of 2)
+ if (seed - 1 >= 0) {
+ return new int[] {seed};
+ }
+ // Bentley-McIlroy use:
+ // for: m = 1; m < 2 * n; m *= 2
+ // This has been modified here to handle n up to MAX_VALUE
+ // by knowing the count of m to generate as the power of 2 >= n.
+
+ // ceil(log2(n)) + 1 == ceil(log2(2*n)) but handles MAX_VALUE
+ int c = 33 - Integer.numberOfLeadingZeros(n - 1);
+ final int[] seeds = new int[c];
+ c = 0;
+ for (int m = 1; c != seeds.length; m *= 2) {
+ seeds[c++] = m;
+ }
+ return seeds;
+ }
+
+ /**
+ * Creates the distribution samples. Handles {@code m = 2^31} using
+ * {@link Integer#MIN_VALUE}.
+ *
+ * The offset is used to adjust each distribution to generate a different
+ * output. Only applies to distributions that do not use the source of randomness.
+ *
+ * Distributions are generated in enum order and recorded in the output {@code info}.
+ * Distributions that are a constant value at {@code m == 1} are not generated.
+ * This case is handled by the plateau distribution which will be a constant value
+ * except one occurrence of zero.
+ *
+ * @param dist Distributions.
+ * @param rng Source of randomness.
+ * @param n Length of the sample.
+ * @param m Sample seed (in [1, 2^31])
+ * @param o Offset.
+ * @param info Sample information.
+ * @return the samples
+ */
+ private static List Supports positive values and the edge case of {@link Integer#MIN_VALUE}
+ * which is treated as an unsigned power of 2.
+ *
+ * @param v Value (ignored if not within {@code [1, 2^31]}).
+ */
+ void setSeed(int v) {
+ seed = v;
+ }
+
+ /**
+ * Sets the sample 'offset' used to generate distributions. Advanced to a new
+ * random integer on each invocation of {@link #setup()}.
+ *
+ * @param v Value.
+ */
+ void setOffset(int v) {
+ offset = v;
+ }
+
+ /**
+ * Sets the number of samples to use for the random distribution mode.
+ * See {@link AbstractDataSource} for details.
+ *
+ * @param v Value.
+ */
+ void setSamples(int v) {
+ samples = v;
+ }
+
+ /**
+ * Sets the seed for the random number generator.
+ *
+ * @param v Value.
+ */
+ void setRngSeed(long v) {
+ this.rngSeed = v;
+ }
+ }
+
+ /**
+ * Source of {@code double} array data to sort.
+ */
+ @State(Scope.Benchmark)
+ public static class SortSource extends AbstractDataSource {
+ /** Data length. */
+ @Param({"1023"})
+ private int length;
+ /** Number of repeats. This is used to control the number of times the data is processed
+ * per invocation. Note that each invocation randomises the order. For very small data
+ * and/or fast methods there may not be enough data to achieve the target of 1
+ * millisecond per invocation. Use this value to increase the length of each invocation.
+ * For example the insertion sort on tiny data, or the sort5 methods, may require this
+ * to be 1,000,000 or higher. */
+ @Param({"1"})
+ private int repeats;
+
+ /** {@inheritDoc} */
+ @Override
+ protected int getLength() {
+ return length;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int size() {
+ return super.size() * repeats;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double[] getData(int index) {
+ // order = (data index) * repeats + repeat
+ // data index = order / repeats
+ return super.getDataSample(order[index] / repeats);
+ }
+ }
+
+ /**
+ * Source of k-th indices to partition.
+ *
+ * This class provides both data to partition and the indices to partition.
+ * The indices and data are created per iteration. The order to process them
+ * is created per invocation.
+ */
+ @State(Scope.Benchmark)
+ public static class KSource extends AbstractDataSource {
+ /** Data length. */
+ @Param({"1023"})
+ private int length;
+ /** Number of indices to select. */
+ @Param({"1", "2", "3", "5", "10"})
+ private int k;
+ /** Number of repeats. */
+ @Param({"10"})
+ private int repeats;
+ /** Distribution mode. K indices can be distributed randomly or uniformly.
+ * If the mode ends with a "s" then the indices are sorted. For example "randoms"
+ * will sort the random indices.
+ */
+ @Param({"random"})
+ private String mode;
+ /** Separation. K can be single indices (s=0) or paired (s!=0). Paired indices are
+ * separated using the specified separation. When running in paired mode the
+ * number of k is doubled and duplicates may occur. This method is used for
+ * testing sparse or uniform distributions of paired indices that may occur when
+ * interpolating quantiles. Since the separation is allowed to be above 1 it also
+ * allows testing configurations for close indices. */
+ @Param({"0"})
+ private int s;
+
+ /** Indices. */
+ private int[][] indices;
+ /** Cache permutation samplers. */
+ private PermutationSampler[] samplers;
+
+ /** {@inheritDoc} */
+ @Override
+ protected int getLength() {
+ return length;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int size() {
+ return super.size() * repeats;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double[] getData(int index) {
+ // order = (data index) * repeats + repeat
+ // data index = order / repeats
+ return super.getDataSample(order[index] / repeats);
+ }
+
+ /**
+ * Gets the indices for the given {@code index}.
+ *
+ * @param index Index.
+ * @return the data indices
+ */
+ public int[] getIndices(int index) {
+ // order = (data index) * repeats + repeat
+ // Directly look-up the indices for this repeat.
+ return indices[order[index]];
+ }
+
+ /**
+ * Create the indices.
+ */
+ @Override
+ @Setup(Level.Iteration)
+ public void setup() {
+ if (s < 0 || s >= getLength()) {
+ throw new IllegalStateException("Invalid separation: " + s);
+ }
+ super.setup();
+
+ // Data will be randomized per iteration
+ if (indices == null) {
+ // First call, create objects
+ indices = new int[size()][];
+ // Cache samplers. These hold an array which is randomized
+ // per call to obtain a permutation.
+ if (k > 1) {
+ samplers = new PermutationSampler[getRange() + 1];
+ }
+ }
+
+ // Create indices in the data sample length.
+ // If a separation is provided then the length is reduced by the separation
+ // to make space for a second index.
+
+ int index = 0;
+ final int noOfSamples = super.size();
+ if (mode.startsWith("random")) {
+ // random mode creates a permutation of k indices in the length
+ if (k > 1) {
+ final int baseLength = getLength();
+ for (int i = 0; i < noOfSamples; i++) {
+ final int len = getDataSize(i);
+ // Create permutation sampler for the length
+ PermutationSampler sampler = samplers[len - baseLength];
+ if (sampler == null) {
+ // Reduce length by the separation
+ final int n = len - s;
+ samplers[len - baseLength] = sampler = new PermutationSampler(rng, n, k);
+ }
+ for (int j = repeats; --j >= 0;) {
+ indices[index++] = sampler.sample();
+ }
+ }
+ } else {
+ // k=1: No requirement for a permutation
+ for (int i = 0; i < noOfSamples; i++) {
+ // Reduce length by the separation
+ final int n = getDataSize(i) - s;
+ for (int j = repeats; --j >= 0;) {
+ indices[index++] = new int[] {rng.nextInt(n)};
+ }
+ }
+ }
+ } else if (mode.startsWith("uniform")) {
+ // uniform indices with a random start
+ for (int i = 0; i < noOfSamples; i++) {
+ // Reduce length by the separation
+ final int n = getDataSize(i) - s;
+ final int step = Math.max(1, (int) Math.round((double) n / k));
+ for (int j = repeats; --j >= 0;) {
+ final int[] k1 = new int[k];
+ int p = rng.nextInt(n);
+ for (int m = 0; m < k; m++) {
+ p = (p + step) % n;
+ k1[m] = p;
+ }
+ indices[index++] = k1;
+ }
+ }
+ } else if (mode.startsWith("single")) {
+ // uniform indices with a random start
+ for (int i = 0; i < noOfSamples; i++) {
+ // Reduce length by the separation
+ final int n = getDataSize(i) - s;
+ int[] samples;
+ // When k approaches n then a linear spacing covers every part
+ // of the array and we sample. Do this when n < k/4. This handles
+ // k > n (saturation).
+ if (n < (k >> 2)) {
+ samples = rng.ints(k, 0, n).toArray();
+ } else {
+ // Linear spacing
+ final int step = n / k;
+ samples = new int[k];
+ for (int j = 0, x = step >> 1; j < k; j++, x += step) {
+ samples[j] = x;
+ }
+ }
+ for (int j = 0; j < repeats; j++) {
+ final int ii = j % k;
+ if (ii == 0) {
+ PermutationSampler.shuffle(rng, samples);
+ }
+ indices[index++] = new int[] {samples[ii]};
+ }
+ }
+ } else if ("index".equals(mode)) {
+ // Same single or paired indices for all samples.
+ // Check the index is valid.
+ for (int i = 0; i < noOfSamples; i++) {
+ // Reduce length by the separation
+ final int n = getDataSize(i) - s;
+ if (k >= n) {
+ throw new IllegalStateException("Invalid k: " + k + " >= " + n);
+ }
+ }
+ final int[] kk = s > 0 ? new int[] {k, k + s} : new int[] {k};
+ Arrays.fill(indices, kk);
+ return;
+ } else {
+ throw new IllegalStateException("Unknown index mode: " + mode);
+ }
+ // Add paired indices
+ if (s > 0) {
+ for (int i = 0; i < indices.length; i++) {
+ final int[] k1 = indices[i];
+ final int[] k2 = new int[k1.length << 1];
+ for (int j = 0; j < k1.length; j++) {
+ k2[j << 1] = k1[j];
+ k2[(j << 1) + 1] = k1[j] + s;
+ }
+ indices[i] = k2;
+ }
+ }
+ // Optionally sort
+ if (mode.endsWith("s")) {
+ for (int i = 0; i < indices.length; i++) {
+ Arrays.sort(indices[i]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Source of k-th indices. This does not extend the {@link AbstractDataSource} to provide
+ * data to partition. It is to be used to test processing of indices without partition
+ * overhead.
+ */
+ @State(Scope.Benchmark)
+ public static class IndexSource {
+ /** Indices. */
+ protected int[][] indices;
+ /** Upper bound (exclusive) on the indices. */
+ @Param({"1000", "1000000", "1000000000"})
+ private int length;
+ /** Number of indices to select. */
+ @Param({"10", "20", "40", "80", "160"})
+ private int k;
+ /** Number of repeats. */
+ @Param({"1000"})
+ private int repeats;
+ /** RNG seed. Created using ThreadLocalRandom.current().nextLong(). Each benchmark
+ * executed by JMH will use the same random data, even across JVMs.
+ *
+ * If this is zero then a random seed is chosen. */
+ @Param({"-7450238124206088695"})
+ private long rngSeed;
+ /** Ordered keys. */
+ @Param({"false"})
+ private boolean ordered;
+ /** Minimum separation between keys. */
+ @Param({"32"})
+ private int separation;
+
+ /**
+ * @return the indices
+ */
+ public int[][] getIndices() {
+ return indices;
+ }
+
+ /**
+ * Gets the minimum separation between keys. This is used by benchmarks
+ * to ignore splitting/search keys below a threshold.
+ *
+ * @return the minimum separation
+ */
+ public int getMinSeparation() {
+ return separation;
+ }
+
+ /**
+ * Create the indices and search points.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ if (k < 2) {
+ throw new IllegalStateException("Require multiple indices");
+ }
+ // Data will be randomized per iteration. It is the same sequence across
+ // benchmarks and JVM instances and allows benchmarking across JVM platforms
+ // with the same data.
+ // Allow pseudorandom seeding
+ if (rngSeed == 0) {
+ rngSeed = RandomSource.createLong();
+ }
+ final UniformRandomProvider rng = RANDOM_SOURCE.create(rngSeed);
+ // Advance the seed for the next iteration.
+ rngSeed = rng.nextLong();
+
+ final SharedStateDiscreteSampler s = DiscreteUniformSampler.of(rng, 0, length - 1);
+
+ indices = new int[repeats][];
+
+ for (int i = repeats; --i >= 0;) {
+ // Indices with possible repeats
+ final int[] x = new int[k];
+ for (int j = k; --j >= 0;) {
+ x[j] = s.sample();
+ }
+ indices[i] = x;
+ if (ordered) {
+ Sorting.sortIndices(x, x.length);
+ }
+ }
+ }
+
+ /**
+ * @return the RNG seed
+ */
+ long getRngSeed() {
+ return rngSeed;
+ }
+ }
+
+ /**
+ * Source of k-th indices to be searched/split.
+ * Can be used to split the same indices multiple times, or split a set of indices
+ * a single time, e.g. split indices k at point p.
+ */
+ @State(Scope.Benchmark)
+ public static class SplitIndexSource extends IndexSource {
+ /** Division mode. */
+ @Param({"RANDOM", "BINARY"})
+ private DivisionMode mode;
+
+ /** Search points. */
+ private int[][] points;
+ /** The look-up samples. These are used to identify a set of indices, and a single point to
+ * find in the range of the indices, e.g. split indices k at point p. The long packs
+ * two integers: the index of the indices k; and the search point p. These are packed
+ * as a long to enable easy shuffling of samples and access to the two indices. */
+ private long[] samples;
+
+ /** Options for the division mode. */
+ public enum DivisionMode {
+ /** Randomly divide. */
+ RANDOM,
+ /** Divide using binary division with recursion left then right. */
+ BINARY;
+ }
+
+ /**
+ * Return the search points. They are the median index points between adjacent
+ * indices. These are in the order specified by the division mode.
+ *
+ * @return the search points
+ */
+ public int[][] getPoints() {
+ return points;
+ }
+
+ /**
+ * @return the sample size
+ */
+ int samples() {
+ return samples.length;
+ }
+
+ /**
+ * Gets the indices for the random sample.
+ *
+ * @param index the index
+ * @return the indices
+ */
+ int[] getIndices(int index) {
+ return indices[(int) (samples[index] >>> Integer.SIZE)];
+ }
+
+ /**
+ * Gets the search point for the random sample.
+ *
+ * @param index the index
+ * @return the search point
+ */
+ int getPoint(int index) {
+ return (int) samples[index];
+ }
+
+ /**
+ * Create the indices and search points.
+ */
+ @Override
+ @Setup(Level.Iteration)
+ public void setup() {
+ super.setup();
+
+ final UniformRandomProvider rng = RANDOM_SOURCE.create(getRngSeed());
+
+ final int[][] indices = getIndices();
+ points = new int[indices.length][];
+
+ final int s = getMinSeparation();
+
+ // Set the division mode
+ final boolean random = Objects.requireNonNull(mode) == DivisionMode.RANDOM;
+
+ int size = 0;
+
+ for (int i = points.length; --i >= 0;) {
+ // Get the sorted unique indices
+ final int[] y = indices[i].clone();
+ final int unique = Sorting.sortIndices(y, y.length);
+
+ // Create the cut points between each unique index
+ int[] p = new int[unique - 1];
+ if (random) {
+ int c = 0;
+ for (int j = 0; j < p.length; j++) {
+ // Ignore dense keys
+ if (y[j] + s < y[j + 1]) {
+ p[c++] = (y[j] + y[j + 1]) >>> 1;
+ }
+ }
+ p = Arrays.copyOf(p, c);
+ PermutationSampler.shuffle(rng, p);
+ points[i] = p;
+ } else {
+ // binary division
+ final int c = divide(y, 0, unique - 1, p, 0, s);
+ points[i] = Arrays.copyOf(p, c);
+ }
+ size += points[i].length;
+ }
+
+ // Create the samples: pack indices index+point into a long
+ samples = new long[size];
+ for (int i = points.length; --i >= 0;) {
+ final long l = ((long) i) << Integer.SIZE;
+ for (final int p : points[i]) {
+ samples[--size] = l | p;
+ }
+ }
+ shuffle(rng, samples);
+ }
+
+ /**
+ * Divide the indices using binary division with recursion left then right.
+ * If a division is possible store the division point and update the count.
+ *
+ * @param indices Indices to divide
+ * @param lo Lower index in indices (inclusive).
+ * @param hi Upper index in indices (inclusive).
+ * @param p Division points.
+ * @param c Count of division points.
+ * @param s Minimum separation between indices.
+ * @return the updated count of division points.
+ */
+ private static int divide(int[] indices, int lo, int hi, int[] p, int c, int s) {
+ if (lo < hi) {
+ // Divide the interval in half
+ final int m = (lo + hi) >>> 1;
+ // Create a division point at approximately the midpoint
+ final int m1 = m + 1;
+ // Ignore dense keys
+ if (indices[m] + s < indices[m1]) {
+ final int k = (indices[m] + indices[m1]) >>> 1;
+ p[c++] = k;
+ }
+ // Recurse left then right.
+ // Does nothing if lo + 1 == hi as m == lo and m1 == hi.
+ c = divide(indices, lo, m, p, c, s);
+ c = divide(indices, m1, hi, p, c, s);
+ }
+ return c;
+ }
+
+ /**
+ * Shuffles the entries of the given array.
+ *
+ * @param rng Source of randomness.
+ * @param array Array whose entries will be shuffled (in-place).
+ */
+ private static void shuffle(UniformRandomProvider rng, long[] array) {
+ for (int i = array.length; i > 1; i--) {
+ swap(array, i - 1, rng.nextInt(i));
+ }
+ }
+
+ /**
+ * Swaps the two specified elements in the array.
+ *
+ * @param array Array.
+ * @param i First index.
+ * @param j Second index.
+ */
+ private static void swap(long[] array, int i, int j) {
+ final long tmp = array[i];
+ array[i] = array[j];
+ array[j] = tmp;
+ }
+ }
+
+ /**
+ * Source of an {@link SearchableInterval}.
+ */
+ @State(Scope.Benchmark)
+ public static class SearchableIntervalSource {
+ /** Name of the source. */
+ @Param({"ScanningKeyInterval",
+ "BinarySearchKeyInterval",
+ "IndexSetInterval",
+ "CompressedIndexSet",
+ // Same speed as the CompressedIndexSet_2
+ //"CompressedIndexSet2",
+ })
+ private String name;
+
+ /** The factory. */
+ private Function This is a specialised class to allow benchmarking the switch from using
+ * quickselect partitioning to using an edge selection.
+ *
+ * This class provides both data to partition and the indices to partition.
+ * The indices and data are created per iteration. The order to process them
+ * is created per invocation.
+ */
+ @State(Scope.Benchmark)
+ public static class EdgeSource extends AbstractDataSource {
+ /** Data length. */
+ @Param({"1023"})
+ private int length;
+ /** Mode. */
+ @Param({"SHIFT"})
+ private Mode mode;
+ /** Parameter to find k. Configured for 'shift' of the length. */
+ @Param({"1", "2", "3", "4", "5", "6", "7", "8", "9"})
+ private int p;
+ /** Target indices (as pairs of {@code [ka, kb]} defining a range to select). */
+ private int[][] indices;
+
+ /** Define the method used to generated the edge k. */
+ public enum Mode {
+ /** Create {@code k} using a right-shift {@code >>>} applied to the length. */
+ SHIFT,
+ /** Use the parameter {@code p} as an index. */
+ INDEX;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int size() {
+ return super.size() * 2;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double[] getData(int index) {
+ // order = (data index) * repeats + repeat
+ // data index = order / repeats; repeats=2 divide by using a shift
+ return super.getDataSample(order[index] >> 1);
+ }
+
+ /**
+ * Gets the sample indices for the given {@code index}.
+ * Returns a range to partition {@code [k1, kn]}.
+ *
+ * @param index Index.
+ * @return the target indices
+ */
+ public int[] getIndices(int index) {
+ // order = (data index) * repeats + repeat
+ // Directly look-up the indices for this repeat.
+ return indices[order[index]];
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected int getLength() {
+ return length;
+ }
+
+ /**
+ * Create the data and check the indices are not at the end.
+ */
+ @Override
+ @Setup(Level.Iteration)
+ public void setup() {
+ // Data will be randomized per iteration
+ super.setup();
+ // Error for a bad configuration. Allow k=0 but not smaller.
+ // Uses the lower bound on the length.
+ int k;
+ if (mode == Mode.SHIFT) {
+ k = length >>> p;
+ if (k == 0 && length >>> (p - 1) == 0) {
+ throw new IllegalStateException(length + " >>> (" + p + " - 1) == 0");
+ }
+ } else if (mode == Mode.INDEX) {
+ k = p;
+ if (k < 0 || k >= length) {
+ throw new IllegalStateException("Invalid index [0, " + length + "): " + p);
+ }
+ } else {
+ throw new IllegalStateException("Unknown mode: " + mode);
+ }
+
+ if (indices == null) {
+ // First call, create objects
+ indices = new int[size()][];
+ }
+
+ // Create a single index at both ends.
+ // Note: Data has variable length so we have to compute the upper end for each sample.
+ // Re-use the constant lower but we do not bother to cache repeats of the upper.
+ final int[] lower = {k, k};
+ final int noOfSamples = super.size();
+ for (int i = 0; i < noOfSamples; i++) {
+ final int len = getDataSize(i);
+ final int k1 = len - 1 - k;
+ indices[i << 1] = lower;
+ indices[(i << 1) + 1] = new int[] {k1, k1};
+ }
+ }
+ }
+
+ /**
+ * Source of a sort function.
+ */
+ @State(Scope.Benchmark)
+ public static class SortFunctionSource {
+ /** Name of the source. */
+ @Param({JDK, SP, BM, SBM, DP, DP5,
+ SBM2,
+ // Not run by default as it is slow on large data
+ //"InsertionSortIF", "InsertionSortIT", "InsertionSort", "InsertionSortB"
+ // Introsort methods with defaults, can configure using the name
+ // e.g. ISP_SBM_QS50.
+ ISP, IDP,
+ })
+ private String name;
+
+ /** Override of minimum quickselect size. */
+ @Param({"0"})
+ private int qs;
+
+ /** The action. */
+ private Consumer Detection of signed zero using direct conversion of raw bits and
+ * comparison with the bit representation is noticeably faster than comparison
+ * using {@code == 0.0}.
+ */
+ @State(Scope.Benchmark)
+ public static class SortNaNFunctionSource {
+ /** Name of the source. */
+ @Param({"RawZeroNaN", "ZeroSignNaN", "NaNRawZero", "NaNZeroSign"})
+ private String name;
+
+ /** The action. */
+ private BiConsumer This is a specialised class to allow benchmarking the switch from using
+ * quickselect partitioning to using edgeselect.
+ */
+ @State(Scope.Benchmark)
+ public static class EdgeFunctionSource {
+ /** Name of the source.
+ * For introselect methods this should effectively turn-off edgeselect. */
+ @Param({HEAP_SELECT, ISP + "_EC0", IDP + "_EC0",
+ // Only use for small length as sort insertion is worst case Order(k * (right - left))
+ // vs heap select() is O(k - left) + O((right - k) * log(k - left))
+ //SORT_SELECT
+ })
+ private String name;
+
+ /** The action. */
+ private BiFunction Note:
+ *
+ * This is a copy of {@link Partition#heapSelectRange(double[], int, int, int, int)}.
+ * It uses no optimised versions for small heaps.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ */
+ static void heapSelectRange0(double[] a, int left, int right, int ka, int kb) {
+ if (right - left < Partition.MIN_HEAPSELECT_SIZE) {
+ Sorting.sort(a, left, right);
+ return;
+ }
+ if (kb - left < right - ka) {
+ Partition.heapSelectLeft(a, left, right, kb, kb - ka);
+ } else {
+ Partition.heapSelectRight(a, left, right, ka, kb - ka);
+ }
+ }
+
+ /**
+ * Partition the elements between {@code ka} and {@code kb} using a heap select
+ * algorithm. It is assumed {@code left <= ka <= kb <= right}.
+ *
+ * Note:
+ *
+ * This is a copy of {@link Partition#heapSelectRange(double[], int, int, int, int)}.
+ * It uses no optimised versions for small heap of size 1.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ */
+ static void heapSelectRange1(double[] a, int left, int right, int ka, int kb) {
+ if (right - left < Partition.MIN_HEAPSELECT_SIZE) {
+ Sorting.sort(a, left, right);
+ return;
+ }
+ if (kb - left < right - ka) {
+ // Optimise
+ if (kb == left) {
+ Partition.selectMinIgnoreZeros(a, left, right);
+ } else {
+ Partition.heapSelectLeft(a, left, right, kb, kb - ka);
+ }
+ } else {
+ // Optimise
+ if (ka == right) {
+ Partition.selectMaxIgnoreZeros(a, left, right);
+ } else {
+ Partition.heapSelectRight(a, left, right, ka, kb - ka);
+ }
+ }
+ }
+
+ /**
+ * Partition the elements between {@code ka} and {@code kb} using a heap select
+ * algorithm. It is assumed {@code left <= ka <= kb <= right}.
+ *
+ * Note:
+ *
+ * This is a copy of {@link Partition#heapSelectRange(double[], int, int, int, int)}.
+ * It uses optimised versions for small heap of size 2.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ */
+ static void heapSelectRange2(double[] a, int left, int right, int ka, int kb) {
+ if (right - left < Partition.MIN_HEAPSELECT_SIZE) {
+ Sorting.sort(a, left, right);
+ return;
+ }
+ if (kb - left < right - ka) {
+ // Optimise
+ if (kb - 1 <= left) {
+ Partition.selectMin2IgnoreZeros(a, left, right);
+ } else {
+ Partition.heapSelectLeft(a, left, right, kb, kb - ka);
+ }
+ } else {
+ // Optimise
+ if (ka + 1 >= right) {
+ Partition.selectMax2IgnoreZeros(a, left, right);
+ } else {
+ Partition.heapSelectRight(a, left, right, ka, kb - ka);
+ }
+ }
+ }
+
+ /**
+ * Partition the elements between {@code ka} and {@code kb} using a heap select
+ * algorithm. It is assumed {@code left <= ka <= kb <= right}.
+ *
+ * Note:
+ *
+ * This is a copy of {@link Partition#heapSelectRange(double[], int, int, int, int)}.
+ * It uses optimised versions for small heap of size 1 and 2.
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ */
+ static void heapSelectRange12(double[] a, int left, int right, int ka, int kb) {
+ if (right - left < Partition.MIN_HEAPSELECT_SIZE) {
+ Sorting.sort(a, left, right);
+ return;
+ }
+ if (kb - left < right - ka) {
+ // Optimise
+ if (kb - 1 <= left) {
+ if (kb == left) {
+ Partition.selectMinIgnoreZeros(a, left, right);
+ } else {
+ Partition.selectMin2IgnoreZeros(a, left, right);
+ }
+ } else {
+ Partition.heapSelectLeft(a, left, right, kb, kb - ka);
+ }
+ } else {
+ // Optimise
+ if (ka + 1 >= right) {
+ if (ka == right) {
+ Partition.selectMaxIgnoreZeros(a, left, right);
+ } else {
+ Partition.selectMax2IgnoreZeros(a, left, right);
+ }
+ } else {
+ Partition.heapSelectRight(a, left, right, ka, kb - ka);
+ }
+ }
+ }
+
+ /**
+ * Extract the data at the specified indices.
+ *
+ * @param data Data.
+ * @param l Lower bound (inclusive).
+ * @param r Upper bound (inclusive).
+ * @return the data
+ */
+ private static double[] extractIndices(double[] data, int l, int r) {
+ final double[] x = new double[r - l + 1];
+ for (int i = l; i <= r; i++) {
+ x[i - l] = data[i];
+ }
+ return x;
+ }
+ }
+
+ /**
+ * Source of an search function. This is a function that find an index
+ * in a sorted list of indices, e.g. a binary search.
+ */
+ @State(Scope.Benchmark)
+ public static class IndexSearchFunctionSource {
+ /** Name of the source. */
+ @Param({"Binary",
+ //"binarySearch",
+ "Scan"})
+ private String name;
+
+ /** The action. */
+ private SearchFunction function;
+
+ /**
+ * Define a search function.
+ */
+ public interface SearchFunction {
+ /**
+ * Find the index of the element {@code k}, or the closest index
+ * to the element (implementation definitions may vary).
+ *
+ * @param a Data.
+ * @param k Element.
+ * @return the index
+ */
+ int find(int[] a, int k);
+ }
+
+ /**
+ * @return the function
+ */
+ public SearchFunction getFunction() {
+ return function;
+ }
+
+ /**
+ * Create the function.
+ */
+ @Setup
+ public void setup() {
+ Objects.requireNonNull(name);
+ if ("Binary".equals(name)) {
+ function = (keys, k) -> Partition.searchLessOrEqual(keys, 0, keys.length - 1, k);
+ } else if ("binarySearch".equals(name)) {
+ function = (keys, k) -> Arrays.binarySearch(keys, 0, keys.length, k);
+ } else if ("Scan".equals(name)) {
+ function = (keys, k) -> {
+ // Assume that k >= keys[0]
+ int i = keys.length;
+ do {
+ --i;
+ } while (keys[i] > k);
+ return i;
+ };
+ } else {
+ throw new IllegalStateException("Unknown index search function: " + name);
+ }
+ }
+ }
+
+ /**
+ * Benchmark a sort on the data.
+ *
+ * @param function Source of the function.
+ * @param source Source of the data.
+ * @param bh Data sink.
+ */
+ @Benchmark
+ public void sort(SortFunctionSource function, SortSource source, Blackhole bh) {
+ final int size = source.size();
+ final Consumer The {@link SearchableInterval} is created for the indices of interest. These are then
+ * cut at all points in the interval between indices to simulate a partition algorithm
+ * dividing the data and requiring a new interval to use in each part:
+ * Note: If a cut is made in the interval then the smallest region of data
+ * that was most recently partitioned was the length between the two flanking k.
+ * This involves a full scan (and partitioning) over the data of length (k2 - k1).
+ * A BitSet-type structure will require a scan over 1/64 of this length of data
+ * to find the next and previous index from a cut point. In practice
+ * the interval may be partitioned over a much larger length, e.g. (kn - k1).
+ * Thus the length of time for the partition algorithm is expected to be at least
+ * 64x the length of time for the BitSet-type scan. The disadvantage of the
+ * BitSet-type structure is memory consumption. For a small number of keys the
+ * structures that search the entire set of keys are fast enough. At very high
+ * density the BitSet-type structures are preferred.
+ *
+ * @param function Source of the interval.
+ * @param source Source of the data.
+ * @return value to consume
+ */
+ @Benchmark
+ public long searchableIntervalNextPrevious(SearchableIntervalSource function, SplitIndexSource source) {
+ final int[][] indices = source.getIndices();
+ final int[][] points = source.getPoints();
+ // Ensure we have something to consume during the benchmark
+ long sum = 0;
+ for (int i = 0; i < indices.length; i++) {
+ final int[] x = indices[i];
+ final int[] p = points[i];
+ final SearchableInterval interval = function.create(x);
+ for (final int k : p) {
+ sum += interval.nextIndex(k);
+ sum += interval.previousIndex(k);
+ }
+ }
+ return sum;
+ }
+
+ /**
+ * Benchmark the tracking of an interval of indices during a partition algorithm.
+ *
+ * This is similar to
+ * {@link #searchableIntervalNextPrevious(SearchableIntervalSource, SplitIndexSource)}.
+ * It uses the {@link SearchableInterval#split(int, int, int[])} method. This requires
+ * {@code k} to be in an open interval. Some modes of the {@link IndexSource} do not
+ * ensure that {@code left < k < right} for all split points so we have to check this
+ * before calling the split method (it is a fixed overhead for the benchmark).
+ *
+ * @param function Source of the interval.
+ * @param source Source of the data.
+ * @return value to consume
+ */
+ @Benchmark
+ public long searchableIntervalSplit(SearchableIntervalSource function, SplitIndexSource source) {
+ final int[][] indices = source.getIndices();
+ final int[][] points = source.getPoints();
+ // Ensure we have something to consume during the benchmark
+ long sum = 0;
+ final int[] bound = {0};
+ for (int i = 0; i < indices.length; i++) {
+ final int[] x = indices[i];
+ final int[] p = points[i];
+ // Note: A partition algorithm would only call split if there are indices
+ // above and below the split point.
+ final SearchableInterval interval = function.create(x);
+ final int left = interval.left();
+ final int right = interval.right();
+ for (final int k : p) {
+ // Check k is in the open interval (left, right)
+ if (left < k && k < right) {
+ sum += interval.split(k, k, bound);
+ sum += bound[0];
+ }
+ }
+ }
+ return sum;
+ }
+
+ /**
+ * Benchmark the creation of an interval of indices for controlling a partition
+ * algorithm.
+ *
+ * This baselines the
+ * {@link #searchableIntervalNextPrevious(SearchableIntervalSource, SplitIndexSource)}
+ * benchmark. For the BitSet-type structures a large overhead is the memory allocation
+ * to create the {@link SearchableInterval}. Note that this will be at most 1/64 the
+ * size of the array that is being partitioned and in practice this overhead is not
+ * significant.
+ *
+ * @param function Source of the interval.
+ * @param source Source of the data.
+ * @param bh Data sink.
+ */
+ @Benchmark
+ public void createSearchableInterval(SearchableIntervalSource function, IndexSource source, Blackhole bh) {
+ final int[][] indices = source.getIndices();
+ for (final int[] x : indices) {
+ bh.consume(function.create(x));
+ }
+ }
+
+ /**
+ * Benchmark the splitting of an interval of indices during a partition algorithm.
+ *
+ * This is similar to
+ * {@link #searchableIntervalSplit(SearchableIntervalSource, SplitIndexSource)}. It
+ * uses the {@link UpdatingInterval#splitLeft(int, int)} method by recursive division
+ * of the indices.
+ *
+ * @param function Source of the interval.
+ * @param source Source of the data.
+ * @param bh Data sink.
+ */
+ @Benchmark
+ public void updatingIntervalSplit(UpdatingIntervalSource function, IndexSource source, Blackhole bh) {
+ final int[][] indices = source.getIndices();
+ final int s = source.getMinSeparation();
+ for (int i = 0; i < indices.length; i++) {
+ split(function.create(indices[i]), s, bh);
+ }
+ }
+
+ /**
+ * Recursively split the interval until the length is below the provided separation.
+ * Consume the interval when no more divides can occur. Simulates a single-pivot
+ * partition algorithm.
+ *
+ * @param interval Interval.
+ * @param s Minimum separation between left and right.
+ * @param bh Data sink.
+ */
+ private static void split(UpdatingInterval interval, int s, Blackhole bh) {
+ int l = interval.left();
+ final int r = interval.right();
+ // Note: A partition algorithm would only call split if there are indices
+ // above and below the split point.
+ if (r - l > s) {
+ final int middle = (l + r) >>> 1;
+ // recurse left
+ split(interval.splitLeft(middle, middle), s, bh);
+ // continue on right side
+ l = interval.left();
+ }
+ bh.consume(interval);
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/Sorting.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/Sorting.java
new file mode 100644
index 000000000..3c2dfe3d7
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/Sorting.java
@@ -0,0 +1,2541 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+import java.util.Arrays;
+
+/**
+ * Support class for sorting arrays.
+ *
+ * Optimal sorting networks are used for small fixed size array sorting.
+ *
+ * Note: Requires that the floating-point data contains no NaN values; sorting
+ * does not respect the order of signed zeros imposed by {@link Double#compare(double, double)}.
+ *
+ * @see Sorting network (Wikipedia)
+ * @see Sorting Networks (Bert Dobbelaere)
+ * @since 1.2
+ */
+final class Sorting {
+ /** The upper threshold to use a modified insertion sort to find unique indices. */
+ private static final int UNIQUE_INSERTION_SORT = 20;
+
+ /** No instances. */
+ private Sorting() {}
+
+ /**
+ * Sorts an array using an insertion sort.
+ *
+ * This method is fast up to approximately 40 - 80 values.
+ *
+ * The {@code internal} flag indicates that the value at {@code data[begin - 1]}
+ * is sorted.
+ *
+ * @param data Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param internal Internal flag.
+ */
+ static void sort(double[] data, int left, int right, boolean internal) {
+ if (internal) {
+ // Assume data[begin - 1] is a pivot and acts as a sentinal on the range.
+ // => no requirement to check j >= left.
+
+ // Note:
+ // Benchmarking fails to show that this is faster
+ // even though it is the same method with fewer instructions.
+ // There may be an issue with the benchmarking data, or noise in the timings.
+
+ // There are also paired-insertion sort methods for internal regions
+ // which benchmark as slower on random data.
+ // On structured data with many ascending runs they are faster.
+
+ for (int i = left; ++i <= right;) {
+ final double v = data[i];
+ // Move preceding higher elements above (if required)
+ if (v < data[i - 1]) {
+ int j = i;
+ while (v < data[--j]) {
+ data[j + 1] = data[j];
+ }
+ data[j + 1] = v;
+ }
+ }
+
+ } else {
+ for (int i = left; ++i <= right;) {
+ final double v = data[i];
+ // Move preceding higher elements above (if required)
+ if (v < data[i - 1]) {
+ int j = i;
+ while (--j >= left && v < data[j]) {
+ data[j + 1] = data[j];
+ }
+ data[j + 1] = v;
+ }
+ }
+ }
+ }
+
+ /**
+ * Sorts an array using an insertion sort.
+ *
+ * This method is fast up to approximately 40 - 80 values.
+ *
+ * @param data Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void sort(double[] data, int left, int right) {
+ for (int i = left; ++i <= right;) {
+ final double v = data[i];
+ // Move preceding higher elements above (if required)
+ if (v < data[i - 1]) {
+ int j = i;
+ while (--j >= left && v < data[j]) {
+ data[j + 1] = data[j];
+ }
+ data[j + 1] = v;
+ }
+ }
+ }
+
+ /**
+ * Sorts an array using an insertion sort.
+ *
+ * This method is fast up to approximately 40 - 80 values.
+ *
+ * @param data Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void sortb(double[] data, int left, int right) {
+ for (int i = left; ++i <= right;) {
+ final double v = data[i];
+ // Move preceding higher elements above.
+ // This method always uses a loop. It benchmarks slower than the
+ // method that uses an if statement to check the loop is required.
+ int j = i;
+ while (--j >= left && v < data[j]) {
+ data[j + 1] = data[j];
+ }
+ data[j + 1] = v;
+ }
+ }
+
+ /**
+ * Sorts an array using a paired insertion sort.
+ *
+ * Warning: It is assumed that the value at {@code data[begin - 1]} is sorted.
+ *
+ * @param data Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void sortPairedInternal1(double[] data, int left, int right) {
+ // Assume data[begin - 1] is a pivot and acts as a sentinal on the range.
+ // => no requirement to check j >= left.
+
+ // Paired insertion sort. Move largest of two elements down the array.
+ // When inserted move the smallest of the two elements down the rest of the array.
+
+ // Pairs require an even length so start at left for even or left + 1 for odd.
+ // This will do nothing when right <= left.
+
+ // Using one index which requires i += 2.
+ for (int i = left + ((right - left + 1) & 0x1); i < right; i += 2) {
+ double v1 = data[i];
+ double v2 = data[i + 1];
+ // Sort the pair
+ if (v2 < v1) {
+ v1 = v2;
+ v2 = data[i];
+ }
+ // Move preceding higher elements above the largest value
+ int j = i;
+ while (v2 < data[--j]) {
+ data[j + 2] = data[j];
+ }
+ // Insert at j + 2. Update j for the next scan down.
+ data[++j + 1] = v2;
+ // Move preceding higher elements above the smallest value
+ while (v1 < data[--j]) {
+ data[j + 1] = data[j];
+ }
+ data[j + 1] = v1;
+ }
+ }
+
+ /**
+ * Sorts an array using a paired insertion sort.
+ *
+ * Warning: It is assumed that the value at {@code data[begin - 1]} is sorted.
+ *
+ * @param data Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void sortPairedInternal2(double[] data, int left, int right) {
+ // Assume data[begin - 1] is a pivot and acts as a sentinal on the range.
+ // => no requirement to check j >= left.
+
+ // Paired insertion sort. Move largest of two elements down the array.
+ // When inserted move the smallest of the two elements down the rest of the array.
+
+ // Pairs require an even length so start at left for even or left + 1 for odd.
+ // This will do nothing when right <= left.
+
+ // Use pair (i, j)
+ for (int i = left + ((right - left + 1) & 0x1), j = i; ++j <= right; i = ++j) {
+ double v1 = data[i];
+ double v2 = data[j];
+ // Sort the pair
+ if (v2 < v1) {
+ v1 = v2;
+ v2 = data[i];
+ }
+ // Move preceding higher elements above the largest value
+ while (v2 < data[--i]) {
+ data[i + 2] = data[i];
+ }
+ // Insert at i + 2. Update i for the next scan down.
+ data[++i + 1] = v2;
+ // Move preceding higher elements above the smallest value
+ while (v1 < data[--i]) {
+ data[i + 1] = data[i];
+ }
+ data[i + 1] = v1;
+ }
+ }
+
+ /**
+ * Sorts an array using a paired insertion sort.
+ *
+ * Warning: It is assumed that the value at {@code data[begin - 1]} is sorted.
+ *
+ * @param data Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void sortPairedInternal3(double[] data, int left, int right) {
+ // Assume data[begin - 1] is a pivot and acts as a sentinal on the range.
+ // => no requirement to check j >= left.
+
+ // Paired insertion sort. Move largest of two elements down the array.
+ // When inserted move the smallest of the two elements down the rest of the array.
+
+ // Pairs require an even length so start at left for even or left + 1 for odd.
+ // This will do nothing when right <= left.
+
+ // As above but only move if required
+ for (int i = left + ((right - left + 1) & 0x1), j = i; ++j <= right; i = ++j) {
+ double v1 = data[i];
+ double v2 = data[j];
+ // Sort the pair
+ if (v2 < v1) {
+ v1 = v2;
+ v2 = data[i];
+ // In the event of no move of v2
+ data[j] = v2;
+ }
+ // Move preceding higher elements above the largest value (if required)
+ if (v2 < data[i - 1]) {
+ while (v2 < data[--i]) {
+ data[i + 2] = data[i];
+ }
+ // Insert at i + 2. Update i for the next scan down.
+ data[++i + 1] = v2;
+ }
+ // Move preceding higher elements above the smallest value (if required)
+ if (v1 < data[i - 1]) {
+ while (v1 < data[--i]) {
+ data[i + 1] = data[i];
+ }
+ // Insert at i+1
+ i++;
+ }
+ // Always write v1 as v2 may have moved down
+ data[i] = v1;
+ }
+ }
+
+ /**
+ * Sorts an array using a paired insertion sort.
+ *
+ * Warning: It is assumed that the value at {@code data[begin - 1]} is sorted.
+ *
+ * @param data Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ */
+ static void sortPairedInternal4(double[] data, int left, int right) {
+ // Assume data[begin - 1] is a pivot and acts as a sentinal on the range.
+ // => no requirement to check j >= left.
+
+ // Paired insertion sort. Move largest of two elements down the array.
+ // When inserted move the smallest of the two elements down the rest of the array.
+
+ // Pairs require an even length so start at left for even or left + 1 for odd.
+ // This will do nothing when right <= left.
+
+ // As above but only move if required
+ for (int i = left + ((right - left + 1) & 0x1), j = i; ++j <= right; i = ++j) {
+ double v1 = data[i];
+ double v2 = data[j];
+ // Sort the pair
+ if (v2 < v1) {
+ v1 = v2;
+ v2 = data[i];
+ // In the event of no moves
+ data[j] = v2;
+ data[i] = v1;
+ }
+ // Move preceding higher elements (if required, only test the smallest)
+ if (v1 < data[i - 1]) {
+ // Move preceding higher elements above the largest value
+ while (v2 < data[--i]) {
+ data[i + 2] = data[i];
+ }
+ // Insert at i + 2. Update i for the next scan down.
+ data[++i + 1] = v2;
+ // Move preceding higher elements above the smallest value
+ while (v1 < data[--i]) {
+ data[i + 1] = data[i];
+ }
+ data[i + 1] = v1;
+ }
+ }
+ }
+
+ /**
+ * Place the minimum of 3 elements in {@code a}; and the larger
+ * two elements in {@code b, c}.
+ *
+ * @param x Values
+ * @param a Index.
+ * @param b Index.
+ * @param c Index.
+ */
+ static void min3(double[] x, int a, int b, int c) {
+ if (x[b] < x[a]) {
+ final double v = x[b];
+ x[b] = x[a];
+ x[a] = v;
+ }
+ if (x[c] < x[a]) {
+ final double v = x[c];
+ x[c] = x[a];
+ x[a] = v;
+ }
+ }
+
+ /**
+ * Place the maximum of 3 elements in {@code c}; and the smaller
+ * two elements in {@code a, b}.
+ *
+ * @param x Values
+ * @param a Index.
+ * @param b Index.
+ * @param c Index.
+ */
+ static void max3(double[] x, int a, int b, int c) {
+ if (x[c] < x[b]) {
+ final double u = x[c];
+ x[c] = x[b];
+ x[b] = u;
+ }
+ if (x[c] < x[a]) {
+ final double v = x[c];
+ x[c] = x[a];
+ x[a] = v;
+ }
+ }
+
+ /**
+ * Sorts the given indices in an array.
+ *
+ * Assumes all indices are valid and distinct.
+ *
+ * Data are arranged such that:
+ * If indices are duplicated elements will not be correctly ordered.
+ * However in this case data will contain the same values and may be partially ordered.
+ *
+ * @param data Data array.
+ * @param i0 Index.
+ * @param i1 Index.
+ * @param i2 Index.
+ */
+ static void sort3(double[] data, int i0, int i1, int i2) {
+ // Decision tree avoiding swaps:
+ // Order [(0,2)]
+ // Move point 1 above point 2 or below point 0
+ final double x = data[i0];
+ final double y = data[i1];
+ final double z = data[i2];
+ if (z < x) {
+ if (y < z) {
+ data[i0] = y;
+ data[i1] = z;
+ data[i2] = x;
+ return;
+ }
+ if (x < y) {
+ data[i0] = z;
+ data[i1] = x;
+ data[i2] = y;
+ return;
+ }
+ // z < y < z
+ data[i0] = z;
+ data[i2] = x;
+ return;
+ }
+ if (y < x) {
+ // y < x < z
+ data[i0] = y;
+ data[i1] = x;
+ return;
+ }
+ if (z < y) {
+ // x < z < y
+ data[i1] = z;
+ data[i2] = y;
+ }
+ // x < y < z
+ }
+
+ /**
+ * Sorts the given indices in an array.
+ *
+ * Note: Requires that the range contains no NaN values. It does not respect the
+ * order of signed zeros.
+ *
+ * Assumes all indices are valid and distinct.
+ *
+ * Data are arranged such that:
+ * If indices are duplicated elements will not be correctly ordered.
+ * However in this case data will contain the same values and may be partially ordered.
+ *
+ * @param data Data array.
+ * @param i0 Index.
+ * @param i1 Index.
+ * @param i2 Index.
+ */
+ static void sort3b(double[] data, int i0, int i1, int i2) {
+ // Order pair:
+ //[(0,2)]
+ // Move point 1 above point 2 or below point 0
+ if (data[i2] < data[i0]) {
+ final double v = data[i2];
+ data[i2] = data[i0];
+ data[i0] = v;
+ }
+ if (data[i2] < data[i1]) {
+ final double v = data[i2];
+ data[i2] = data[i1];
+ data[i1] = v;
+ } else if (data[i1] < data[i0]) {
+ final double v = data[i1];
+ data[i1] = data[i0];
+ data[i0] = v;
+ }
+ }
+
+ /**
+ * Sorts the given indices in an array.
+ *
+ * Note: Requires that the range contains no NaN values. It does not respect the
+ * order of signed zeros.
+ *
+ * Assumes all indices are valid and distinct.
+ *
+ * Data are arranged such that:
+ *
+ * If indices are duplicated elements will not be correctly ordered.
+ * However in this case data will contain the same values and may be partially
+ * ordered.
+ *
+ * @param data Data array.
+ * @param i0 Index.
+ * @param i1 Index.
+ * @param i2 Index.
+ */
+ static void sort3c(double[] data, int i0, int i1, int i2) {
+ // Order pairs:
+ // [(0,2)]
+ // [(0,1)]
+ // [(1,2)]
+ if (data[i2] < data[i0]) {
+ final double v = data[i2];
+ data[i2] = data[i0];
+ data[i0] = v;
+ }
+ if (data[i1] < data[i0]) {
+ final double v = data[i1];
+ data[i1] = data[i0];
+ data[i0] = v;
+ }
+ if (data[i2] < data[i1]) {
+ final double v = data[i2];
+ data[i2] = data[i1];
+ data[i1] = v;
+ }
+ }
+
+ /**
+ * Sorts the given indices in an array using an insertion sort.
+ *
+ * Assumes all indices are valid and distinct.
+ *
+ * Data are arranged such that:
+ * If indices are duplicated elements will not be correctly ordered.
+ * However in this case data will contain the same values and may be partially ordered.
+ *
+ * @param data Data array.
+ * @param i0 Index.
+ * @param i1 Index.
+ * @param i2 Index.
+ * @param i3 Index.
+ */
+ static void sort4(double[] data, int i0, int i1, int i2, int i3) {
+ // Uses an optimal sorting network from Knuth's Art of Computer Programming.
+ // 5 comparisons.
+ // Order pairs:
+ //[(0,2),(1,3)]
+ //[(0,1),(2,3)]
+ //[(1,2)]
+ if (data[i3] < data[i1]) {
+ final double u = data[i3];
+ data[i3] = data[i1];
+ data[i1] = u;
+ }
+ if (data[i2] < data[i0]) {
+ final double v = data[i2];
+ data[i2] = data[i0];
+ data[i0] = v;
+ }
+
+ if (data[i3] < data[i2]) {
+ final double u = data[i3];
+ data[i3] = data[i2];
+ data[i2] = u;
+ }
+ if (data[i1] < data[i0]) {
+ final double v = data[i1];
+ data[i1] = data[i0];
+ data[i0] = v;
+ }
+
+ if (data[i2] < data[i1]) {
+ final double u = data[i2];
+ data[i2] = data[i1];
+ data[i1] = u;
+ }
+ }
+
+ /**
+ * Sorts the given indices in an array.
+ *
+ * Assumes all indices are valid and distinct.
+ *
+ * Data are arranged such that:
+ * If indices are duplicated elements will not be correctly ordered.
+ * However in this case data will contain the same values and may be partially ordered.
+ *
+ * @param data Data array.
+ * @param i0 Index.
+ * @param i1 Index.
+ * @param i2 Index.
+ * @param i3 Index.
+ * @param i4 Index.
+ */
+ static void sort5(double[] data, int i0, int i1, int i2, int i3, int i4) {
+ // Uses an optimal sorting network from Knuth's Art of Computer Programming.
+ // 9 comparisons.
+ // Order pairs:
+ // [(0,3),(1,4)]
+ // [(0,2),(1,3)]
+ // [(0,1),(2,4)]
+ // [(1,2),(3,4)]
+ // [(2,3)]
+ if (data[i4] < data[i1]) {
+ final double u = data[i4];
+ data[i4] = data[i1];
+ data[i1] = u;
+ }
+ if (data[i3] < data[i0]) {
+ final double v = data[i3];
+ data[i3] = data[i0];
+ data[i0] = v;
+ }
+
+ if (data[i3] < data[i1]) {
+ final double u = data[i3];
+ data[i3] = data[i1];
+ data[i1] = u;
+ }
+ if (data[i2] < data[i0]) {
+ final double v = data[i2];
+ data[i2] = data[i0];
+ data[i0] = v;
+ }
+
+ if (data[i4] < data[i2]) {
+ final double u = data[i4];
+ data[i4] = data[i2];
+ data[i2] = u;
+ }
+ if (data[i1] < data[i0]) {
+ final double v = data[i1];
+ data[i1] = data[i0];
+ data[i0] = v;
+ }
+
+ if (data[i4] < data[i3]) {
+ final double u = data[i4];
+ data[i4] = data[i3];
+ data[i3] = u;
+ }
+ if (data[i2] < data[i1]) {
+ final double v = data[i2];
+ data[i2] = data[i1];
+ data[i1] = v;
+ }
+
+ if (data[i3] < data[i2]) {
+ final double u = data[i3];
+ data[i3] = data[i2];
+ data[i2] = u;
+ }
+ }
+
+ /**
+ * Sorts the given indices in an array.
+ *
+ * Assumes all indices are valid and distinct.
+ *
+ * Data are arranged such that:
+ * If indices are duplicated elements will not be correctly ordered.
+ * However in this case data will contain the same values and may be partially ordered.
+ *
+ * @param data Data array.
+ * @param i0 Index.
+ * @param i1 Index.
+ * @param i2 Index.
+ * @param i3 Index.
+ * @param i4 Index.
+ */
+ static void sort5b(double[] data, int i0, int i1, int i2, int i3, int i4) {
+ // Sorting network for size 5 is 9 comparisons (see sort5).
+ // Sorting network for size 4 is 5 comparisons + 2 or 3 extra.
+ // This method benchmarks marginally faster (~1%) than the sorting network of size 5
+ // on length 5 data. When the data is larger and the indices are uniformly
+ // spread across the range, the sorting network is faster.
+
+ // Order quadruple:
+ //[(0,1,3,4)]
+ // Move point 2 above points 3,4 or below points 0,1
+ sort4(data, i0, i1, i3, i4);
+ final double u = data[i2];
+ if (u > data[i3]) {
+ data[i2] = data[i3];
+ data[i3] = u;
+ if (u > data[i4]) {
+ data[i3] = data[i4];
+ data[i4] = u;
+ }
+ } else if (u < data[i1]) {
+ data[i2] = data[i1];
+ data[i1] = u;
+ if (u < data[i0]) {
+ data[i1] = data[i0];
+ data[i0] = u;
+ }
+ }
+ }
+
+ /**
+ * Sorts the given indices in an array.
+ *
+ * Assumes all indices are valid and distinct.
+ *
+ * Data are arranged such that:
+ * If indices are duplicated elements will not be correctly ordered.
+ * However in this case data will contain the same values and may be partially ordered.
+ *
+ * @param data Data array.
+ * @param i0 Index.
+ * @param i1 Index.
+ * @param i2 Index.
+ * @param i3 Index.
+ * @param i4 Index.
+ */
+ static void sort5c(double[] data, int i0, int i1, int i2, int i3, int i4) {
+ // Sorting of 5 elements in optimum 7 comparisons.
+ // Code adapted from Raphael, Computer Science Stack Exchange.
+ // https://cs.stackexchange.com/a/44982
+ // https://gist.github.com/akerbos/5acb345ff3d41bc888c4
+
+ // 1. Sort the first two pairs.
+ if (data[i1] < data[i0]) {
+ final double u = data[i1];
+ data[i1] = data[i0];
+ data[i0] = u;
+ }
+ if (data[i3] < data[i2]) {
+ final double v = data[i3];
+ data[i3] = data[i2];
+ data[i2] = v;
+ }
+
+ // 2. Order the pairs w.r.t. their respective larger element.
+ // Call the result [a,b,c,d,e]; we know a x[c]) {
+ final double u = x[c];
+ x[c] = x[b];
+ x[b] = u;
+ } else if (x[c] > x[d]) {
+ // a--c
+ // b--d
+ final double xc = x[d];
+ x[d] = x[c];
+ x[c] = xc;
+ // a--d
+ // b--c
+ if (x[a] > xc) {
+ x[c] = x[a];
+ // Move a pair to maintain the sorted order
+ //x[a] = xc;
+ x[a] = x[b];
+ x[b] = xc;
+ }
+ }
+ }
+
+ /**
+ * Place the upper median of 4 elements in {@code c}; the smaller two elements in
+ * {@code a,b}; and the larger element in {@code d}.
+ *
+ * @param x Values
+ * @param a Index.
+ * @param b Index.
+ * @param c Index.
+ * @param d Index.
+ */
+ static void upperMedian4c(double[] x, int a, int b, int c, int d) {
+ // 4 comparisons
+ if (x[d] < x[b]) {
+ final double u = x[d];
+ x[d] = x[b];
+ x[b] = u;
+ }
+ if (x[c] < x[a]) {
+ final double v = x[c];
+ x[c] = x[a];
+ x[a] = v;
+ }
+ // a--c
+ // b--d
+ if (x[d] < x[c]) {
+ final double v = x[a];
+ final double u = x[c];
+ x[a] = x[b];
+ x[c] = x[d];
+ x[b] = v;
+ x[d] = u;
+ }
+ // a--c
+ // b--d
+ if (x[c] < x[b]) {
+ final double u = x[c];
+ x[c] = x[b];
+ x[b] = u;
+ }
+ }
+
+ /**
+ * Place the upper median of 4 elements in {@code c}; the smaller two elements in
+ * {@code a,b}; and the larger element in {@code d}.
+ *
+ * @param x Values
+ * @param a Index.
+ * @param b Index.
+ * @param c Index.
+ * @param d Index.
+ */
+ static void upperMedian4d(double[] x, int a, int b, int c, int d) {
+ // 4 comparisons
+ if (x[d] < x[a]) {
+ final double u = x[d];
+ x[d] = x[a];
+ x[a] = u;
+ }
+ if (x[c] < x[b]) {
+ final double v = x[c];
+ x[c] = x[b];
+ x[b] = v;
+ }
+ // a--d
+ // b--c
+ if (x[d] < x[c]) {
+ final double xc = x[d];
+ x[d] = x[c];
+ x[c] = xc;
+ // a--c
+ // b--d
+ if (xc < x[b]) {
+ x[c] = x[b];
+ x[b] = xc;
+ // fully sorted here
+ }
+ // else full sort requires a:b ordering
+ // Not fully sorted for 6 of 24 permutations
+ } else if (x[c] < x[a]) {
+ // a--d
+ // b--c
+ final double v = x[a];
+ // Do a full sort for 1 additional swap
+ x[a] = x[b];
+ x[b] = x[c];
+ x[c] = v;
+ // minimum swaps to put the lower median at b
+ //x[d] = x[b];
+ //x[b] = v;
+ }
+ }
+
+ /**
+ * Return the median of a continuous block of 5 elements.
+ * Data may be partially reordered.
+ *
+ * @param a Values
+ * @param i1 First index.
+ * @return the median index
+ */
+ static int median5(double[] a, int i1) {
+ final int i2 = i1 + 1;
+ final int i3 = i1 + 2;
+ final int i4 = i1 + 3;
+ final int i5 = i1 + 4;
+ // 6 comparison decision tree
+ // Possible median in parentheses
+ // (12345)
+ if (a[i2] < a[i1]) {
+ final double v = a[i2];
+ a[i2] = a[i1];
+ a[i1] = v;
+ }
+ if (a[i4] < a[i3]) {
+ final double v = a[i4];
+ a[i4] = a[i3];
+ a[i3] = v;
+ }
+ // (1<2 3<4 5)
+ if (a[i1] < a[i3]) {
+ // 1(2 3<4 5)
+ if (a[i5] < a[i2]) {
+ final double v = a[i5];
+ a[i5] = a[i2];
+ a[i2] = v;
+ }
+ // 1(2<5 3<4)
+ if (a[i2] < a[i3]) {
+ // 1,2(5 3<4)
+ return a[i5] < a[i3] ? i5 : i3;
+ }
+ // 1,3(2<5 4)
+ return a[i2] < a[i4] ? i2 : i4;
+ }
+ // 3(1<2 4 5)
+ if (a[i5] < a[i4]) {
+ final double v = a[i5];
+ a[i5] = a[i4];
+ a[i4] = v;
+ }
+ // 3(1<2 4<5)
+ if (a[i1] < a[i4]) {
+ // 3,1(2 4<5)
+ return a[i2] < a[i4] ? i2 : i4;
+ }
+ // 3,4(1<2 5)
+ return a[i1] < a[i5] ? i1 : i5;
+ }
+
+ /**
+ * Return the median of 5 elements. Data may be partially reordered.
+ *
+ * @param a Values
+ * @param i1 Index.
+ * @param i2 Index.
+ * @param i3 Index.
+ * @param i4 Index.
+ * @param i5 Index.
+ * @return the median index
+ */
+ static int median5(double[] a, int i1, int i2, int i3, int i4, int i5) {
+ // 6 comparison decision tree
+ // Possible median in parentheses
+ // (12345)
+ if (a[i2] < a[i1]) {
+ final double v = a[i2];
+ a[i2] = a[i1];
+ a[i1] = v;
+ }
+ if (a[i4] < a[i3]) {
+ final double v = a[i4];
+ a[i4] = a[i3];
+ a[i3] = v;
+ }
+ // (1<2 3<4 5)
+ if (a[i1] < a[i3]) {
+ // 1(2 3<4 5)
+ if (a[i5] < a[i2]) {
+ final double v = a[i5];
+ a[i5] = a[i2];
+ a[i2] = v;
+ }
+ // 1(2<5 3<4)
+ if (a[i2] < a[i3]) {
+ // 1,2(5 3<4)
+ return a[i5] < a[i3] ? i5 : i3;
+ }
+ // 1,3(2<5 4)
+ return a[i2] < a[i4] ? i2 : i4;
+ }
+ // 3(1<2 4 5)
+ if (a[i5] < a[i4]) {
+ final double v = a[i5];
+ a[i5] = a[i4];
+ a[i4] = v;
+ }
+ // 3(1<2 4<5)
+ if (a[i1] < a[i4]) {
+ // 3,1(2 4<5)
+ return a[i2] < a[i4] ? i2 : i4;
+ }
+ // 3,4(1<2 5)
+ return a[i1] < a[i5] ? i1 : i5;
+ }
+
+ /**
+ * Return the median of a continuous block of 5 elements.
+ * Data may be partially reordered.
+ *
+ * @param a Values
+ * @param i1 First index.
+ * @return the median index
+ */
+ static int median5b(double[] a, int i1) {
+ final int i2 = i1 + 1;
+ final int i3 = i1 + 2;
+ final int i4 = i1 + 3;
+ final int i5 = i1 + 4;
+ // 6 comparison decision tree
+ // Possible median in parentheses
+ // (12345)
+ if (a[i2] < a[i1]) {
+ final double v = a[i2];
+ a[i2] = a[i1];
+ a[i1] = v;
+ }
+ if (a[i4] < a[i3]) {
+ final double v = a[i4];
+ a[i4] = a[i3];
+ a[i3] = v;
+ }
+ // (1<2 3<4 5)
+ if (a[i1] < a[i3]) {
+ // 1(2 3<4 5)
+ if (a[i5] < a[i2]) {
+ // 1(5<2 3<4)
+ if (a[i5] < a[i3]) {
+ // 1,5(2 3<4)
+ return a[i2] < a[i3] ? i2 : i3;
+ }
+ // 1,3(2<5 4)
+ return a[i5] < a[i4] ? i5 : i4;
+ }
+ // 1(2<5 3<4)
+ if (a[i2] < a[i3]) {
+ // 1,2(5 3<4)
+ return a[i5] < a[i3] ? i5 : i3;
+ }
+ // 1,3(2<5 4)
+ return a[i2] < a[i4] ? i2 : i4;
+ }
+ // 3(1<2 4 5)
+ if (a[i5] < a[i4]) {
+ // 3(1<2 5<4)
+ if (a[i1] < a[i5]) {
+ // 3,1(2 5<4)
+ return a[i2] < a[i5] ? i2 : i5;
+ }
+ // 3,5(1<2 4)
+ return a[i1] < a[i4] ? i1 : i4;
+ }
+ // 3(1<2 4<5)
+ if (a[i1] < a[i4]) {
+ // 3,1(2 4<5)
+ return a[i2] < a[i4] ? i2 : i4;
+ }
+ // 3,4(1<2 5)
+ return a[i1] < a[i5] ? i1 : i5;
+ }
+
+ /**
+ * Return the median of a continuous block of 5 elements.
+ * Data may be partially reordered.
+ *
+ * @param a Values
+ * @param i1 First index.
+ * @return the median index
+ */
+ static int median5c(double[] a, int i1) {
+ // Sort 4
+ Sorting.sort4(a, i1, i1 + 1, i1 + 3, i1 + 4);
+ // median of [e-4, e-3, e-2]
+ int m = i1 + 2;
+ if (a[m] < a[m - 1]) {
+ --m;
+ } else if (a[m] > a[m + 1]) {
+ ++m;
+ }
+ return m;
+ }
+
+ /**
+ * Place the median of 5 elements in {@code c}; the smaller 2 elements in
+ * {@code a, b}; and the larger two elements in {@code d, e}.
+ *
+ * @param x Values
+ * @param a Index.
+ * @param b Index.
+ * @param c Index.
+ * @param d Index.
+ * @param e Index.
+ */
+ static void median5d(double[] x, int a, int b, int c, int d, int e) {
+ // 6 comparison decision tree from:
+ // Alexandrescu (2016) Fast Deterministic Selection, arXiv:1606.00484, Algorithm 4
+ // https://arxiv.org/abs/1606.00484
+ if (x[c] < x[a]) {
+ final double v = x[c];
+ x[c] = x[a];
+ x[a] = v;
+ }
+ if (x[d] < x[b]) {
+ final double u = x[d];
+ x[d] = x[b];
+ x[b] = u;
+ }
+ if (x[d] < x[c]) {
+ final double v = x[d];
+ x[d] = x[c];
+ x[c] = v;
+ final double u = x[b];
+ x[b] = x[a];
+ x[a] = u;
+ }
+ if (x[e] < x[b]) {
+ final double v = x[e];
+ x[e] = x[b];
+ x[b] = v;
+ }
+ if (x[e] < x[c]) {
+ final double u = x[e];
+ x[e] = x[c];
+ x[c] = u;
+ if (u < x[a]) {
+ x[c] = x[a];
+ x[a] = u;
+ }
+ } else {
+ if (x[c] < x[b]) {
+ final double u = x[c];
+ x[c] = x[b];
+ x[b] = u;
+ }
+ }
+ }
+
+ /**
+ * Sorts the given indices in an array.
+ *
+ * Assumes all indices are valid and distinct.
+ *
+ * Data are arranged such that:
+ * If indices are duplicated elements will not be correctly ordered.
+ * However in this case data will contain the same values and may be partially ordered.
+ *
+ * @param data Data array.
+ * @param i0 Index.
+ * @param i1 Index.
+ * @param i2 Index.
+ * @param i3 Index.
+ * @param i4 Index.
+ * @param i5 Index.
+ * @param i6 Index.
+ */
+ static void sort7(double[] data, int i0, int i1, int i2, int i3, int i4, int i5, int i6) {
+ // Uses an optimal sorting network from Knuth's Art of Computer Programming.
+ // 16 comparisons.
+ // Order pairs:
+ //[(0,6),(2,3),(4,5)]
+ //[(0,2),(1,4),(3,6)]
+ //[(0,1),(2,5),(3,4)]
+ //[(1,2),(4,6)]
+ //[(2,3),(4,5)]
+ //[(1,2),(3,4),(5,6)]
+ if (data[i5] < data[i4]) {
+ final double u = data[i5];
+ data[i5] = data[i4];
+ data[i4] = u;
+ }
+ if (data[i3] < data[i2]) {
+ final double v = data[i3];
+ data[i3] = data[i2];
+ data[i2] = v;
+ }
+ if (data[i6] < data[i0]) {
+ final double w = data[i6];
+ data[i6] = data[i0];
+ data[i0] = w;
+ }
+
+ if (data[i6] < data[i3]) {
+ final double u = data[i6];
+ data[i6] = data[i3];
+ data[i3] = u;
+ }
+ if (data[i4] < data[i1]) {
+ final double v = data[i4];
+ data[i4] = data[i1];
+ data[i1] = v;
+ }
+ if (data[i2] < data[i0]) {
+ final double w = data[i2];
+ data[i2] = data[i0];
+ data[i0] = w;
+ }
+
+ if (data[i4] < data[i3]) {
+ final double u = data[i4];
+ data[i4] = data[i3];
+ data[i3] = u;
+ }
+ if (data[i5] < data[i2]) {
+ final double v = data[i5];
+ data[i5] = data[i2];
+ data[i2] = v;
+ }
+ if (data[i1] < data[i0]) {
+ final double w = data[i1];
+ data[i1] = data[i0];
+ data[i0] = w;
+ }
+
+ if (data[i6] < data[i4]) {
+ final double u = data[i6];
+ data[i6] = data[i4];
+ data[i4] = u;
+ }
+ if (data[i2] < data[i1]) {
+ final double v = data[i2];
+ data[i2] = data[i1];
+ data[i1] = v;
+ }
+
+ if (data[i5] < data[i4]) {
+ final double u = data[i5];
+ data[i5] = data[i4];
+ data[i4] = u;
+ }
+ if (data[i3] < data[i2]) {
+ final double v = data[i3];
+ data[i3] = data[i2];
+ data[i2] = v;
+ }
+
+ if (data[i6] < data[i5]) {
+ final double u = data[i6];
+ data[i6] = data[i5];
+ data[i5] = u;
+ }
+ if (data[i4] < data[i3]) {
+ final double v = data[i4];
+ data[i4] = data[i3];
+ data[i3] = v;
+ }
+ if (data[i2] < data[i1]) {
+ final double w = data[i2];
+ data[i2] = data[i1];
+ data[i1] = w;
+ }
+ }
+
+ /**
+ * Sorts the given indices in an array.
+ *
+ * Assumes all indices are valid and distinct.
+ *
+ * Data are arranged such that:
+ * If indices are duplicated elements will not be correctly ordered.
+ * However in this case data will contain the same values and may be partially ordered.
+ *
+ * @param data Data array.
+ * @param i0 Index.
+ * @param i1 Index.
+ * @param i2 Index.
+ * @param i3 Index.
+ * @param i4 Index.
+ * @param i5 Index.
+ * @param i6 Index.
+ * @param i7 Index.
+ */
+ static void sort8(double[] data, int i0, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
+ // Uses an optimal sorting network from Knuth's Art of Computer Programming.
+ // 19 comparisons.
+ // Order pairs:
+ //[(0,2),(1,3),(4,6),(5,7)]
+ //[(0,4),(1,5),(2,6),(3,7)]
+ //[(0,1),(2,3),(4,5),(6,7)]
+ //[(2,4),(3,5)]
+ //[(1,4),(3,6)]
+ //[(1,2),(3,4),(5,6)]
+ if (data[i7] < data[i5]) {
+ final double u = data[i7];
+ data[i7] = data[i5];
+ data[i5] = u;
+ }
+ if (data[i6] < data[i4]) {
+ final double v = data[i6];
+ data[i6] = data[i4];
+ data[i4] = v;
+ }
+ if (data[i3] < data[i1]) {
+ final double w = data[i3];
+ data[i3] = data[i1];
+ data[i1] = w;
+ }
+ if (data[i2] < data[i0]) {
+ final double x = data[i2];
+ data[i2] = data[i0];
+ data[i0] = x;
+ }
+
+ if (data[i7] < data[i3]) {
+ final double u = data[i7];
+ data[i7] = data[i3];
+ data[i3] = u;
+ }
+ if (data[i6] < data[i2]) {
+ final double v = data[i6];
+ data[i6] = data[i2];
+ data[i2] = v;
+ }
+ if (data[i5] < data[i1]) {
+ final double w = data[i5];
+ data[i5] = data[i1];
+ data[i1] = w;
+ }
+ if (data[i4] < data[i0]) {
+ final double x = data[i4];
+ data[i4] = data[i0];
+ data[i0] = x;
+ }
+
+ if (data[i7] < data[i6]) {
+ final double u = data[i7];
+ data[i7] = data[i6];
+ data[i6] = u;
+ }
+ if (data[i5] < data[i4]) {
+ final double v = data[i5];
+ data[i5] = data[i4];
+ data[i4] = v;
+ }
+ if (data[i3] < data[i2]) {
+ final double w = data[i3];
+ data[i3] = data[i2];
+ data[i2] = w;
+ }
+ if (data[i1] < data[i0]) {
+ final double x = data[i1];
+ data[i1] = data[i0];
+ data[i0] = x;
+ }
+
+ if (data[i5] < data[i3]) {
+ final double u = data[i5];
+ data[i5] = data[i3];
+ data[i3] = u;
+ }
+ if (data[i4] < data[i2]) {
+ final double v = data[i4];
+ data[i4] = data[i2];
+ data[i2] = v;
+ }
+
+ if (data[i6] < data[i3]) {
+ final double u = data[i6];
+ data[i6] = data[i3];
+ data[i3] = u;
+ }
+ if (data[i4] < data[i1]) {
+ final double v = data[i4];
+ data[i4] = data[i1];
+ data[i1] = v;
+ }
+
+ if (data[i6] < data[i5]) {
+ final double u = data[i6];
+ data[i6] = data[i5];
+ data[i5] = u;
+ }
+ if (data[i4] < data[i3]) {
+ final double v = data[i4];
+ data[i4] = data[i3];
+ data[i3] = v;
+ }
+ if (data[i2] < data[i1]) {
+ final double w = data[i2];
+ data[i2] = data[i1];
+ data[i1] = w;
+ }
+ }
+
+
+ /**
+ * Sorts the given indices in an array.
+ *
+ * Assumes all indices are valid and distinct.
+ *
+ * Data are arranged such that:
+ * If indices are duplicated elements will not be correctly ordered.
+ * However in this case data will contain the same values and may be partially ordered.
+ *
+ * @param data Data array.
+ * @param i0 Index.
+ * @param i1 Index.
+ * @param i2 Index.
+ * @param i3 Index.
+ * @param i4 Index.
+ * @param i5 Index.
+ * @param i6 Index.
+ * @param i7 Index.
+ * @param i8 Index.
+ * @param i9 Index.
+ * @param i10 Index.
+ */
+ static void sort11(double[] data, int i0, int i1, int i2, int i3, int i4, int i5, int i6, int i7,
+ int i8, int i9, int i10) {
+ // Uses an optimal sorting network from Knuth's Art of Computer Programming.
+ // 35 comparisons.
+ // Order pairs:
+ //[(0,9),(1,6),(2,4),(3,7),(5,8)]
+ //[(0,1),(3,5),(4,10),(6,9),(7,8)]
+ //[(1,3),(2,5),(4,7),(8,10)]
+ //[(0,4),(1,2),(3,7),(5,9),(6,8)]
+ //[(0,1),(2,6),(4,5),(7,8),(9,10)]
+ //[(2,4),(3,6),(5,7),(8,9)]
+ //[(1,2),(3,4),(5,6),(7,8)]
+ //[(2,3),(4,5),(6,7)]
+ if (data[i8] < data[i5]) {
+ final double u = data[i8];
+ data[i8] = data[i5];
+ data[i5] = u;
+ }
+ if (data[i7] < data[i3]) {
+ final double v = data[i7];
+ data[i7] = data[i3];
+ data[i3] = v;
+ }
+ if (data[i4] < data[i2]) {
+ final double w = data[i4];
+ data[i4] = data[i2];
+ data[i2] = w;
+ }
+ if (data[i6] < data[i1]) {
+ final double x = data[i6];
+ data[i6] = data[i1];
+ data[i1] = x;
+ }
+ if (data[i9] < data[i0]) {
+ final double y = data[i9];
+ data[i9] = data[i0];
+ data[i0] = y;
+ }
+
+ if (data[i8] < data[i7]) {
+ final double u = data[i8];
+ data[i8] = data[i7];
+ data[i7] = u;
+ }
+ if (data[i9] < data[i6]) {
+ final double v = data[i9];
+ data[i9] = data[i6];
+ data[i6] = v;
+ }
+ if (data[i10] < data[i4]) {
+ final double w = data[i10];
+ data[i10] = data[i4];
+ data[i4] = w;
+ }
+ if (data[i5] < data[i3]) {
+ final double x = data[i5];
+ data[i5] = data[i3];
+ data[i3] = x;
+ }
+ if (data[i1] < data[i0]) {
+ final double y = data[i1];
+ data[i1] = data[i0];
+ data[i0] = y;
+ }
+
+ if (data[i10] < data[i8]) {
+ final double u = data[i10];
+ data[i10] = data[i8];
+ data[i8] = u;
+ }
+ if (data[i7] < data[i4]) {
+ final double v = data[i7];
+ data[i7] = data[i4];
+ data[i4] = v;
+ }
+ if (data[i5] < data[i2]) {
+ final double w = data[i5];
+ data[i5] = data[i2];
+ data[i2] = w;
+ }
+ if (data[i3] < data[i1]) {
+ final double x = data[i3];
+ data[i3] = data[i1];
+ data[i1] = x;
+ }
+
+ if (data[i8] < data[i6]) {
+ final double u = data[i8];
+ data[i8] = data[i6];
+ data[i6] = u;
+ }
+ if (data[i9] < data[i5]) {
+ final double v = data[i9];
+ data[i9] = data[i5];
+ data[i5] = v;
+ }
+ if (data[i7] < data[i3]) {
+ final double w = data[i7];
+ data[i7] = data[i3];
+ data[i3] = w;
+ }
+ if (data[i2] < data[i1]) {
+ final double x = data[i2];
+ data[i2] = data[i1];
+ data[i1] = x;
+ }
+ if (data[i4] < data[i0]) {
+ final double y = data[i4];
+ data[i4] = data[i0];
+ data[i0] = y;
+ }
+
+ if (data[i10] < data[i9]) {
+ final double u = data[i10];
+ data[i10] = data[i9];
+ data[i9] = u;
+ }
+ if (data[i8] < data[i7]) {
+ final double v = data[i8];
+ data[i8] = data[i7];
+ data[i7] = v;
+ }
+ if (data[i5] < data[i4]) {
+ final double w = data[i5];
+ data[i5] = data[i4];
+ data[i4] = w;
+ }
+ if (data[i6] < data[i2]) {
+ final double x = data[i6];
+ data[i6] = data[i2];
+ data[i2] = x;
+ }
+ if (data[i1] < data[i0]) {
+ final double y = data[i1];
+ data[i1] = data[i0];
+ data[i0] = y;
+ }
+
+ if (data[i9] < data[i8]) {
+ final double u = data[i9];
+ data[i9] = data[i8];
+ data[i8] = u;
+ }
+ if (data[i7] < data[i5]) {
+ final double v = data[i7];
+ data[i7] = data[i5];
+ data[i5] = v;
+ }
+ if (data[i6] < data[i3]) {
+ final double w = data[i6];
+ data[i6] = data[i3];
+ data[i3] = w;
+ }
+ if (data[i4] < data[i2]) {
+ final double x = data[i4];
+ data[i4] = data[i2];
+ data[i2] = x;
+ }
+
+ if (data[i8] < data[i7]) {
+ final double u = data[i8];
+ data[i8] = data[i7];
+ data[i7] = u;
+ }
+ if (data[i6] < data[i5]) {
+ final double v = data[i6];
+ data[i6] = data[i5];
+ data[i5] = v;
+ }
+ if (data[i4] < data[i3]) {
+ final double w = data[i4];
+ data[i4] = data[i3];
+ data[i3] = w;
+ }
+ if (data[i2] < data[i1]) {
+ final double x = data[i2];
+ data[i2] = data[i1];
+ data[i1] = x;
+ }
+
+ if (data[i7] < data[i6]) {
+ final double u = data[i7];
+ data[i7] = data[i6];
+ data[i6] = u;
+ }
+ if (data[i5] < data[i4]) {
+ final double v = data[i5];
+ data[i5] = data[i4];
+ data[i4] = v;
+ }
+ if (data[i3] < data[i2]) {
+ final double w = data[i3];
+ data[i3] = data[i2];
+ data[i2] = w;
+ }
+ }
+
+ /**
+ * Sort the unique indices in-place to the start of the array. Duplicates are moved
+ * to the end of the array and set to negative. For convenience the maximum
+ * index is set into the final position in the array. If this is a duplicate it is
+ * set to negative using the twos complement representation:
+ *
+ * A small number of indices is sorted in place. A large number will use an
+ * IndexSet which is returned for reuse by the caller. The threshold for this
+ * switch is provided by the caller. An index set is used when
+ * {@code indices.length > countThreshold} and there is more than 1 index.
+ *
+ * This method assumes the {@code data} contains only positive integers.
+ *
+ * @param countThreshold Threshold to use an IndexSet.
+ * @param data Indices.
+ * @param n Number of indices.
+ * @return the index set (or null if not used)
+ */
+ static IndexSet sortUnique(int countThreshold, int[] data, int n) {
+ if (n <= 1) {
+ return null;
+ }
+ if (n > countThreshold) {
+ return sortUnique(data, n);
+ }
+ int unique = 1;
+ int j;
+ // Do an insertion sort but only compare the current set of unique values.
+ for (int i = 0; ++i < n;) {
+ final int v = data[i];
+ // Erase data
+ data[i] = -1;
+ j = unique;
+ if (v > data[j - 1]) {
+ // Insert at end
+ data[j] = v;
+ unique++;
+ } else if (v < data[j - 1]) {
+ // Find insertion point in the unique indices
+ do {
+ --j;
+ } while (j >= 0 && v < data[j]);
+ // Either insert at the start, or insert non-duplicate
+ if (j < 0 || v != data[j]) {
+ // Update j so it is the insertion position
+ j++;
+ // Process the delayed moves
+ // Move from [j, unique) to [j+1, unique+1)
+ // System.arraycopy(data, j, data, j + 1, unique - j)
+ for (int k = unique; k-- > j;) {
+ data[k + 1] = data[k];
+ }
+ data[j] = v;
+ unique++;
+ }
+ }
+ }
+ // Set the max value at the end, bit-flipped
+ if (unique < n) {
+ data[n - 1] = ~data[unique - 1];
+ }
+ return null;
+ }
+
+ /**
+ * Sort the unique indices in-place to the start of the array. Duplicates are moved
+ * to the end of the array and set to negative. For convenience the maximum
+ * index is set into the final position in the array. If this is a duplicate it is
+ * set to negative using the twos complement representation:
+ *
+ * Uses an IndexSet which is returned to the caller. Assumes the indices
+ * are non-zero in length.
+ *
+ * @param data Indices.
+ * @param n Number of indices.
+ * @return the index set
+ */
+ private static IndexSet sortUnique(int[] data, int n) {
+ final IndexSet set = IndexSet.of(data, n);
+ // Iterate
+ final int[] unique = {0};
+ set.forEach(i -> data[unique[0]++] = i);
+ if (unique[0] < n) {
+ for (int i = unique[0]; i < n; i++) {
+ data[i] = -1;
+ }
+ // Set the max value at the end, bit flipped
+ data[n - 1] = ~data[unique[0] - 1];
+ }
+ return set;
+ }
+
+ /**
+ * Sort the unique indices in-place to the start of the array. The number of
+ * indices is returned.
+ *
+ * This method assumes the {@code data} contains only positive integers.
+ *
+ * @param data Indices.
+ * @param n Number of indices.
+ * @return the number of indices
+ */
+ static int sortIndices(int[] data, int n) {
+ // Simple cases
+ if (n < 3) {
+ if (n == 2) {
+ final int i0 = data[0];
+ final int i1 = data[1];
+ if (i0 > i1) {
+ data[0] = i1;
+ data[1] = i0;
+ } else if (i0 == i1) {
+ return 1;
+ }
+ }
+ // n=0,1,2 unique values
+ return n;
+ }
+
+ // Strategy: Must be fast on already ascending data.
+ // Note: The recommended way to generate a lot of partition indices from
+ // many quantiles for interpolation is to generate in sequence.
+
+ // n <= small:
+ // Modified insertion sort (naturally finds ascending data)
+ // n > small:
+ // Look for ascending sequence and compact
+ // else:
+ // Remove duplicates using an order(1) data structure and sort
+
+ if (n <= UNIQUE_INSERTION_SORT) {
+ return sortIndicesInsertionSort(data, n);
+ }
+
+ if (isAscending(data, n)) {
+ return compressDuplicates(data, n);
+ }
+
+ // At least 20 indices that are partially unordered.
+ // Find min/max
+ int min = data[0];
+ int max = min;
+ for (int i = 0; ++i < n;) {
+ min = Math.min(min, data[i]);
+ max = Math.max(max, data[i]);
+ }
+
+ // Benchmarking shows the IndexSet is very fast when the long[] efficiently
+ // resides in cache memory. If the indices are very well separated the
+ // distribution is sparse and it is faster to use a HashIndexSet despite
+ // having to perform a sort after making keys unique.
+ // Both structures have Order(1) detection of unique keys (the HashIndexSet
+ // is configured with a load factor that should see low collision rates).
+ // IndexSet sort Order(n) (data is stored sorted and must be read)
+ // HashIndexSet sort Order(n log n) (unique data is sorted separately)
+
+ // For now base the choice on memory consumption alone which is a fair
+ // approximation when n < 1000.
+ // Above 1000 indices we assume that sorting the indices is a small cost
+ // compared to sorting/partitioning the data that requires so many indices.
+ // If the input data is small upstream code could detect this, e.g.
+ // indices.length >> data.length, and choose to sort the data rather than
+ // partitioning so many indices.
+
+ // If the HashIndexSet uses < 8x memory of IndexSet then prefer that.
+ // This detects obvious cases of sparse keys where the IndexSet is
+ // outperformed by the HashIndexSet. Otherwise we can assume the
+ // memory consumption of the IndexSet is small compared to the data to be
+ // partitioned at these target indices (max 1/64 for double[] data); any
+ // time taken here for sorting indices should be less than partitioning time.
+
+ // This requires more analysis of performance crossover.
+ // Note: Expected behaviour under extreme use-cases should be documented.
+
+ if (HashIndexSet.memoryFootprint(n) < (IndexSet.memoryFootprint(min, max) >>> 3)) {
+ return sortIndicesHashIndexSet(data, n);
+ }
+
+ // Repeat code from IndexSet as we have the min/max
+ final IndexSet set = IndexSet.ofRange(min, max);
+ for (int i = -1; ++i < n;) {
+ set.set(data[i]);
+ }
+ return set.toArray(data);
+ }
+
+ /**
+ * Sort the unique indices in-place to the start of the array. The number of
+ * indices is returned.
+ *
+ * This method assumes the {@code data} contains only positive integers;
+ * and that {@code n} is small relative to the range of indices {@code [min, max]} such
+ * that storing all indices in an {@link IndexSet} is not memory efficient.
+ *
+ * @param data Indices.
+ * @param n Number of indices.
+ * @return the number of indices
+ */
+ static int sortIndices2(int[] data, int n) {
+ // Simple cases
+ if (n < 3) {
+ if (n == 2) {
+ final int i0 = data[0];
+ final int i1 = data[1];
+ if (i0 > i1) {
+ data[0] = i1;
+ data[1] = i0;
+ } else if (i0 == i1) {
+ return 1;
+ }
+ }
+ // n=0,1,2 unique values
+ return n;
+ }
+
+ // Strategy: Must be fast on already ascending data.
+ // Note: The recommended way to generate a lot of partition indices from
+ // many quantiles for interpolation is to generate in sequence.
+
+ // n <= small:
+ // Modified insertion sort (naturally finds ascending data)
+ // n > small:
+ // Look for ascending sequence and compact
+ // else:
+ // Remove duplicates using an order(1) data structure and sort
+
+ if (n <= UNIQUE_INSERTION_SORT) {
+ return sortIndicesInsertionSort(data, n);
+ }
+
+ if (isAscending(data, n)) {
+ return compressDuplicates(data, n);
+ }
+
+ return sortIndicesHashIndexSet(data, n);
+ }
+
+ /**
+ * Test the data is in ascending order: {@code data[i] <= data[i+1]} for all {@code i}.
+ * Data is assumed to be at least length 1.
+ *
+ * @param data Data.
+ * @param n Length of data.
+ * @return true if ascending
+ */
+ private static boolean isAscending(int[] data, int n) {
+ for (int i = 0; ++i < n;) {
+ if (data[i] < data[i - 1]) {
+ // descending
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Test the data is in ascending order: {@code data[i] <= data[i+1]} for all {@code i}.
+ * Data is assumed to be at least length 1.
+ *
+ * @param data Data.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @return true if ascending
+ */
+ static boolean isAscending(double[] data, int left, int right) {
+ for (int i = left; ++i <= right;) {
+ if (data[i] < data[i - 1]) {
+ // descending
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // The following methods all perform the same function and are present
+ // for performance testing.
+
+ /**
+ * Sort the unique indices in-place to the start of the array. The number of
+ * indices is returned.
+ *
+ * Uses an insertion sort modified to ignore duplicates.
+ *
+ * Warning: Requires {@code n > 0}.
+ *
+ * @param data Indices.
+ * @param n Number of indices.
+ * @return the number of indices
+ */
+ static int sortIndicesInsertionSort(int[] data, int n) {
+ int unique = 1;
+ // Do an insertion sort but only compare the current set of unique values.
+ for (int i = 0; ++i < n;) {
+ final int v = data[i];
+ int j = unique - 1;
+ if (v > data[j]) {
+ // Insert at end
+ data[unique] = v;
+ unique++;
+ } else if (v < data[j]) {
+ // Find insertion point in the unique indices
+ do {
+ --j;
+ } while (j >= 0 && v < data[j]);
+ // Insertion point = j + 1
+ // Insert if at start or non-duplicate
+ if (j < 0 || v != data[j]) {
+ // Move (j, unique) to (j+1, unique+1)
+ for (int k = unique; --k > j;) {
+ data[k + 1] = data[k];
+ }
+ data[j + 1] = v;
+ unique++;
+ }
+ }
+ }
+ return unique;
+ }
+
+ /**
+ * Sort the unique indices in-place to the start of the array. The number of
+ * indices is returned.
+ *
+ * Uses a binary search to find the insert point.
+ *
+ * Warning: Requires {@code n > 1}.
+ *
+ * @param data Indices.
+ * @param n Number of indices.
+ * @return the number of indices
+ */
+ static int sortIndicesBinarySearch(int[] data, int n) {
+ // Sort first 2
+ if (data[1] < data[0]) {
+ final int v = data[0];
+ data[0] = data[1];
+ data[1] = v;
+ }
+ int unique = data[0] != data[1] ? 2 : 1;
+ // Insert the remaining indices if unique
+ OUTER:
+ for (int i = 1; ++i < n;) {
+ // Binary search with fast exit on match
+ int l = 0;
+ int r = unique - 1;
+ final int k = data[i];
+ while (l <= r) {
+ // Middle value
+ final int m = (l + r) >>> 1;
+ final int v = data[m];
+ // Test:
+ // l------m------r
+ // v k update left
+ // k v update right
+ if (v < k) {
+ l = m + 1;
+ } else if (v > k) {
+ r = m - 1;
+ } else {
+ // Equal
+ continue OUTER;
+ }
+ }
+ // key not found: insert at l
+ System.arraycopy(data, l, data, l + 1, unique - l);
+ data[l] = k;
+ unique++;
+ }
+ return unique;
+ }
+
+ /**
+ * Sort the unique indices in-place to the start of the array. The number of
+ * indices is returned.
+ *
+ * Uses a heap sort modified to ignore duplicates.
+ *
+ * Warning: Requires {@code n > 0}.
+ *
+ * @param data Indices.
+ * @param n Number of indices.
+ * @return the number of indices
+ */
+ static int sortIndicesHeapSort(int[] data, int n) {
+ // Build the min heap using Floyd's heap-construction algorithm
+ // Start at parent of the last element in the heap (n-1)
+ final int offset = n - 1;
+ for (int start = offset >> 1; start >= 0; start--) {
+ minHeapSiftDown(data, offset, start, n);
+ }
+
+ // The min heap has been constructed in-place so a[n-1] is the min.
+ // To sort we have to move elements from the top of the
+ // heap to the position immediately before the end of the heap
+ // (which is below right), reducing the heap size each step:
+ // root
+ // |--------------|k|-min-heap-|r|
+ // | <-swap-> |
+
+ // Move top of heap to the sorted end and move the end
+ // to the top.
+ int previous = data[offset];
+ data[offset] = data[0];
+ data[0] = previous;
+ int s = n - 1;
+ minHeapSiftDown(data, offset, 0, s);
+
+ // Min heap is now 1 smaller
+ // Proceed with the remaining elements but do not write them
+ // to the sorted data unless different from the previous value.
+ int last = 0;
+ for (;;) {
+ s--;
+ // Move top of heap to the sorted end
+ final int v = data[offset];
+ data[offset] = data[offset - s];
+ if (previous != v) {
+ data[++last] = v;
+ previous = v;
+ }
+ if (s == 1) {
+ // end of heap
+ break;
+ }
+ minHeapSiftDown(data, offset, 0, s);
+ }
+ // Stopped sifting when the heap was size 1.
+ // Move the last (max) value to the sorted data.
+ if (previous != data[offset]) {
+ data[++last] = data[offset];
+ }
+ return last + 1;
+ }
+
+ /**
+ * Sift the top element down the min heap.
+ *
+ * Note this creates the min heap in descending sequence so the
+ * heap is positioned below the root.
+ *
+ * @param a Heap data.
+ * @param offset Offset of the heap in the data.
+ * @param root Root of the heap.
+ * @param n Size of the heap.
+ */
+ private static void minHeapSiftDown(int[] a, int offset, int root, int n) {
+ // For node i:
+ // left child: 2i + 1
+ // right child: 2i + 2
+ // parent: floor((i-1) / 2)
+
+ // Value to sift
+ int p = root;
+ final int v = a[offset - p];
+ // Left child of root: p * 2 + 1
+ int c = (p << 1) + 1;
+ while (c < n) {
+ // Left child value
+ int cv = a[offset - c];
+ // Use the right child if less
+ if (c + 1 < n && cv > a[offset - c - 1]) {
+ cv = a[offset - c - 1];
+ c++;
+ }
+ // Min heap requires parent <= child
+ if (v <= cv) {
+ // Less than smallest child - done
+ break;
+ }
+ // Swap and descend
+ a[offset - p] = cv;
+ p = c;
+ c = (p << 1) + 1;
+ }
+ a[offset - p] = v;
+ }
+
+ /**
+ * Sort the unique indices in-place to the start of the array. The number of
+ * indices is returned.
+ *
+ * Uses a full sort and a second-pass to ignore duplicates.
+ *
+ * Warning: Requires {@code n > 0}.
+ *
+ * @param data Indices.
+ * @param n Number of indices.
+ * @return the number of indices
+ */
+ static int sortIndicesSort(int[] data, int n) {
+ java.util.Arrays.sort(data, 0, n);
+ return compressDuplicates(data, n);
+ }
+
+ /**
+ * Compress duplicates in the ascending data.
+ *
+ * Warning: Requires {@code n > 0}.
+ *
+ * @param data Indices.
+ * @param n Number of indices.
+ * @return the number of unique indices
+ */
+ private static int compressDuplicates(int[] data, int n) {
+ // Compress to remove duplicates
+ int last = 0;
+ int top = data[0];
+ for (int i = 0; ++i < n;) {
+ final int v = data[i];
+ if (v == top) {
+ continue;
+ }
+ top = v;
+ data[++last] = v;
+ }
+ return last + 1;
+ }
+
+ /**
+ * Sort the unique indices in-place to the start of the array. The number of
+ * indices is returned.
+ *
+ * Uses an {@link IndexSet} to ignore duplicates. The sorted array is
+ * extracted from the {@link IndexSet} storage in order.
+ *
+ * Warning: Requires {@code n > 0}.
+ *
+ * @param data Indices.
+ * @param n Number of indices.
+ * @return the number of indices
+ * @see IndexSet#toArray(int[])
+ */
+ static int sortIndicesIndexSet(int[] data, int n) {
+ // Delegate to IndexSet
+ // Storage (bytes) = 8 * ceil((max - min) / 64), irrespective of n.
+ // This can be use a lot of memory when the indices are spread out.
+ return IndexSet.of(data, n).toArray(data);
+ }
+
+ /**
+ * Sort the unique indices in-place to the start of the array. The number of
+ * indices is returned.
+ *
+ * Uses an {@link IndexSet} to ignore duplicates. The sorted array is
+ * extracted from the {@link IndexSet} storage in order.
+ *
+ * Warning: Requires {@code n > 0}.
+ *
+ * @param data Indices.
+ * @param n Number of indices.
+ * @return the number of indices
+ * @see IndexSet#toArray2(int[])
+ */
+ static int sortIndicesIndexSet2(int[] data, int n) {
+ // Delegate to IndexSet
+ // Storage (bytes) = 8 * ceil((max - min) / 64), irrespective of n.
+ // This can be use a lot of memory when the indices are spread out.
+ return IndexSet.of(data, n).toArray2(data);
+ }
+
+ /**
+ * Sort the unique indices in-place to the start of the array. The number of
+ * indices is returned.
+ *
+ * Uses a {@link HashIndexSet} to ignore duplicates and then performs
+ * a full sort of the unique values.
+ *
+ * Warning: Requires {@code n > 0}.
+ *
+ * @param data Indices.
+ * @param n Number of indices.
+ * @return the number of indices
+ */
+ static int sortIndicesHashIndexSet(int[] data, int n) {
+ // Compress to remove duplicates.
+ // Duplicates are checked using a HashIndexSet.
+ // Storage (bytes) = 4 * next-power-of-2(n*2) => 2-4 times n
+ final HashIndexSet set = new HashIndexSet(n);
+ int i = 0;
+ int last = 0;
+ set.add(data[0]);
+ while (++i < n) {
+ final int v = data[i];
+ if (set.add(v)) {
+ data[++last] = v;
+ }
+ }
+ // Sort unique data.
+ // This can exploit the input already being sorted.
+ Arrays.sort(data, 0, ++last);
+ return last;
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/SplittingInterval.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/SplittingInterval.java
new file mode 100644
index 000000000..1504f23cc
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/SplittingInterval.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * An interval that contains indices used for partitioning an array into multiple regions.
+ *
+ * The interval provides the following functionality:
+ *
+ * Note that the interval provides the supported bounds. If a split invalidates an interval
+ * the bounds are undefined and the interval is marked as {@link #empty()}.
+ *
+ * Implementations may assume indices are positive.
+ *
+ * @since 1.2
+ */
+interface SplittingInterval {
+ /**
+ * The start (inclusive) of the interval.
+ *
+ * @return start of the interval
+ */
+ int left();
+
+ /**
+ * The end (inclusive) of the interval.
+ *
+ * @return end of the interval
+ */
+ int right();
+
+ /**
+ * Signal this interval is empty. The left and right bounds are undefined. This results
+ * from a split where there is no right side.
+ *
+ * @return {@code true} if empty
+ */
+ boolean empty();
+
+ /**
+ * Split the interval using two splitting indices. Returns the left interval that occurs
+ * before the specified split index {@code ka}, and updates the current interval left bound
+ * to after the specified split index {@code kb}.
+ *
+ * If {@code ka <= left} the returned left interval is {@code null}.
+ *
+ * If {@code kb >= right} the current interval is invalidated and marked as empty.
+ *
+ * @param ka Split index.
+ * @param kb Split index.
+ * @return the left interval
+ * @see #empty()
+ */
+ SplittingInterval split(int ka, int kb);
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/UpdatingInterval.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/UpdatingInterval.java
new file mode 100644
index 000000000..7f2e6406f
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/UpdatingInterval.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * An interval that contains indices used for partitioning an array into multiple regions.
+ *
+ * The interval provides the following functionality:
+ *
+ * Note that the interval provides the supported bounds. If an index {@code k} is
+ * outside the supported bounds the result is undefined.
+ *
+ * Implementations may assume indices are positive.
+ *
+ * @since 1.2
+ */
+interface UpdatingInterval {
+ /**
+ * The start (inclusive) of the interval.
+ *
+ * @return start of the interval
+ */
+ int left();
+
+ /**
+ * The end (inclusive) of the interval.
+ *
+ * @return end of the interval
+ */
+ int right();
+
+ /**
+ * Update the interval so {@code k <= left}.
+ *
+ * Note: Requires {@code left < k <= right}, i.e. there exists a valid interval
+ * above the index.
+ *
+ * Note: Requires {@code left <= k < right}, i.e. there exists a valid interval
+ * below the index.
+ *
+ * Note: Requires {@code left < ka <= kb < right}, i.e. there exists a valid interval
+ * above and below the split indices.
+ *
+ * If {@code ka <= left} or {@code kb >= right} the result is undefined.
+ *
+ * @param ka Split index.
+ * @param kb Split index.
+ * @return the left interval
+ */
+ UpdatingInterval splitLeft(int ka, int kb);
+
+ /**
+ * Split the interval using two splitting indices. Returns the right interval that occurs
+ * after the specified split index {@code kb}, and updates the current interval right bound
+ * to before the specified split index {@code ka}.
+ *
+ * Note: Requires {@code left < ka <= kb < right}, i.e. there exists a valid interval
+ * above and below the split indices.
+ *
+ * If {@code ka <= left} or {@code kb >= right} the result is undefined.
+ *
+ * @param ka Split index.
+ * @param kb Split index.
+ * @return the right interval
+ */
+ UpdatingInterval splitRight(int ka, int kb);
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/package-info.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/package-info.java
new file mode 100644
index 000000000..1712ef336
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Benchmarks for the {@code org.apache.commons.numbers.arrays} components.
+ *
+ * @since 1.2
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
diff --git a/commons-numbers-examples/examples-jmh/src/test/java/org/apache/commons/numbers/examples/jmh/arrays/CompressedIndexSetTest.java b/commons-numbers-examples/examples-jmh/src/test/java/org/apache/commons/numbers/examples/jmh/arrays/CompressedIndexSetTest.java
new file mode 100644
index 000000000..1ff5098ae
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/test/java/org/apache/commons/numbers/examples/jmh/arrays/CompressedIndexSetTest.java
@@ -0,0 +1,365 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.stream.Stream;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link CompressedIndexSet} and {@link CompressedIndexSet2}.
+ */
+class CompressedIndexSetTest {
+ /** Compression levels to test. */
+ private static final int[] COMPRESSION = {1, 2, 3};
+
+ @Test
+ void testInvalidRangeThrows() {
+ // Valid compression
+ final int c = 1;
+ Assertions.assertThrows(IllegalArgumentException.class, () -> CompressedIndexSet.ofRange(c, -1, 3));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> CompressedIndexSet.ofRange(c, 0, -1));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> CompressedIndexSet.ofRange(c, 456, 123));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> CompressedIndexSet.of(c, new int[0]));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> CompressedIndexSet.of(c, new int[] {-1}));
+ // Fixed compression
+ Assertions.assertThrows(IllegalArgumentException.class, () -> CompressedIndexSet2.ofRange(-1, 3));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> CompressedIndexSet2.ofRange(0, -1));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> CompressedIndexSet2.ofRange(456, 123));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> CompressedIndexSet2.of(new int[0]));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> CompressedIndexSet2.of(new int[] {-1}));
+ }
+
+ @Test
+ void testInvalidCompressionThrows() {
+ for (final int c : new int[] {0, -1, 32}) {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> CompressedIndexSet.ofRange(c, 1, 3));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> CompressedIndexSet.of(c, new int[] {1}));
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testGetSet(int[] indices, int n) {
+ for (final int c : COMPRESSION) {
+ final CompressedIndexSet set = createCompressedIndexSet(c, indices);
+ final BitSet ref = new BitSet(n);
+ final int left = set.left();
+ final int range = 1 << c;
+ for (final int i : indices) {
+ // The contains value is a probability due to the compression.
+ // It will be true if any of the indices in the compressed range are set.
+ final int mapped = getCompressedIndexLow(c, left, i);
+ boolean contains = ref.get(mapped);
+ for (int j = 1; !contains && j < range; j++) {
+ contains = ref.get(mapped + j);
+ }
+ Assertions.assertEquals(contains, set.get(i), () -> String.valueOf(i));
+ set.set(i);
+ ref.set(i);
+ Assertions.assertTrue(set.get(i));
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testGetSet"})
+ void testPreviousIndexOrLeftMinus1(int[] indices, int n) {
+ for (final int c : COMPRESSION) {
+ final CompressedIndexSet set = createCompressedIndexSet(c, indices);
+ final BitSet ref = new BitSet(n);
+ final int left = set.left();
+ final int right = set.right();
+ Arrays.sort(indices);
+ final int highBit = indices[indices.length - 1];
+ Assertions.assertEquals(set.left() - 1, set.previousIndexOrLeftMinus1(0));
+ Assertions.assertEquals(set.left() - 1, set.previousIndexOrLeftMinus1(highBit));
+ Assertions.assertEquals(set.left() - 1, set.previousIndexOrLeftMinus1(highBit * 2));
+ for (final int i : indices) {
+ final int lo = getCompressedIndexLow(c, left, i);
+ final int hi = getCompressedIndexHigh(c, left, right, i);
+ boolean contains = ref.get(lo);
+ for (int j = lo + 1; !contains && j <= hi; j++) {
+ contains = ref.get(j);
+ }
+ if (contains) {
+ for (int j = lo; j <= hi; j++) {
+ Assertions.assertEquals(j, set.previousIndexOrLeftMinus1(j), () -> "contains: " + i);
+ }
+ } else {
+ int prev = ref.previousSetBit(lo);
+ if (prev < 0) {
+ prev = left - 1;
+ } else {
+ prev = getCompressedIndexHigh(c, left, right, prev);
+ }
+ Assertions.assertEquals(prev, set.previousIndexOrLeftMinus1(i), () -> "previous upper: " + i);
+ }
+ set.set(i);
+ ref.set(i);
+ // Re-check within
+ for (int j = lo; j <= hi; j++) {
+ Assertions.assertEquals(j, set.previousIndexOrLeftMinus1(j), () -> "within: " + i);
+ }
+ // Check after
+ Assertions.assertEquals(hi, set.previousIndexOrLeftMinus1(hi + 1), () -> "after: " + i);
+ Assertions.assertEquals(hi, set.previousIndexOrLeftMinus1(hi + 42), () -> "after: " + i);
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testGetSet"})
+ void testNextIndexOrRightPlus1(int[] indices, int n) {
+ for (final int c : COMPRESSION) {
+ final CompressedIndexSet set = createCompressedIndexSet(c, indices);
+ final BitSet ref = new BitSet(n);
+ final int left = set.left();
+ final int right = set.right();
+ Arrays.sort(indices);
+ final int highBit = indices[indices.length - 1];
+ Assertions.assertEquals(set.right() + 1, set.nextIndexOrRightPlus1(0));
+ Assertions.assertEquals(set.right() + 1, set.nextIndexOrRightPlus1(highBit));
+ Assertions.assertEquals(set.right() + 1, set.nextIndexOrRightPlus1(highBit * 2));
+ // Process in descending order
+ for (int i = -1, j = indices.length; ++i < --j;) {
+ final int k = indices[i];
+ indices[i] = indices[j];
+ indices[j] = k;
+ }
+ for (final int i : indices) {
+ final int lo = getCompressedIndexLow(c, left, i);
+ final int hi = getCompressedIndexHigh(c, left, right, i);
+ boolean contains = ref.get(lo);
+ for (int j = lo + 1; !contains && j <= hi; j++) {
+ contains = ref.get(j);
+ }
+ if (contains) {
+ for (int j = lo; j <= hi; j++) {
+ Assertions.assertEquals(j, set.nextIndexOrRightPlus1(j), () -> "contains: " + i);
+ }
+ } else {
+ int next = ref.nextSetBit(lo);
+ if (next < 0) {
+ next = right + 1;
+ } else {
+ next = getCompressedIndexLow(c, left, next);
+ }
+ Assertions.assertEquals(next, set.nextIndexOrRightPlus1(i), () -> "next upper: " + i);
+ }
+ set.set(i);
+ ref.set(i);
+ // Re-check within
+ for (int j = lo; j <= hi; j++) {
+ Assertions.assertEquals(j, set.nextIndexOrRightPlus1(j), () -> "within: " + i);
+ }
+ // Check before
+ Assertions.assertEquals(lo, set.nextIndexOrRightPlus1(lo - 1), () -> "before: " + i);
+ Assertions.assertEquals(lo, set.nextIndexOrRightPlus1(lo - 42), () -> "before: " + i);
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testGetSet"})
+ void testNextPreviousIndex(int[] indices, int ignored) {
+ for (final int c : COMPRESSION) {
+ final CompressedIndexSet set = CompressedIndexSet.of(c, indices);
+ final int left = set.left();
+ final int right = set.right();
+ Assertions.assertThrows(IndexOutOfBoundsException.class, () -> set.previousIndex(left - 1));
+ Assertions.assertThrows(IndexOutOfBoundsException.class, () -> set.previousIndex(right + Long.SIZE << c));
+ Assertions.assertThrows(IndexOutOfBoundsException.class, () -> set.nextIndex(left - 1));
+ Assertions.assertThrows(IndexOutOfBoundsException.class, () -> set.nextIndex(right + Long.SIZE << c));
+ for (final int i : indices) {
+ // Test against validated method
+ final int lo = getCompressedIndexLow(c, left, i);
+ final int hi = getCompressedIndexHigh(c, left, right, i);
+ // Search with left <= k <= right
+ Assertions.assertTrue(lo >= left && hi <= right);
+ for (int j = lo; j <= hi; j++) {
+ Assertions.assertEquals(set.previousIndexOrLeftMinus1(j), set.previousIndex(j));
+ Assertions.assertEquals(set.nextIndexOrRightPlus1(j), set.nextIndex(j));
+ }
+ if (lo > left) {
+ Assertions.assertEquals(set.previousIndexOrLeftMinus1(lo - 1), set.previousIndex(lo - 1));
+ }
+ if (hi < right) {
+ Assertions.assertEquals(set.nextIndexOrRightPlus1(hi + 1), set.nextIndex(hi + 1));
+ }
+ }
+ }
+ }
+
+ static Stream This method returns l, unless clipped by the upper bound of the supported
+ * indices (right; in example above clipping would return k).
+ *
+ * @param c Compression.
+ * @param left Lower bound of the set of compressed indices.
+ * @param right Upper bound of the set of compressed indices.
+ * @param i Index.
+ * @return the upper bound of the index range
+ */
+ private static int getCompressedIndexHigh(int c, int left, int right, int i) {
+ return Math.min(right, (((i - left) >>> c) << c) + left + (1 << c) - 1);
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/test/java/org/apache/commons/numbers/examples/jmh/arrays/DoubleDataTransformersTest.java b/commons-numbers-examples/examples-jmh/src/test/java/org/apache/commons/numbers/examples/jmh/arrays/DoubleDataTransformersTest.java
new file mode 100644
index 000000000..182b24071
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/test/java/org/apache/commons/numbers/examples/jmh/arrays/DoubleDataTransformersTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link DoubleDataTransformers}.
+ */
+class DoubleDataTransformersTest {
+ @ParameterizedTest
+ @MethodSource(value = {"nanData"})
+ void testNaNErrorWithNaN(double[] a) {
+ final DoubleDataTransformer t1 = DoubleDataTransformers.createFactory(NaNPolicy.ERROR, false).get();
+ Assertions.assertThrows(IllegalArgumentException.class, () -> t1.preProcess(a));
+ final DoubleDataTransformer t2 = DoubleDataTransformers.createFactory(NaNPolicy.ERROR, true).get();
+ Assertions.assertThrows(IllegalArgumentException.class, () -> t2.preProcess(a));
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"nonNanData"})
+ void testNaNError(double[] a) {
+ assertSortTransformer(a, DoubleDataTransformers.createFactory(NaNPolicy.ERROR, false).get(), true, false);
+ assertSortTransformer(a, DoubleDataTransformers.createFactory(NaNPolicy.ERROR, true).get(), true, true);
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"nanData", "nonNanData"})
+ void testNaNInclude(double[] a) {
+ assertSortTransformer(a, DoubleDataTransformers.createFactory(NaNPolicy.INCLUDE, false).get(), true, false);
+ assertSortTransformer(a, DoubleDataTransformers.createFactory(NaNPolicy.INCLUDE, true).get(), true, true);
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"nanData", "nonNanData"})
+ void testNaNExclude(double[] a) {
+ assertSortTransformer(a, DoubleDataTransformers.createFactory(NaNPolicy.EXCLUDE, false).get(), false, false);
+ assertSortTransformer(a, DoubleDataTransformers.createFactory(NaNPolicy.EXCLUDE, true).get(), false, true);
+ }
+
+ /**
+ * Assert the transformer allows partitioning the data as if sorting using
+ * {@link Arrays#sort(double[])}. NaN should be moved to the end; signed zeros
+ * should be correctly ordered.
+ *
+ * @param a Data.
+ * @param t Transformer.
+ * @param includeNaN True if the size should include NaN
+ * @param copy True if the pre-processed data should be a copy
+ */
+ private static void assertSortTransformer(double[] a, DoubleDataTransformer t,
+ boolean includeNaN, boolean copy) {
+ final double[] original = a.clone();
+ final double[] b = t.preProcess(a);
+ if (copy) {
+ Assertions.assertNotSame(a, b);
+ } else {
+ Assertions.assertSame(a, b);
+ }
+ // Count NaN
+ final int nanCount = (int) Arrays.stream(a).filter(Double::isNaN).count();
+ Assertions.assertEquals(a.length - nanCount, t.length(), "Length to process");
+ Assertions.assertEquals(a.length - (includeNaN ? 0 : nanCount), t.size(), "Size of data");
+ // Partition / sort data up to the specified length
+ Arrays.sort(b, 0, t.length());
+ // Full sort of the original
+ Arrays.sort(original);
+ // Correct data given partition indices
+ if (b.length > 0) {
+ // Use potentially invalid partition index
+ t.postProcess(b, new int[] {b.length - 1}, 1);
+ }
+ Assertions.assertArrayEquals(original, b);
+ }
+
+ static Stream Supported compressed indices. Each index is compressed by a power of 2, then
+ * decompressed to create a range of indices {@code [from, to)}. See
+ * {@link #createBitSet(int[], int)} for details of the indices that must be iterated
+ * over.
+ *
+ * @param constructor Iterator constructor.
+ * @param indices Indices.
+ * @param separation Minimum separation between uncompressed indices.
+ * @param compression Compression level (for compressed indices).
+ */
+ private static void assertIterator(BiFunction Compressed indices are created using {@code c = (i - min) >>> compression}.
+ * This is then decompressed {@code from = (c << compression) + min}. The BitSet
+ * has all bits set in {@code [from, from + (1 << compression))}.
+ *
+ * @param indices Indices.
+ * @param compression Compression level.
+ * @return the set
+ */
+ private static BitSet createBitSet(int[] indices, int compression) {
+ final int max = indices[indices.length - 1] + 1 + (1 << compression);
+ final BitSet set = new BitSet(max);
+ if (compression == 0) {
+ Arrays.stream(indices).forEach(set::set);
+ } else {
+ final int min = indices[0];
+ final int width = 1 << compression;
+ Arrays.stream(indices).forEach(i -> {
+ i = (((i - min) >>> compression) << compression) + min;
+ set.set(i, i + width);
+ });
+ }
+ return set;
+ }
+
+ /**
+ * Output the iterator intervals for the indices.
+ */
+ @ParameterizedTest
+ @MethodSource
+ @Disabled("This is not a test")
+ void testIntervals(int compression, int[] indices) {
+ IndexIterator iterator;
+ if (compression == 0) {
+ final int unique = Sorting.sortIndices(indices, indices.length);
+ iterator = KeyIndexIterator.of(indices, unique);
+ } else {
+ iterator = CompressedIndexSet.iterator(compression, indices, indices.length);
+ }
+ TestUtils.printf("Compression %d%n", compression);
+ do {
+ final int l = iterator.left();
+ final int r = iterator.right();
+ TestUtils.printf("%d %d : %d%n", l, r, r - l + 1);
+ } while (iterator.next());
+ }
+
+ static Stream This method allows variable length indices using a count of the indices to
+ * process.
+ *
+ * @param a Values.
+ * @param k Indices.
+ * @param n Count of indices.
+ */
+ void partition(double[] a, int[] k, int n);
+ }
+
+ /**
+ * Partition function. Used to test different implementations.
+ */
+ private interface DoublePartitionFunction2 {
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * This test progressively adds more indices to the cache. It then verifies the
+ * range is correctly bracketed and any internal pivots can be traversed as sorted
+ * (pivot) and unsorted (non-pivot) regions.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param indices Batches of indices to add.
+ * @param cache Pivot cache.
+ */
+ private static void assertSingleRange(int left, int right, int[][] indices, PivotCache cache) {
+ final BitSet ref = new BitSet();
+ Assertions.assertEquals(left, cache.left());
+ Assertions.assertEquals(right, cache.right());
+ Assertions.assertEquals(ref.previousSetBit(left), cache.previousPivot(left));
+ Assertions.assertEquals(ref.nextSetBit(left), cache.nextPivot(left));
+ Assertions.assertEquals(Integer.MAX_VALUE, cache.nextPivotOrElse(right, Integer.MAX_VALUE));
+ // This assumes the cache supports internal scanning
+ // With current implementations this is true.
+ for (final int[] batch : indices) {
+ for (final int i : batch) {
+ ref.set(i);
+ cache.add(i);
+ if (i >= left && i <= right && !cache.sparse()) {
+ Assertions.assertTrue(cache.contains(i));
+ }
+ }
+ Assertions.assertEquals(left, cache.left());
+ Assertions.assertEquals(right, cache.right());
+ // Flanking pivots. Note: If actually used for partitioning these
+ // must be updated from the possible -1 result.
+ final int lower = ref.previousSetBit(left);
+ final int upper = ref.nextSetBit(right);
+ Assertions.assertEquals(lower, cache.previousPivot(left));
+ Assertions.assertEquals(upper, cache.nextPivot(right));
+ if (upper < 0) {
+ Assertions.assertEquals(Integer.MAX_VALUE, cache.nextPivotOrElse(right, Integer.MAX_VALUE));
+ }
+
+ // The partition algorithm must run so [left, right] is sorted
+ // lower---left--------------------------right----upper
+
+ if (cache.sparse()) {
+ continue;
+ }
+
+ // Not sparse: can check for indices
+ Assertions.assertEquals(ref.get(left), cache.contains(left));
+ Assertions.assertEquals(ref.get(right), cache.contains(right));
+
+ if (!(cache instanceof ScanningPivotCache)) {
+ continue;
+ }
+ final ScanningPivotCache scanningCache = (ScanningPivotCache) cache;
+
+ // Test internal scanning from the ends for additional pivots
+ // lower---left------p----------p2-------right----upper
+ // s--------e
+
+ int p = ref.nextSetBit(left);
+ Assertions.assertEquals(p, cache.nextPivot(left));
+
+ if (p == upper) {
+ // No internal pivots: just run partitioning
+ continue;
+ }
+
+ int p2 = ref.previousSetBit(right);
+ Assertions.assertEquals(p2, cache.previousPivot(right));
+
+ // Must partition: (lower, p) and (p2, upper)
+ // Then fully sort all unsorted parts in (p, p2)
+
+ // left to right
+ // Check the traversal with a copy
+ BitSet copy = (BitSet) ref.clone();
+ // partition [left, p) using bracket (lower, p)
+ copy.set(left, p);
+
+ int s = ref.nextClearBit(p);
+ int e = ref.nextSetBit(s);
+ // e can be -1 so check it is within left
+ while (left < e && e < upper) {
+ // sort [s, e)
+ copy.set(s, e);
+ Assertions.assertEquals(s, scanningCache.nextNonPivot(p));
+ Assertions.assertEquals(e, cache.nextPivot(s));
+ p = s;
+ s = ref.nextClearBit(p);
+ e = ref.nextSetBit(s);
+ }
+ // partition (p, right] using bracket (p, upper)
+ copy.set(p + 1, right + 1);
+ final int unsorted = copy.nextClearBit(left);
+ Assertions.assertTrue(right < unsorted, () -> "Bad left-to-right traversal: " + unsorted);
+
+ // right to left
+ // Check the traversal with a copy
+ copy = (BitSet) ref.clone();
+ // partition (p2, right] using bracket (p2, upper)
+ copy.set(p2 + 1, right + 1);
+
+ e = ref.previousClearBit(p2);
+ s = ref.previousSetBit(e);
+ while (lower < s) {
+ // sort (s, e]
+ copy.set(s + 1, e + 1);
+ Assertions.assertEquals(e, scanningCache.previousNonPivot(p2));
+ Assertions.assertEquals(s, cache.previousPivot(e));
+ p2 = s;
+ e = ref.previousClearBit(p2);
+ s = ref.previousSetBit(e);
+ }
+
+ // partition [left, p2) using bracket (lower, p2)
+ copy.set(left, p2);
+ final int unsorted2 = copy.previousClearBit(right);
+ Assertions.assertTrue(unsorted2 < left, () -> "Bad right-to-left traversal: " + unsorted2);
+ }
+ }
+
+ static Stream This test progressively adds more indices to the cache. It then verifies the
+ * range is correctly bracketed and any internal pivots can be traversed as sorted
+ * (pivot) and unsorted (non-pivot) regions.
+ *
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param indices Batches of indices to add.
+ * @param cache Pivot cache.
+ */
+ private static void assertSetRange(int left, int right, int[][] indices, PivotCache cache) {
+ final BitSet ref = new BitSet();
+ Assertions.assertEquals(left, cache.left());
+ Assertions.assertEquals(right, cache.right());
+ Assertions.assertEquals(ref.previousSetBit(left), cache.previousPivot(left));
+ Assertions.assertEquals(ref.nextSetBit(left), cache.nextPivot(left));
+ Assertions.assertEquals(Integer.MAX_VALUE, cache.nextPivotOrElse(right, Integer.MAX_VALUE));
+ // This assumes the cache supports internal scanning
+ // With current implementations this is true.
+ for (final int[] batch : indices) {
+ // BitSet uses an exclusive end
+ ref.set(batch[0], batch[1] + 1);
+ cache.add(batch[0], batch[1]);
+ Assertions.assertEquals(left, cache.left());
+ Assertions.assertEquals(right, cache.right());
+ // Flanking pivots. Note: If actually used for partitioning these
+ // must be updated from the possible -1 result.
+ final int lower = ref.previousSetBit(left);
+ final int upper = ref.nextSetBit(right);
+ Assertions.assertEquals(lower, cache.previousPivot(left), "left flanking pivot");
+ Assertions.assertEquals(upper, cache.nextPivot(right), "right flanking pivot");
+ if (upper < 0) {
+ Assertions.assertEquals(Integer.MAX_VALUE, cache.nextPivotOrElse(right, Integer.MAX_VALUE));
+ }
+ if (cache.sparse()) {
+ continue;
+ }
+ // Simple test within the range
+ for (int i = left; i <= right; i++) {
+ Assertions.assertEquals(ref.get(i), cache.contains(i));
+ }
+ }
+ }
+
+ static Stream{@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ *
+ * data [0, 1, 2, 1, 2, 5, 2, 3, 3, 6, 7, 7, 7, 7]
+ *
+ *
+ * k=4 : [0, 1, 2, 1], [2], [5, 2, 3, 3, 6, 7, 7, 7, 7]
+ * k=4,8 : [0, 1, 2, 1], [2], [3, 3, 2], [5], [6, 7, 7, 7, 7]
+ *
+ *
+ *
+ *
+ *
+ * @since 1.2
+ */
+public final class Selection {
+
+ /** No instances. */
+ private Selection() {}
+
+ /**
+ * Partition the array such that index {@code k} corresponds to its correctly
+ * sorted value in the equivalent fully sorted array.
+ *
+ * @param a Values.
+ * @param k Index.
+ * @throws IndexOutOfBoundsException if index {@code k} is not within the
+ * sub-range {@code [0, a.length)}
+ */
+ public static void select(double[] a, int k) {
+ IndexSupport.checkIndex(0, a.length, k);
+ doSelect(a, 0, a.length, k);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array.
+ *
+ * @param a Values.
+ * @param k Indices (may be destructively modified).
+ * @throws IndexOutOfBoundsException if any index {@code k} is not within the
+ * sub-range {@code [0, a.length)}
+ */
+ public static void select(double[] a, int[] k) {
+ IndexSupport.checkIndices(0, a.length, k);
+ doSelect(a, 0, a.length, k);
+ }
+
+ /**
+ * Partition the array such that index {@code k} corresponds to its correctly
+ * sorted value in the equivalent fully sorted array.
+ *
+ * @param a Values.
+ * @param fromIndex Index of the first element (inclusive).
+ * @param toIndex Index of the last element (exclusive).
+ * @param k Index.
+ * @throws IndexOutOfBoundsException if the sub-range {@code [fromIndex, toIndex)} is out of
+ * bounds of range {@code [0, a.length)}; or if index {@code k} is not within the
+ * sub-range {@code [fromIndex, toIndex)}
+ */
+ public static void select(double[] a, int fromIndex, int toIndex, int k) {
+ IndexSupport.checkFromToIndex(fromIndex, toIndex, a.length);
+ IndexSupport.checkIndex(fromIndex, toIndex, k);
+ doSelect(a, fromIndex, toIndex, k);
+ }
+
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array.
+ *
+ * @param a Values.
+ * @param fromIndex Index of the first element (inclusive).
+ * @param toIndex Index of the last element (exclusive).
+ * @param k Indices (may be destructively modified).
+ * @throws IndexOutOfBoundsException if the sub-range {@code [fromIndex, toIndex)} is out of
+ * bounds of range {@code [0, a.length)}; or if any index {@code k} is not within the
+ * sub-range {@code [fromIndex, toIndex)}
+ */
+ public static void select(double[] a, int fromIndex, int toIndex, int[] k) {
+ IndexSupport.checkFromToIndex(fromIndex, toIndex, a.length);
+ IndexSupport.checkIndices(fromIndex, toIndex, k);
+ doSelect(a, fromIndex, toIndex, k);
+ }
+
+ /**
+ * Partition the array such that index {@code k} corresponds to its correctly
+ * sorted value in the equivalent fully sorted array.
+ *
+ *
+ *
+ *
+ * {@code
+ * l-----------k----------r
+ * |--> l
+ * }
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the new left
+ */
+ int updateLeft(int k);
+
+ /**
+ * Update the right bound of the interval so {@code right <= k}.
+ *
+ * {@code
+ * l-----------k----------r
+ * r <--|
+ * }
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the new right
+ */
+ int updateRight(int k);
+
+ /**
+ * Split the interval using two splitting indices. Returns the left interval that occurs
+ * before the specified split index {@code ka}, and updates the current interval left bound
+ * to after the specified split index {@code kb}.
+ *
+ * {@code
+ * l-----------ka-kb----------r
+ * r1 <--| |--> l1
+ *
+ * r1 < ka
+ * l1 > kb
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ */
+ void partition(double[] a, int left, int right, int ka, int kb);
+ }
+
+ /**
+ * Partition function. Used to test different implementations.
+ */
+ private interface DoublePartitionFunction {
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ *
+ * Interval: 012345678
+ * Compressed (c=1): 0-1-2-3-4
+ * Compressed (c=2): 0---1---2
+ * Compressed (c=2): 0-------1
+ *
+ *
+ *
+ * Interval: 012345678
+ * Compressed: 0-1-2-3-4
+ *
+ *
+ * {@code
+ * Double.compare(x, y) > 0
+ * }
+ *
+ * @param x Value.
+ * @param y Value.
+ * @return {@code x > y}
+ */
+ static boolean greaterThan(double x, double y) {
+ if (x > y) {
+ return true;
+ }
+ if (x < y) {
+ return false;
+ }
+ // Equal numbers; signed zeros (-0.0, 0.0); or NaNs
+ final long a = Double.doubleToLongBits(x);
+ final long b = Double.doubleToLongBits(y);
+ return a > b;
+ }
+
+ /**
+ * Return {@code true} if {@code x < y}.
+ *
+ * {@code
+ * Double.compare(x, y) < 0
+ * }
+ *
+ * @param x Value.
+ * @param y Value.
+ * @return {@code x < y}
+ */
+ static boolean lessThan(double x, double y) {
+ if (x < y) {
+ return true;
+ }
+ if (x > y) {
+ return false;
+ }
+ // Equal numbers; signed zeros (-0.0, 0.0); or NaNs
+ final long a = Double.doubleToLongBits(x);
+ final long b = Double.doubleToLongBits(y);
+ return a < b;
+ }
+
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/DualPivotingStrategy.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/DualPivotingStrategy.java
new file mode 100644
index 000000000..5b2a99ee4
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/DualPivotingStrategy.java
@@ -0,0 +1,817 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * A strategy to pick two pivot indices of an array for partitioning.
+ *
+ *
+ * min max mean sd median skew
+ * [1] 0.0000 0.9970 0.3327 0.2357 0.2920 0.5654
+ * [2] 0.0020 1.0000 0.3346 0.2356 0.2940 0.5675
+ * [3] 0.0000 0.9970 0.3328 0.2356 0.2920 0.5656
+ *
+ */
+ MEDIANS {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int[] pivot2) {
+ // Original 'medians' method from the dual-pivot quicksort paper by Vladimir Yaroslavskiy
+ final int len = right - left;
+ // Do not pivot at the ends by setting 1/3 to at least 1.
+ // This is safe if len >= 2.
+ final int third = Math.max(1, len / 3);
+ final int m1 = left + third;
+ final int m2 = right - third;
+ // Ensure p1 is lower
+ if (data[m1] < data[m2]) {
+ pivot2[0] = m2;
+ return m1;
+ }
+ pivot2[0] = m1;
+ return m2;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right) {
+ final int len = right - left;
+ final int third = Math.max(1, len / 3);
+ final int m1 = left + third;
+ final int m2 = right - third;
+ return new int[] {m1, m2};
+ }
+
+ @Override
+ int samplingEffect() {
+ return UNCHANGED;
+ }
+ },
+ /**
+ * Pivot around the 2nd and 4th values from 5 approximately uniformly spaced within the range.
+ * Uses points +/- sixths from the median: 1/6, 1/3, 1/2, 2/3, 5/6.
+ *
+ *
+ * min max mean sd median skew
+ * [1] 0.0010 0.9820 0.3327 0.1778 0.3130 0.4650
+ * [2] 0.0030 0.9760 0.3348 0.1778 0.3150 0.4665
+ * [3] 0.0010 0.9870 0.3325 0.1779 0.3130 0.4698
+ *
+ */
+ SORT_5 {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int[] pivot2) {
+ // 1/6 = 5/30 ~ 1/8 + 1/32 + 1/64 : 0.1666 ~ 0.1719
+ // Ensure the value is above zero to choose different points!
+ // This is safe if len >= 4.
+ final int len = right - left;
+ final int sixth = 1 + (len >>> 3) + (len >>> 5) + (len >>> 6);
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - sixth;
+ final int p1 = p2 - sixth;
+ final int p4 = p3 + sixth;
+ final int p5 = p4 + sixth;
+ Sorting.sort5(data, p1, p2, p3, p4, p5);
+ pivot2[0] = p4;
+ return p2;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right) {
+ final int len = right - left;
+ final int sixth = 1 + (len >>> 3) + (len >>> 5) + (len >>> 6);
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - sixth;
+ final int p1 = p2 - sixth;
+ final int p4 = p3 + sixth;
+ final int p5 = p4 + sixth;
+ return new int[] {p1, p2, p3, p4, p5};
+ }
+
+ @Override
+ int samplingEffect() {
+ return SORT;
+ }
+ },
+ /**
+ * Pivot around the 2nd and 4th values from 5 approximately uniformly spaced within the range.
+ * Uses points +/- sevenths from the median: 3/14, 5/14, 1/2, 9/14, 11/14.
+ *
+ *
+ * min max mean sd median skew
+ * [1] 0.0010 0.9790 0.3330 0.1780 0.3140 0.4665
+ * [2] 0.0030 0.9800 0.3348 0.1778 0.3150 0.4681
+ * [3] 0.0010 0.9770 0.3322 0.1777 0.3130 0.4677
+ *
+ */
+ SORT_5B {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int[] pivot2) {
+ // 1/7 = 5/35 ~ 1/8 + 1/64 : 0.1429 ~ 0.1406
+ // Ensure the value is above zero to choose different points!
+ // This is safe if len >= 4.
+ final int len = right - left;
+ final int seventh = 1 + (len >>> 3) + (len >>> 6);
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - seventh;
+ final int p1 = p2 - seventh;
+ final int p4 = p3 + seventh;
+ final int p5 = p4 + seventh;
+ Sorting.sort5(data, p1, p2, p3, p4, p5);
+ pivot2[0] = p4;
+ return p2;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right) {
+ final int len = right - left;
+ final int seventh = 1 + (len >>> 3) + (len >>> 6);
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - seventh;
+ final int p1 = p2 - seventh;
+ final int p4 = p3 + seventh;
+ final int p5 = p4 + seventh;
+ return new int[] {p1, p2, p3, p4, p5};
+ }
+
+ @Override
+ int samplingEffect() {
+ return SORT;
+ }
+ },
+ /**
+ * This strategy is the same as {@link #SORT_5B} with the exception that it
+ * returns identical pivots if the data at the chosen pivots is equal.
+ *
+ *
+ * min max mean sd median skew
+ * [1] 0.0010 0.9790 0.3324 0.1779 0.3130 0.4666
+ * [2] 0.0030 0.9850 0.3348 0.1778 0.3150 0.4686
+ * [3] 0.0010 0.9720 0.3327 0.1779 0.3130 0.4666
+ *
+ */
+ SORT_5C {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int[] pivot2) {
+ // 1/8 = 0.125
+ // Ensure the value is above zero to choose different points!
+ // This is safe if len >= 4.
+ final int len = right - left;
+ final int eighth = 1 + (len >>> 3);
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - eighth;
+ final int p1 = p2 - eighth;
+ final int p4 = p3 + eighth;
+ final int p5 = p4 + eighth;
+ Sorting.sort5(data, p1, p2, p3, p4, p5);
+ pivot2[0] = p4;
+ return p2;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right) {
+ final int len = right - left;
+ final int eighth = 1 + (len >>> 3);
+ final int p3 = left + (len >>> 1);
+ final int p2 = p3 - eighth;
+ final int p1 = p2 - eighth;
+ final int p4 = p3 + eighth;
+ final int p5 = p4 + eighth;
+ return new int[] {p1, p2, p3, p4, p5};
+ }
+
+ @Override
+ int samplingEffect() {
+ return SORT;
+ }
+ },
+ /**
+ * Pivot around the 2nd and 4th values from 5 medians approximately uniformly spaced within
+ * the range. The medians are from 3 samples. The 5 samples of 3 do not overlap thus this
+ * method requires {@code right - left >= 14}. The samples can be visualised as 5 sorted
+ * columns:
+ *
+ *
+ * v w x y z
+ * 1 2 3 4 5
+ * a b c d e
+ *
+ *
+ *
+ * min max mean sd median skew
+ * [1] 0.0090 0.9170 0.3783 0.1320 0.3730 0.2107
+ * [2] 0.0030 0.8950 0.2438 0.1328 0.2270 0.6150
+ * [3] 0.0110 0.9140 0.3779 0.1319 0.3730 0.2114
+ *
+ *
+ * w x y z
+ * 1 2 3 4
+ * a b c d
+ *
+ *
+ *
+ * min max mean sd median skew
+ * [1] 0.0160 0.9580 0.4269 0.1454 0.4230 0.1366
+ * [2] 0.0020 0.8270 0.1467 0.1193 0.1170 1.1417
+ * [3] 0.0140 0.9560 0.4264 0.1453 0.4230 0.1352
+ *
+ *
+ * x y z
+ * 1 2 3
+ * a b c
+ *
+ *
+ *
+ * min max mean sd median skew
+ * [1] 0.0010 0.9460 0.3062 0.1560 0.2910 0.4455
+ * [2] 0.0030 0.9820 0.3875 0.1813 0.3780 0.2512
+ * [3] 0.0010 0.9400 0.3063 0.1558 0.2910 0.4453
+ *
+ *
+ * v w x y z
+ * q r s t u
+ * 1 2 3 4 5
+ * f g h i j
+ * a b c d e
+ *
+ *
+ *
+ * min max mean sd median skew
+ * [1] 0.0270 0.8620 0.3996 0.1093 0.3970 0.1130
+ * [2] 0.0030 0.8100 0.2010 0.1106 0.1860 0.6691
+ * [3] 0.0270 0.8970 0.3994 0.1093 0.3970 0.1147
+ *
+ *
+ * min max mean sd median skew
+ * [1] 0.0020 0.9600 0.3745 0.1609 0.3640 0.3092
+ * [2] 0.0030 0.9490 0.2512 0.1440 0.2300 0.6920
+ * [3] 0.0030 0.9620 0.3743 0.1609 0.3640 0.3100
+ *
+ *
+ * min max mean sd median skew
+ * [1] 0.0030 0.9480 0.3327 0.1485 0.3200 0.4044
+ * [2] 0.0050 0.9350 0.3345 0.1485 0.3220 0.4056
+ * [3] 0.0020 0.9320 0.3328 0.1485 0.3200 0.4063
+ *
+ */
+ SORT_8 {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int[] pivot2) {
+ // 1/9 = 4/36 = 8/72 ~ 7/64 ~ 1/16 + 1/32 + 1/64 : 0.11111 ~ 0.1094
+ // Ensure the value is above zero to choose different points!
+ // This is safe if len >= 7.
+ final int len = right - left;
+ final int ninth = Math.max(1, (len >>> 4) + (len >>> 5) + (len >>> 6));
+ // Work from middle outward. This is deliberate to ensure data.length==7
+ // throws an index out-of-bound exception.
+ final int m = left + (len >>> 1);
+ final int p4 = m - (ninth >> 1);
+ final int p3 = p4 - ninth;
+ final int p2 = p3 - ninth;
+ final int p1 = p2 - ninth;
+ final int p5 = m + (ninth >> 1) + 1;
+ final int p6 = p5 + ninth;
+ final int p7 = p6 + ninth;
+ final int p8 = p7 + ninth;
+ Sorting.sort8(data, p1, p2, p3, p4, p5, p6, p7, p8);
+ pivot2[0] = p6;
+ return p3;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right) {
+ final int len = right - left;
+ final int ninth = Math.max(1, (len >>> 4) + (len >>> 5) + (len >>> 6));
+ final int m = left + (len >>> 1);
+ final int p4 = m - (ninth >> 1);
+ final int p3 = p4 - ninth;
+ final int p2 = p3 - ninth;
+ final int p1 = p2 - ninth;
+ final int p5 = m + (ninth >> 1) + 1;
+ final int p6 = p5 + ninth;
+ final int p7 = p6 + ninth;
+ final int p8 = p7 + ninth;
+ return new int[] {p1, p2, p3, p4, p5, p6, p7, p8};
+ }
+
+ @Override
+ int samplingEffect() {
+ return SORT;
+ }
+ },
+ /**
+ * Pivot around the 4th and 8th values from 11 approximately uniformly spaced within the range.
+ * Uses points +/- twelfths from the median: ..., m - 1/12, m, m + 1/12, ... .
+ *
+ *
+ * min max mean sd median skew
+ * [1] 0.0060 0.9000 0.3328 0.1301 0.3230 0.3624
+ * [2] 0.0100 0.9190 0.3345 0.1299 0.3250 0.3643
+ * [3] 0.0060 0.8970 0.3327 0.1302 0.3230 0.3653
+ *
+ */
+ SORT_11 {
+ @Override
+ int pivotIndex(double[] data, int left, int right, int[] pivot2) {
+ // 1/12 = 8/96 ~ 1/16 + 1/32 ~ 9/96 : 0.8333 ~ 0.09375
+ // Ensure the value is above zero to choose different points!
+ // This is safe if len >= 10.
+ final int len = right - left;
+ final int twelfth = Math.max(1, (len >>> 4) + (len >>> 6));
+ final int p6 = left + (len >>> 1);
+ final int p5 = p6 - twelfth;
+ final int p4 = p5 - twelfth;
+ final int p3 = p4 - twelfth;
+ final int p2 = p3 - twelfth;
+ final int p1 = p2 - twelfth;
+ final int p7 = p6 + twelfth;
+ final int p8 = p7 + twelfth;
+ final int p9 = p8 + twelfth;
+ final int p10 = p9 + twelfth;
+ final int p11 = p10 + twelfth;
+ Sorting.sort11(data, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11);
+ pivot2[0] = p8;
+ return p4;
+ }
+
+ @Override
+ int[] getSampledIndices(int left, int right) {
+ final int len = right - left;
+ final int twelfth = Math.max(1, (len >>> 4) + (len >>> 6));
+ final int p6 = left + (len >>> 1);
+ final int p5 = p6 - twelfth;
+ final int p4 = p5 - twelfth;
+ final int p3 = p4 - twelfth;
+ final int p2 = p3 - twelfth;
+ final int p1 = p2 - twelfth;
+ final int p7 = p6 + twelfth;
+ final int p8 = p7 + twelfth;
+ final int p9 = p8 + twelfth;
+ final int p10 = p9 + twelfth;
+ final int p11 = p10 + twelfth;
+ return new int[] {p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11};
+ }
+
+ @Override
+ int samplingEffect() {
+ return SORT;
+ }
+ };
+
+ /** Sampled points are unchanged. */
+ static final int UNCHANGED = 0;
+ /** Sampled points are partially sorted. */
+ static final int PARTIAL_SORT = 0x1;
+ /** Sampled points are sorted. */
+ static final int SORT = 0x2;
+
+ /**
+ * Find two pivot indices of the array so that partitioning into 3-regions can be made.
+ *
+ * {@code
+ * left <= p1 <= p2 <= right
+ * }
+ *
+ *
+ *
+ *
+ * @return the effect
+ */
+ abstract int samplingEffect();
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/HashIndexSet.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/HashIndexSet.java
new file mode 100644
index 000000000..a9e999abe
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/HashIndexSet.java
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * An index set backed by a open-addressed hash table using linear hashing. Table size is a power
+ * of 2 and has a maximum capacity of 2^29 with a fixed load factor of 0.5. If the functional
+ * capacity is exceeded then the set raises an {@link IllegalStateException}.
+ *
+ * {@code
+ * int[] keys = ...
+ * HashIndexSet set = new HashIndexSet(keys.length);
+ * for (int k : keys) {
+ * if (set.add(k)) {
+ * // first occurrence of k in keys
+ * }
+ * }
+ * }
+ *
+ * @see Open addressing (Wikipedia)
+ * @since 1.2
+ */
+final class HashIndexSet {
+ /** Message for an invalid index. */
+ private static final String INVALID_INDEX = "Invalid index: ";
+ /** The maximum capacity of the set. */
+ private static final int MAX_CAPACITY = 1 << 29;
+ /** The minimum size of the backing array. */
+ private static final int MIN_SIZE = 16;
+ /**
+ * Unsigned 32-bit integer numerator of the golden ratio (0.618) with an assumed
+ * denominator of 2^32.
+ *
+ *
+ * 2654435769 = round(2^32 * (sqrt(5) - 1) / 2)
+ * Long.toHexString((long)(0x1p32 * (Math.sqrt(5.0) - 1) / 2))
+ *
+ */
+ private static final int PHI = 0x9e3779b9;
+
+ /** The set. */
+ private final int[] set;
+ /** The size. */
+ private int size;
+
+ /**
+ * Create an instance with size to store up to the specified {@code capacity}.
+ *
+ *
+ * l----r
+ * l----r
+ * lr
+ * lr
+ * l----------------r
+ *
+ *
+ * @since 1.2
+ */
+interface IndexIterator {
+ /**
+ * The start (inclusive) of the current block of indices.
+ *
+ * @return start index
+ */
+ int left();
+
+ /**
+ * The end (inclusive) of the current block of indices.
+ *
+ * @return end index
+ */
+ int right();
+
+ /**
+ * The end index.
+ *
+ * @return the end index
+ */
+ int end();
+
+ /**
+ * Advance the iterator to the next block of indices.
+ *
+ * {@code
+ * while (right() <= index) {
+ * if (!next()) {
+ * return false;
+ * }
+ * }
+ * return false;
+ * }
+ *
+ * {@code
+ * return right() >= end();
+ * }
+ *
+ * @param index Index.
+ * @return true if the next {@code left > index}, or there is no next left
+ */
+ default boolean nextAfter(int index) {
+ return right() >= end();
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexIterators.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexIterators.java
new file mode 100644
index 000000000..0f377a389
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/IndexIterators.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * Support for creating {@link IndexIterator} implementations.
+ *
+ * @since 1.2
+ */
+final class IndexIterators {
+
+ /** No instances. */
+ private IndexIterators() {}
+
+ /**
+ * Creates an iterator for index {@code k}.
+ *
+ * @param k Index.
+ * @return the iterator
+ */
+ static IndexIterator ofIndex(int k) {
+ return new SingleIndex(k);
+ }
+
+ /**
+ * Creates an iterator for the closed interval {@code [k1, k2]}.
+ *
+ *
+ * 0010100011000101000100
+ * 0 2 2 0 2 0 2 2 0 2 0
+ *
+ *
+ * 0010000011000101000100
+ * 4 0 4 4 4 0
+ *
+ *
+ * 0010000011000101000000
+ * 8 8 0
+ *
+ *
+ * 0010000011000101000000
+ * 16 0
+ *
+ *
+ * 0010000011000101000000
+ * 32
+ *
+ *
+ * 0010000011000101000000
+ * 64
+ *
+ * {@code distance = 1 << separation}
+ *
+ *
+ *
+ *
+ * @param separation Log2 of the maximum separation between indices.
+ * @return true if saturated
+ */
+ boolean saturated(int separation);
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/KeyIndexIterator.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/KeyIndexIterator.java
new file mode 100644
index 000000000..bb7eaec48
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/KeyIndexIterator.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * An {@link IndexIterator} backed by an array of ordered keys.
+ *
+ * @since 1.2
+ */
+final class KeyIndexIterator implements IndexIterator {
+ /** The ordered keys. */
+ private final int[] keys;
+ /** The original number of keys minus 1. */
+ private final int nm1;
+
+ /** Iterator left. */
+ private int left;
+ /** Iterator right position. Never advanced beyond {@code n - 1}. */
+ private int hi = -1;
+
+ /**
+ * Create an instance with the provided keys.
+ *
+ * @param indices Indices.
+ * @param n Number of indices.
+ */
+ KeyIndexIterator(int[] indices, int n) {
+ keys = indices;
+ this.nm1 = n - 1;
+ next();
+ }
+
+ /**
+ * Initialise an instance with {@code n} initial {@code indices}. The indices are used in place.
+ *
+ * @param indices Indices.
+ * @param n Number of indices.
+ * @return the iterator
+ * @throws IllegalArgumentException if the indices are not unique and ordered; or not
+ * in the range {@code [0, 2^31-1)}; or {@code n <= 0}
+ */
+ static KeyIndexIterator of(int[] indices, int n) {
+ // Check the indices are uniquely ordered
+ if (n <= 0) {
+ throw new IllegalArgumentException("No indices to define the range");
+ }
+ int p = indices[0];
+ for (int i = 0; ++i < n;) {
+ final int c = indices[i];
+ if (c <= p) {
+ throw new IllegalArgumentException("Indices are not unique and ordered");
+ }
+ p = c;
+ }
+ if (indices[0] < 0) {
+ throw new IllegalArgumentException("Unsupported min value: " + indices[0]);
+ }
+ if (indices[n - 1] == Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("Unsupported max value: " + Integer.MAX_VALUE);
+ }
+ return new KeyIndexIterator(indices, n);
+ }
+
+ @Override
+ public int left() {
+ return left;
+ }
+
+ @Override
+ public int right() {
+ return keys[hi];
+ }
+
+ @Override
+ public int end() {
+ return keys[nm1];
+ }
+
+ @Override
+ public boolean next() {
+ int i = hi;
+ if (i < nm1) {
+ // Blocks [left, right] use a maximum separation of 2 between indices
+ int k = keys[++i];
+ left = k;
+ while (++i <= nm1 && k + 2 >= keys[i]) {
+ k = keys[i];
+ }
+ hi = i - 1;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean positionAfter(int index) {
+ int i = hi;
+ int r = keys[i];
+ if (r <= index && i < nm1) {
+ // fast-forward right
+ while (++i <= nm1) {
+ r = keys[i];
+ if (r > index) {
+ // Advance to match the output of next()
+ while (++i <= nm1 && r + 2 >= keys[i]) {
+ r = keys[i];
+ }
+ break;
+ }
+ }
+ hi = --i;
+ // calculate left
+ // Blocks [left, right] use a maximum separation of 2 between indices
+ int k = r;
+ while (--i >= 0 && keys[i] + 2 >= k) {
+ k = keys[i];
+ // indices <= index are not required
+ if (k <= index) {
+ left = index + 1;
+ return r > index;
+ }
+ }
+ left = k;
+ }
+ return r > index;
+ }
+
+ @Override
+ public boolean nextAfter(int index) {
+ if (hi < nm1) {
+ // test if the next left is after the index
+ return keys[hi + 1] > index;
+ }
+ // no more indices
+ return true;
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/KeyUpdatingInterval.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/KeyUpdatingInterval.java
new file mode 100644
index 000000000..33620dd3f
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/KeyUpdatingInterval.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * An {@link UpdatingInterval} and {@link SplittingInterval} backed by an array of ordered keys.
+ *
+ * @since 1.2
+ */
+final class KeyUpdatingInterval implements UpdatingInterval, SplittingInterval {
+ /** Size to use a scan of the keys when splitting instead of binary search.
+ * Note binary search has an overhead on small size due to the random left/right
+ * branching per iteration. It is much faster on very large sizes. */
+ private static final int SCAN_SIZE = 256;
+
+ /** The ordered keys. */
+ private final int[] keys;
+ /** Index of the left key. */
+ private int l;
+ /** Index of the right key. */
+ private int r;
+
+ /**
+ * Create an instance with the provided {@code indices}.
+ * Indices must be sorted.
+ *
+ * @param indices Indices.
+ * @param n Number of indices.
+ */
+ KeyUpdatingInterval(int[] indices, int n) {
+ this(indices, 0, n - 1);
+ }
+
+ /**
+ * @param indices Indices.
+ * @param l Index of left key.
+ * @param r Index of right key.
+ */
+ private KeyUpdatingInterval(int[] indices, int l, int r) {
+ keys = indices;
+ this.l = l;
+ this.r = r;
+ }
+
+ /**
+ * Initialise an instance with the {@code indices}. The indices are used in place.
+ *
+ * @param indices Indices.
+ * @param n Number of indices.
+ * @return the interval
+ * @throws IllegalArgumentException if the indices are not unique and ordered;
+ * or {@code n <= 0}
+ */
+ static KeyUpdatingInterval of(int[] indices, int n) {
+ // Check the indices are uniquely ordered
+ if (n <= 0) {
+ throw new IllegalArgumentException("No indices to define the range");
+ }
+ int p = indices[0];
+ for (int i = 0; ++i < n;) {
+ final int c = indices[i];
+ if (c <= p) {
+ throw new IllegalArgumentException("Indices are not unique and ordered");
+ }
+ p = c;
+ }
+ if (indices[0] < 0) {
+ throw new IllegalArgumentException("Unsupported min value: " + indices[0]);
+ }
+ if (indices[n - 1] == Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("Unsupported max value: " + Integer.MAX_VALUE);
+ }
+ return new KeyUpdatingInterval(indices, n);
+ }
+
+ @Override
+ public int left() {
+ return keys[l];
+ }
+
+ @Override
+ public int right() {
+ return keys[r];
+ }
+
+ @Override
+ public int updateLeft(int k) {
+ // Assume left < k <= right (i.e. we must move left at least 1)
+ // Search using a scan on the assumption that k is close to the end
+ int i = l;
+ do {
+ ++i;
+ } while (keys[i] < k);
+ l = i;
+ return keys[i];
+ }
+
+ @Override
+ public int updateRight(int k) {
+ // Assume left <= k < right (i.e. we must move right at least 1)
+ // Search using a scan on the assumption that k is close to the end
+ int i = r;
+ do {
+ --i;
+ } while (keys[i] > k);
+ r = i;
+ return keys[i];
+ }
+
+ @Override
+ public UpdatingInterval splitLeft(int ka, int kb) {
+ // left < ka <= kb < right
+
+ // Find the new left bound for the upper interval.
+ // Switch to a linear scan if length is small.
+ int i;
+ if (r - l < SCAN_SIZE) {
+ i = r;
+ do {
+ --i;
+ } while (keys[i] > kb);
+ } else {
+ // Binary search
+ i = Partition.searchLessOrEqual(keys, l, r, kb);
+ }
+ final int lowerLeft = l;
+ l = i + 1;
+
+ // Find the new right bound for the lower interval using a scan since a
+ // typical use case has ka == kb and this is faster than a second binary search.
+ while (keys[i] >= ka) {
+ --i;
+ }
+ // return left
+ return new KeyUpdatingInterval(keys, lowerLeft, i);
+ }
+
+ @Override
+ public UpdatingInterval splitRight(int ka, int kb) {
+ // left < ka <= kb < right
+
+ // Find the new left bound for the upper interval.
+ // Switch to a linear scan if length is small.
+ int i;
+ if (r - l < SCAN_SIZE) {
+ i = r;
+ do {
+ --i;
+ } while (keys[i] > kb);
+ } else {
+ // Binary search
+ i = Partition.searchLessOrEqual(keys, l, r, kb);
+ }
+ final int upperLeft = i + 1;
+
+ // Find the new right bound for the lower interval using a scan since a
+ // typical use case has ka == kb and this is faster than a second binary search.
+ while (keys[i] >= ka) {
+ --i;
+ }
+ final int upperRight = r;
+ r = i;
+ // return right
+ return new KeyUpdatingInterval(keys, upperLeft, upperRight);
+ }
+
+ /**
+ * Return the current number of indices in the interval.
+ * This is undefined when {@link #empty()}.
+ *
+ * @return the size
+ */
+ int size() {
+ return r - l + 1;
+ }
+
+ @Override
+ public boolean empty() {
+ // Empty when the interval is invalid. Signalled by a negative right index.
+ return r < 0;
+ }
+
+ @Override
+ public SplittingInterval split(int ka, int kb) {
+ if (ka <= left()) {
+ // No left interval
+ if (kb >= right()) {
+ // No right interval
+ invalidate();
+ } else if (kb >= left()) {
+ // Update the left bound.
+ // Search using a scan on the assumption that kb is close to the end
+ // given that ka is less then the end.
+ int i = l;
+ do {
+ ++i;
+ } while (keys[i] < kb);
+ l = i;
+ }
+ return null;
+ }
+ if (kb >= right()) {
+ // No right interval.
+ // Find new right bound for the left-side.
+ // Search using a scan on the assumption that ka is close to the end
+ // given that kb is greater then the end.
+ int i = r;
+ if (ka <= keys[i]) {
+ do {
+ --i;
+ } while (keys[i] > ka);
+ }
+ invalidate();
+ return new KeyUpdatingInterval(keys, l, i);
+ }
+ // Split
+ return (SplittingInterval) splitLeft(ka, kb);
+ }
+
+ /**
+ * Invalidate the interval and mark as empty.
+ */
+ private void invalidate() {
+ r = -1;
+ }
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/KthSelector.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/KthSelector.java
new file mode 100644
index 000000000..ef7ab5f59
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/KthSelector.java
@@ -0,0 +1,2365 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+import java.util.Arrays;
+import java.util.BitSet;
+
+/**
+ * A Kth selector implementation to pick up the Kth ordered element
+ * from a data array containing the input numbers. Uses a partial sort of the data.
+ *
+ * {@code c = (i - left) >> separation}
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ *
+ *
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ *
+ *
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ *
+ * data [0, 1, 2, 1, 2, 5, 2, 3, 3, 6, 7, 7, 7, 7]
+ *
+ *
+ * k=4 : [0, 1, 2, 1], [2], [5, 2, 3, 3, 6, 7, 7, 7, 7]
+ * k=4,8 : [0, 1, 2, 1], [2], [3, 3, 2], [5], [6, 7, 7, 7, 7]
+ *
+ *
+ *
+ *
+ *
+ * @since 1.2
+ */
+final class Partition {
+ // This class contains implementations for use in benchmarking.
+
+ /** Default pivoting strategy. Note: Using the dynamic strategy avoids excess recursion
+ * on the Bentley and McIlroy test data vs the MEDIAN_OF_3 strategy. */
+ static final PivotingStrategy PIVOTING_STRATEGY = PivotingStrategy.DYNAMIC;
+ /**
+ * Default pivoting strategy. Choosing from 5 points is unbiased on random data and
+ * has a lower standard deviation around the thirds than choosing 2 points
+ * (Yaroslavskiy's original method, see {@link DualPivotingStrategy#MEDIANS}). It
+ * performs well across various test data.
+ *
+ *
+ *
*/
+ static final int MIN_QUICKSELECT_SIZE = 0;
+ /** Minimum size for heapselect.
+ * Below this switch to insertion sort rather than selection. This is used to avoid
+ * heap select on tiny data. */
+ static final int MIN_HEAPSELECT_SIZE = 5;
+ /** Minimum size for sortselect.
+ * Below this switch to insertion sort rather than selection. This is used to avoid
+ * sort select on tiny data. */
+ static final int MIN_SORTSELECT_SIZE = 4;
+ /** Default selection constant for edgeselect. Any k closer than this to the left/right
+ * bound will be selected using the configured edge selection function. */
+ static final int EDGESELECT_CONSTANT = 20;
+ /** Default sort selection constant for linearselect. Note that linear select variants
+ * recursively call quickselect so very small lengths are included with an initial
+ * medium length. Using lengths of 1023-5 and 2043-53 indicate optimum performance around
+ * 80 for median-of-medians when placing the sample on the left. Adaptive linear methods
+ * are faster and so this value is reduced. Quickselect adaptive has a value around 20-30.
+ * Note: When using {@link ExpandStrategy#T2} the input length must create a sample of at
+ * least length 2 as each end of the sample is used as a sentinel. With a sample length of
+ * 1/12 of the data this requires edge select of at least 12. */
+ static final int LINEAR_SORTSELECT_SIZE = 24;
+ /** Default sub-sampling size to identify a single pivot. Sub-sampling is performed if the
+ * length is above this value thus using MAX_VALUE sets it off by default.
+ * The SELECT algorithm of Floyd-Rivest uses 600. */
+ static final int SUBSAMPLING_SIZE = Integer.MAX_VALUE;
+ /** Default key strategy. */
+ static final KeyStrategy KEY_STRATEGY = KeyStrategy.INDEX_SET;
+ /** Default 1 or 2 key strategy. */
+ static final PairedKeyStrategy PAIRED_KEY_STRATEGY = PairedKeyStrategy.SEARCHABLE_INTERVAL;
+ /** Default recursion multiple. */
+ static final int RECURSION_MULTIPLE = 2;
+ /** Default recursion constant. */
+ static final int RECURSION_CONSTANT = 0;
+ /** Default compression. */
+ static final int COMPRESSION_LEVEL = 1;
+ /** Default control flags. */
+ static final int CONTROL_FLAGS = 0;
+ /** Default option flags. */
+ static final int OPTION_FLAGS = 0;
+ /** Default single-pivot partition strategy. */
+ static final SPStrategy SP_STRATEGY = SPStrategy.KBM;
+ /** Default expand partition strategy. A ternary method is faster on equal elements and no
+ * slower on unique elements. */
+ static final ExpandStrategy EXPAND_STRATEGY = ExpandStrategy.T2;
+ /** Default single-pivot linear select strategy. */
+ static final LinearStrategy LINEAR_STRATEGY = LinearStrategy.RSA;
+ /** Default edge select strategy. */
+ static final EdgeSelectStrategy EDGE_STRATEGY = EdgeSelectStrategy.ESS;
+ /** Default single-pivot stopper strategy. */
+ static final StopperStrategy STOPPER_STRATEGY = StopperStrategy.SQA;
+ /** Default quickselect adaptive mode. */
+ static final AdaptMode ADAPT_MODE = AdaptMode.ADAPT3;
+
+ /** Sampling mode using Floyd-Rivest sampling. */
+ static final int MODE_FR_SAMPLING = -1;
+ /** Sampling mode. */
+ static final int MODE_SAMPLING = 0;
+ /** No sampling but use adaption of the target k. */
+ static final int MODE_ADAPTION = 1;
+ /** No sampling and no adaption of target k (strict margins). */
+ static final int MODE_STRICT = 2;
+
+ // Floyd-Rivest flags
+
+ /** Control flag for random sampling. */
+ static final int FLAG_RANDOM_SAMPLING = 0x2;
+ /** Control flag for vector swap of the sample. */
+ static final int FLAG_MOVE_SAMPLE = 0x4;
+ /** Control flag for random subset sampling. This creates the sample at the end
+ * of the data and requires moving regions to reposition around the target k. */
+ static final int FLAG_SUBSET_SAMPLING = 0x8;
+
+ // RNG flags
+
+ /** Control flag for biased nextInt(n) RNG. */
+ static final int FLAG_BIASED_RANDOM = 0x1000;
+ /** Control flag for SplittableRandom RNG. */
+ static final int FLAG_SPLITTABLE_RANDOM = 0x2000;
+ /** Control flag for MSWS RNG. */
+ static final int FLAG_MSWS = 0x4000;
+
+ // Quickselect adaptive flags. Must not clash with the Floyd-Rivest/RNG flags
+ // that are supported for sample mode.
+
+ /** Control flag for quickselect adaptive to propagate the no sampling mode recursively. */
+ static final int FLAG_QA_PROPAGATE = 0x1;
+ /** Control flag for quickselect adaptive variant of Floyd-Rivest random sampling. */
+ static final int FLAG_QA_RANDOM_SAMPLING = 0x4;
+ /** Control flag for quickselect adaptive to use a different far left/right step
+ * using min of 4; then median of 3 into the 2nd 12th-tile. The default (original) uses
+ * lower median of 4; then min of 3 into 4th 12th-tile). The default has a larger
+ * upper margin of 3/8 vs 1/3 for the new method. The new method is better
+ * with the original k mapping for far left/right and similar speed to the original
+ * far left/right step using the new k mapping. When sampling is off it is marginally
+ * faster, may be due to improved layout of the sample closer to the strict 1/12 lower margin.
+ * There is no compelling evidence to indicate is it better so the default uses
+ * the original far step method. */
+ static final int FLAG_QA_FAR_STEP = 0x8;
+ /** Control flag for quickselect adaptive to map k using the same k mapping for all
+ * repeated steps. This enables the original algorithm behaviour.
+ *
+ *
+ * target: k
+ * subset: ll---P-------rr
+ * sorted: l----------------------P-------------------------------------------r
+ * Good pivot
+ *
+ *
+ *
+ * target: k
+ * subset: ll----P-------rr
+ * sorted: l------------------------------------------P----------------------r
+ * Bad pivot
+ *
+ *
+ *
+ * Floyd and Rivest (1975)
+ * Algorithm 489: The Algorithm SELECT—for Finding the ith Smallest of n elements.
+ * Comm. ACM. 18 (3): 173.
+ *
*/
+ private final int subSamplingSize;
+
+ // Use non-final members for settings used to configure partitioning functions
+
+ /** Setting to indicate strategy for processing of multiple keys. */
+ private KeyStrategy keyStrategy = KEY_STRATEGY;
+ /** Setting to indicate strategy for processing of 1 or 2 keys. */
+ private PairedKeyStrategy pairedKeyStrategy = PAIRED_KEY_STRATEGY;
+
+ /** Multiplication factor {@code m} applied to the length based recursion factor {@code x}.
+ * The recursion is set using {@code m * x + c}.
+ * {@code
+ * data[i < ka] <= data[ka] <= data[kb] <= data[kb < i]
+ * }
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower bound (inclusive) of the central region.
+ * @param kb Upper bound (inclusive) of the central region.
+ * @param leftInner Flag to indicate {@code left - 1} is a pivot.
+ * @param rightInner Flag to indicate {@code right + 1} is a pivot.
+ */
+ void partition(double[] a, int left, int right, int ka, int kb,
+ boolean leftInner, boolean rightInner);
+
+ /**
+ * Partition (partially sort) the array in the range {@code [left, right]} around
+ * a central region {@code [ka, kb]}. The central region should be entirely
+ * sorted.
+ *
+ * {@code
+ * data[i < ka] <= data[ka] <= data[kb] <= data[kb < i]
+ * }
+ *
+ * {@code
+ * data[i < ka] <= data[ka] <= data[kb] <= data[kb < i]
+ * }
+ *
+ * {@code
+ * |k0 k1|
+ * |
+ *
+ *
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param upper Upper bound (inclusive) of the pivot range [k1].
+ * @param leftInner Flag to indicate {@code left - 1} is a pivot.
+ * @param rightInner Flag to indicate {@code right + 1} is a pivot.
+ * @return Lower bound (inclusive) of the pivot range [k0].
+ */
+ int partition(double[] a, int left, int right, int[] upper,
+ boolean leftInner, boolean rightInner);
+
+ // Add support to have a pivot cache. Assume it is to store pivots after kb.
+ // Switch to not using it when right < kb, or doing a full sort between
+ // left and right (pivots are irrelevant).
+
+ @Override
+ default void partition(double[] a, int left, int right, int ka, int kb,
+ boolean leftInner, boolean rightInner) {
+ // Skip when [left, right] does not overlap [ka, kb]
+ if (right - left < 1) {
+ return;
+ }
+ // Assume: left <= right && ka <= kb
+ // Ranges may overlap either way:
+ // left ---------------------- right
+ // ka --- kb
+ //
+ // Requires full sort:
+ // ka ------------------------- kb
+ // left ---- right
+ //
+ // This will naturally perform a full sort when ka < left and kb > right
+
+ // Edge case for a single point
+ if (ka == right) {
+ selectMax(a, left, ka);
+ } else if (kb == left) {
+ selectMin(a, kb, right);
+ } else {
+ final int[] upper = {0};
+ final int k0 = partition(a, left, right, upper, leftInner, rightInner);
+ final int k1 = upper[0];
+ // Sorted in [k0, k1]
+ // Unsorted in [left, k0) and (k1, right]
+ if (ka < k0) {
+ partition(a, left, k0 - 1, ka, kb, leftInner, true);
+ }
+ if (kb > k1) {
+ partition(a, k1 + 1, right, ka, kb, true, rightInner);
+ }
+ }
+ }
+
+ @Override
+ default void partitionSequential(double[] a, int left, int right, int ka, int kb,
+ boolean leftInner, boolean rightInner, PivotStore pivots) {
+ // This method is a copy of the above method except:
+ // - It records all sorted ranges to the cache
+ // - It switches to the above method when the cache is not required
+ if (right - left < 1) {
+ return;
+ }
+ if (ka == right) {
+ selectMax(a, left, ka);
+ pivots.add(ka);
+ } else if (kb == left) {
+ selectMin(a, kb, right);
+ pivots.add(kb);
+ } else {
+ final int[] upper = {0};
+ final int k0 = partition(a, left, right, upper, leftInner, rightInner);
+ final int k1 = upper[0];
+ // Sorted in [k0, k1]
+ // Unsorted in [left, k0) and (k1, right]
+ pivots.add(k0, k1);
+
+ if (ka < k0) {
+ if (k0 - 1 < kb) {
+ // Left branch entirely below kb - no cache required
+ partition(a, left, k0 - 1, ka, kb, leftInner, true);
+ } else {
+ partitionSequential(a, left, k0 - 1, ka, kb, leftInner, true, pivots);
+ }
+ }
+ if (kb > k1) {
+ partitionSequential(a, k1 + 1, right, ka, kb, true, rightInner, pivots);
+ }
+ }
+ }
+
+ @Override
+ default void partition(double[] a, int left, int right, int ka, int kb,
+ boolean leftInner, boolean rightInner, PivotStore pivots) {
+ // This method is a copy of the above method except:
+ // - It records all sorted ranges to the cache
+ // - It switches to the above method when the cache is not required
+ if (right - left < 1) {
+ return;
+ }
+ if (ka == right) {
+ selectMax(a, left, ka);
+ pivots.add(ka);
+ } else if (kb == left) {
+ selectMin(a, kb, right);
+ pivots.add(kb);
+ } else {
+ final int[] upper = {0};
+ final int k0 = partition(a, left, right, upper, leftInner, rightInner);
+ final int k1 = upper[0];
+ // Sorted in [k0, k1]
+ // Unsorted in [left, k0) and (k1, right]
+ pivots.add(k0, k1);
+
+ if (ka < k0) {
+ partition(a, left, k0 - 1, ka, kb, leftInner, true, pivots);
+ }
+ if (kb > k1) {
+ partition(a, k1 + 1, right, ka, kb, true, rightInner, pivots);
+ }
+ }
+ }
+
+ @Override
+ default void sort(double[] a, int left, int right, boolean leftInner, boolean rightInner) {
+ // Skip when [left, right] is sorted
+ // Note: This has no insertion sort for small lengths (so is less performant).
+ // It can be used to test the partition algorithm across the entire data.
+ if (right - left < 1) {
+ return;
+ }
+ final int[] upper = {0};
+ final int k0 = partition(a, left, right, upper, leftInner, rightInner);
+ final int k1 = upper[0];
+ // Sorted in [k0, k1]
+ // Unsorted in [left, k0) and (k1, right]
+ sort(a, left, k0 - 1, leftInner, true);
+ sort(a, k1 + 1, right, true, rightInner);
+ }
+ }
+
+ /**
+ * Single-pivot partition method handling equal values.
+ */
+ @FunctionalInterface
+ interface SPEPartition {
+ /**
+ * Partition an array slice around a single pivot. Partitioning exchanges array
+ * elements such that all elements smaller than pivot are before it and all
+ * elements larger than pivot are after it.
+ *
+ * {@code
+ * |k0 k1|
+ * |
+ *
+ *
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param upper Upper bound (inclusive) of the pivot range [k1].
+ * @param pivot Pivot location.
+ * @return Lower bound (inclusive) of the pivot range [k0].
+ */
+ int partition(double[] a, int left, int right, int pivot, int[] upper);
+ }
+
+ /**
+ * Dual-pivot partition method handling equal values.
+ */
+ @FunctionalInterface
+ interface DPPartition {
+ /**
+ * Partition an array slice around two pivots. Partitioning exchanges array
+ * elements such that all elements smaller than pivot are before it and all
+ * elements larger than pivot are after it.
+ *
+ * {@code
+ * |k0 k1| |k2 k3|
+ * |
+ *
+ *
+ *
+ * {@code
+ * |l |s |p0 p1| e| r|
+ * | ??? |
+ *
+ * {@code
+ * |l |k0 k1| r|
+ * |
+ *
+ *
+ *
+ * @param a Data array.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param start Start of the partition range (inclusive).
+ * @param end End of the partitioned range (inclusive).
+ * @param pivot0 Lower pivot location (inclusive).
+ * @param pivot1 Upper pivot location (inclusive).
+ * @param upper Upper bound (inclusive) of the pivot range [k1].
+ * @return Lower bound (inclusive) of the pivot range [k0].
+ */
+ int partition(double[] a, int left, int right, int start, int end,
+ int pivot0, int pivot1, int[] upper);
+ }
+
+ /**
+ * Function to map the distance from the edge of a range {@code [l, r]} to a smaller
+ * range. The mapping is used in the quickselect adaptive method to adapt {@code k}
+ * based on the position in the sample: {@code kf'/|A|}; k is the index to partition;
+ * |A| is the size of the data to partition; f' is the size of the sample.
+ */
+ enum MapDistance {
+ /** Use the median of the new range. */
+ MEDIAN {
+ @Override
+ int mapDistance(int d, int l, int r, int n) {
+ return n >>> 1;
+ }
+ },
+ /** Map the distance using a fraction of the original range: {@code d / (r - l)}. */
+ ADAPT {
+ @Override
+ int mapDistance(int d, int l, int r, int n) {
+ // If distance==r-l this returns n-1
+ return (int) (d * (n - 1.0) / (r - l));
+ }
+ },
+ /** Use the midpoint between the adaption computed by the {@link #MEDIAN} and {@link #ADAPT} methods. */
+ HALF_ADAPT {
+ @Override
+ int mapDistance(int d, int l, int r, int n) {
+ // Half-adaption: compute the full adaption
+ final int x = ADAPT.mapDistance(d, l, r, n);
+ // Compute the median between the x and the middle
+ final int m = n >>> 1;
+ return (m + x) >>> 1;
+ }
+ },
+ /**
+ * Map the distance assuming the distance to the edge is small. This method is
+ * used when the sample has a lower margin (minimum number of elements) in the
+ * original range of {@code 2(d+1)}. That is each element in the sample has at
+ * least 1 element below it in the original range. This occurs for example when
+ * the sample is built using a median-of-3. In this case setting the mapped
+ * distance as {@code d/2} will ensure that the lower margin in the original data
+ * is at least {@code d} and consequently {@code d} is inside the lower margin. It
+ * will generate a bounds error if called with {@code d > 2(r - l)}.
+ */
+ EDGE_ADAPT {
+ @Override
+ int mapDistance(int d, int l, int r, int n) {
+ return d >>> 1;
+ }
+ };
+
+ /**
+ * Map the distance from the edge of {@code [l, r]} to a new distance in {@code [0, n)}.
+ *
+ *
+ *
+ *
+ * {@code
+ * 1: sampling + adaption --> no-sampling + adaption
+ * 2: sampling + adaption --> no-sampling + no-adaption
+ * 3: sampling + adaption --> no-sampling + adaption --> no-sampling + no-adaption
+ * 4: sampling + no-adaption --> no-sampling + no-adaption
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ *
+ *
+ *
+ * {@code
+ * [L, R] PivotCache
+ * [3] -> [3] -
+ *
+ * // min separation 0
+ * [3, 4, 5] -> [~3, ~4, ~5] [4, 5]
+ * [3, 4, 7, 8] -> [~3, ~4, ~7, ~8] [4, 8]
+ *
+ * // min separation 1
+ * [3, 4, 5] -> [3, 5, MIN_VALUE] -
+ * [3, 4, 5, 8] -> [3, 5, ~8, MIN_VALUE] [8]
+ * [3, 4, 5, 6, 7, 8] -> [3, 8, MIN_VALUE, ...] -
+ * [3, 4, 7, 8] -> [3, 4, 7, 8] [7, 8]
+ * [3, 4, 7, 8, 99] -> [3, 4, 7, 8, ~99] [7, 99]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ *
+ * b confidence (%)
+ * 2 76.56
+ * 3 92.92
+ * 4 97.83
+ * 5 99.33
+ * 6 99.79
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < ka] <= data[ka] <= data[kb] <= data[kb < i]
+ * }
+ *
+ *
+ * b confidence (%)
+ * 2 76.56
+ * 3 92.92
+ * 4 97.83
+ * 5 99.33
+ * 6 99.79
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * left <= k[ia] <= k[ib] <= right : ia <= ib
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * left <= ka <= kb <= right
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * left <= ka <= kb <= right
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ *
+ * Floyd and Rivest (1975)
+ * Algorithm 489: The Algorithm SELECT—for Finding the ith Smallest of n elements.
+ * Comm. ACM. 18 (3): 173.
+ *
+ *
+ * @param a Values.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param k Key of interest.
+ * @param flags Control behaviour.
+ */
+ private void selectFR(double[] a, int left, int right, int k, int flags) {
+ int l = left;
+ int r = right;
+ while (true) {
+ // The following edgeselect modifications are additions to the
+ // FR algorithm. These have been added for testing and only affect the finishing
+ // selection of small lengths.
+
+ // It is possible to use edgeselect when k is close to the end
+ // |l|-----|ka|--------|kb|------|r|
+ // ---------s2----------
+ // ----------s4-----------
+ if (Math.min(k - l, r - k) < edgeSelectConstant) {
+ edgeSelection.partition(a, l, r, k, k);
+ return;
+ }
+
+ // use SELECT recursively on a sample of size S to get an estimate for the
+ // (k-l+1)-th smallest element into a[k], biased slightly so that the (k-l+1)-th
+ // element is expected to lie in the smaller set after partitioning.
+ int pivot = k;
+ int p = l;
+ int q = r;
+ // length - 1
+ int n = r - l;
+ if (n > 600) {
+ ++n;
+ final int ith = k - l + 1;
+ final double z = Math.log(n);
+ final double s = 0.5 * Math.exp(0.6666666666666666 * z);
+ final double sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * Integer.signum(ith - (n >> 1));
+ final int ll = Math.max(l, (int) (k - ith * s / n + sd));
+ final int rr = Math.min(r, (int) (k + (n - ith) * s / n + sd));
+ // Optional: sample [l, r] into [l, rs]
+ if ((flags & FLAG_SUBSET_SAMPLING) != 0) {
+ // Create a random sample at the left end.
+ // This creates an unbiased random sample.
+ // This method is not as fast as sampling into [ll, rr] (see below).
+ final IntUnaryOperator rng = createRNG(n, k);
+ final int rs = l + rr - ll;
+ for (int i = l - 1; i < rs;) {
+ // r - rand [0, r - i + 1) : i is currently i-1
+ final int j = r - rng.applyAsInt(r - i);
+ final double t = a[++i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ selectFR(a, l, rs, k - ll + l, flags);
+ // Current:
+ // |l |k-ll+l| rs| r|
+ // | < v | v | > v | ??? |
+ // Move partitioned data
+ // |l |p |k| q| r|
+ // | < v | ??? |v| ??? | > v |
+ p = k - ll + l;
+ q = r - rs + p;
+ vectorSwap(a, p + 1, rs, r);
+ vectorSwap(a, p, p, k);
+ } else {
+ // Note: Random sampling is a redundant overhead on fully random data
+ // and will part destroy sorted data. On data that is: partially partitioned;
+ // has many repeat elements; or is structured with repeat patterns, the
+ // shuffle removes side-effects of patterns and stabilises performance.
+ if ((flags & FLAG_RANDOM_SAMPLING) != 0) {
+ // This is not a random sample from [l, r] when k is not exactly
+ // in the middle. By sampling either side of k the sample
+ // will maintain the value of k if the data is already partitioned
+ // around k. However sorted data will be part scrambled by the shuffle.
+ // This sampling has the best performance overall across datasets.
+ final IntUnaryOperator rng = createRNG(n, k);
+ // Shuffle [ll, k) from [l, k)
+ if (ll > l) {
+ for (int i = k; i > ll;) {
+ // l + rand [0, i - l + 1) : i is currently i+1
+ final int j = l + rng.applyAsInt(i - l);
+ final double t = a[--i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ // Shuffle (k, rr] from (k, r]
+ if (rr < r) {
+ for (int i = k; i < rr;) {
+ // r - rand [0, r - i + 1) : i is currently i-1
+ final int j = r - rng.applyAsInt(r - i);
+ final double t = a[++i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+ }
+ }
+ selectFR(a, ll, rr, k, flags);
+ // Current:
+ // |l |ll |k| rr| r|
+ // | ??? | < v |v| > v | ??? |
+ // Optional: move partitioned data
+ // Unlikely to make a difference as the partitioning will skip
+ // over {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ *
+ *
+ *
+ * @param x Values.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param k Key of interest.
+ * @param bounds Inclusive bounds {@code [k-, k+]} containing {@code k}.
+ * @param rng Random generator for samples in {@code [0, n)}.
+ */
+ private void selectKFR(double[] x, int left, int right, int k, int[] bounds,
+ IntUnaryOperator rng) {
+ int l = left;
+ int r = right;
+ while (true) {
+ // The following edgeselect modifications are additions to the
+ // KFR algorithm. These have been added for testing and only affect the finishing
+ // selection of small lengths.
+
+ // It is possible to use edgeselect when k is close to the end
+ // |l|-----|ka|--------|kb|------|r|
+ // ---------s2----------
+ // ----------s4-----------
+ if (Math.min(k - l, r - k) < edgeSelectConstant) {
+ edgeSelection.partition(x, l, r, k, k);
+ bounds[0] = bounds[1] = k;
+ return;
+ }
+
+ // length - 1
+ int n = r - l;
+ if (n < 600) {
+ // Switch to quickselect
+ final int p0 = partitionKBM(x, l, r,
+ pivotingStrategy.pivotIndex(x, l, r, k), bounds);
+ final int p1 = bounds[0];
+ if (k < p0) {
+ // The element is in the left partition
+ r = p0 - 1;
+ } else if (k > p1) {
+ // The element is in the right partition
+ l = p1 + 1;
+ } else {
+ // The range contains the element we wanted.
+ bounds[0] = p0;
+ bounds[1] = p1;
+ return;
+ }
+ continue;
+ }
+
+ // Floyd-Rivest sub-sampling
+ ++n;
+ // Step 1: Choose sample size s <= n-1 and gap g > 0
+ final double z = Math.log(n);
+ // sample size = alpha * n^(2/3) * ln(n)^1/3 (4.1)
+ // sample size = alpha * n^(2/3) (4.17; original Floyd-Rivest size)
+ final double s = 0.5 * Math.exp(0.6666666666666666 * z) * Math.cbrt(z);
+ //final double s = 0.5 * Math.exp(0.6666666666666666 * z);
+ // gap = sqrt(beta * s * ln(n))
+ final double g = Math.sqrt(0.25 * s * z);
+ final int rs = (int) (l + s - 1);
+ // Step 2: Sample selection
+ // Convenient to place the random sample in [l, rs]
+ if (rng == null) {
+ rng = createRNG(n, k);
+ }
+ for (int i = l - 1; i < rs;) {
+ // r - rand [0, r - i + 1) : i is currently i-1
+ final int j = r - rng.applyAsInt(r - i);
+ final double t = x[++i];
+ x[i] = x[j];
+ x[j] = t;
+ }
+
+ // Step 3: pivot selection
+ final double isn = (k - l + 1) * s / n;
+ final int ku = (int) Math.max(Math.floor(l - 1 + isn - g), l);
+ final int kv = (int) Math.min(Math.ceil(l - 1 + isn + g), rs);
+ // Find u and v by recursion
+ selectKFR(x, l, rs, ku, bounds, rng);
+ final int kum = bounds[0];
+ int kup = bounds[1];
+ int kvm;
+ int kvp;
+ if (kup >= kv) {
+ kvm = kv;
+ kvp = kup;
+ kup = kv - 1;
+ // u == v will use single-pivot ternary partitioning
+ } else {
+ selectKFR(x, kup + 1, rs, kv, bounds, rng);
+ kvm = bounds[0];
+ kvp = bounds[1];
+ }
+
+ // Step 4: Partitioning
+ final double u = x[kup];
+ final double v = x[kvm];
+ // |l |ku- ku+| |kv- kv+| rs| r| (6.4)
+ // | x < u | x = u | u < x < v | x = v | x > v | ??? |
+ final int ll = kum;
+ int pp = kup;
+ final int rr = r - rs + kvp;
+ int qq = rr - kvp + kvm;
+ vectorSwap(x, kvp + 1, rs, r);
+ vectorSwap(x, kvm, kvp, rr);
+ // |l |ll pp| |kv- |qq rr| r| (6.5)
+ // | x < u | x = u | u < x < v | ??? | x = v | x > v |
+
+ int a;
+ int b;
+ int c;
+ int d;
+
+ // Note: The quintary partitioning is as specified in Kiwiel.
+ // Moving each branch to methods had no effect on performance.
+
+ if (u == v) {
+ // Can be optimised by omitting step A1 (moving of sentinels). Here the
+ // size of ??? is large and initialisation is insignificant.
+ a = partitionKBM(x, ll, rr, pp, bounds);
+ d = bounds[0];
+ // Make ternary and quintary partitioning compatible
+ b = d + 1;
+ c = a - 1;
+ } else if (k < (r + l) >>> 1) {
+ // Left k: u < x[k] < v --> expects x > v.
+ // Quintary partitioning using the six-part array:
+ // |ll pp| p| |i j| |q rr| (6.6)
+ // | x = u | u < x < v | x < u | ??? | x > v | x = v |
+ //
+ // |ll pp| p| j|i |q rr| (6.7)
+ // | x = u | u < x < v | x < u | x > v | x = v |
+ //
+ // Swap the second and third part:
+ // |ll pp| |b c|i |q rr| (6.8)
+ // | x = u | x < u | u < x < v | x > v | x = v |
+ //
+ // Swap the extreme parts with their neighbours:
+ // |ll |a |b c| d| rr| (6.9)
+ // | x < u | x = u | u < x < v | x = v | x > v |
+ int p = kvm - 1;
+ int q = qq;
+ int i = p;
+ int j = q;
+ for (;;) {
+ while (x[++i] < v) {
+ if (x[i] < u) {
+ continue;
+ }
+ // u <= xi < v
+ final double xi = x[i];
+ x[i] = x[++p];
+ if (xi > u) {
+ x[p] = xi;
+ } else {
+ x[p] = x[++pp];
+ x[pp] = xi;
+ }
+ }
+ while (x[--j] >= v) {
+ if (x[j] == v) {
+ final double xj = x[j];
+ x[j] = x[--q];
+ x[q] = xj;
+ }
+ }
+ // Here x[j] < v <= x[i]
+ if (i >= j) {
+ break;
+ }
+ //swap(x, i, j)
+ final double xi = x[j];
+ final double xj = x[i];
+ x[i] = xi;
+ x[j] = xj;
+ if (xi > u) {
+ x[i] = x[++p];
+ x[p] = xi;
+ } else if (xi == u) {
+ x[i] = x[++p];
+ x[p] = x[++pp];
+ x[pp] = xi;
+ }
+ if (xj == v) {
+ x[j] = x[--q];
+ x[q] = xj;
+ }
+ }
+ a = ll + i - p - 1;
+ b = a + pp + 1 - ll;
+ d = rr - q + 1 + j;
+ c = d - rr + q - 1;
+ vectorSwap(x, pp + 1, p, j);
+ //vectorSwap(x, ll, pp, b - 1);
+ //vectorSwap(x, i, q - 1, rr);
+ vectorSwapL(x, ll, pp, b - 1, u);
+ vectorSwapR(x, i, q - 1, rr, v);
+ } else {
+ // Right k: u < x[k] < v --> expects x < u.
+ // Symmetric quintary partitioning replacing 6.6-6.8 with:
+ // |ll p| |i j| |q |qq rr| (6.10)
+ // | x = u | x < u | ??? | x > v | u < x < v | x = v |
+ //
+ // |ll p| j|i |q |qq rr| (6.11)
+ // | x = u | x < u | x > v | u < x < v | x = v |
+ //
+ // |ll p| j|b c| |qq rr| (6.12)
+ // | x = u | x < u | u < x < v | x > v | x = v |
+ //
+ // |ll |a |b c| d| rr| (6.9)
+ // | x < u | x = u | u < x < v | x = v | x > v |
+ int p = pp;
+ int q = qq - kvm + kup + 1;
+ int i = p;
+ int j = q;
+ vectorSwap(x, pp + 1, kvm - 1, qq - 1);
+ for (;;) {
+ while (x[++i] <= u) {
+ if (x[i] == u) {
+ final double xi = x[i];
+ x[i] = x[++p];
+ x[p] = xi;
+ }
+ }
+ while (x[--j] > u) {
+ if (x[j] > v) {
+ continue;
+ }
+ // u < xj <= v
+ final double xj = x[j];
+ x[j] = x[--q];
+ if (xj < v) {
+ x[q] = xj;
+ } else {
+ x[q] = x[--qq];
+ x[qq] = xj;
+ }
+ }
+ // Here x[j] < v <= x[i]
+ if (i >= j) {
+ break;
+ }
+ //swap(x, i, j)
+ final double xi = x[j];
+ final double xj = x[i];
+ x[i] = xi;
+ x[j] = xj;
+ if (xi == u) {
+ x[i] = x[++p];
+ x[p] = xi;
+ }
+ if (xj < v) {
+ x[j] = x[--q];
+ x[q] = xj;
+ } else if (xj == v) {
+ x[j] = x[--q];
+ x[q] = x[--qq];
+ x[qq] = xj;
+ }
+ }
+ a = ll + i - p - 1;
+ b = a + p + 1 - ll;
+ d = rr - q + 1 + j;
+ c = d - rr + qq - 1;
+ vectorSwap(x, i, q - 1, qq - 1);
+ //vectorSwap(x, ll, p, j);
+ //vectorSwap(x, c + 1, qq - 1, rr);
+ vectorSwapL(x, ll, p, j, u);
+ vectorSwapR(x, c + 1, qq - 1, rr, v);
+ }
+
+ // Step 5/6/7: Stopping test, reduction and recursion
+ // |l |a |b c| d| r|
+ // | x < u | x = u | u < x < v | x = v | x > v |
+ if (a <= k) {
+ l = b;
+ }
+ if (c < k) {
+ l = d + 1;
+ }
+ if (k <= d) {
+ r = c;
+ }
+ if (k < b) {
+ r = a - 1;
+ }
+ if (l >= r) {
+ if (l == r) {
+ // [b, c]
+ bounds[0] = bounds[1] = k;
+ } else {
+ // l > r
+ bounds[0] = r + 1;
+ bounds[1] = l - 1;
+ }
+ return;
+ }
+ }
+ }
+
+ /**
+ * Vector swap x[a:b] <-> x[b+1:c] means the first m = min(b+1-a, c-b)
+ * elements of the array x[a:c] are exchanged with its last m elements.
+ *
+ * @param x Array.
+ * @param a Index.
+ * @param b Index.
+ * @param c Index.
+ */
+ private static void vectorSwap(double[] x, int a, int b, int c) {
+ for (int i = a - 1, j = c + 1, m = Math.min(b + 1 - a, c - b); --m >= 0;) {
+ final double v = x[++i];
+ x[i] = x[--j];
+ x[j] = v;
+ }
+ }
+
+ /**
+ * Vector swap x[a:b] <-> x[b+1:c] means the first m = min(b+1-a, c-b)
+ * elements of the array x[a:c] are exchanged with its last m elements.
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < ka] <= data[ka] <= data[kb] <= data[kb < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < ka] <= data[ka] <= data[kb] <= data[kb < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < ka] <= data[ka] <= data[kb] <= data[kb < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < ka] <= data[ka] <= data[kb] <= data[kb < i]
+ * }
+ *
+ * {@code
+ * |k0 k1| |k2 k3|
+ * |
+ *
+ *
+ *
+ *
+ * {@code
+ * |l |s |p0 p1| e| r|
+ * | ??? |
+ *
+ * {@code
+ * |l |s |p0 p1| e| r|
+ * | ??? |
+ *
+ * {@code
+ * |l |s |p0 p1| e| r|
+ * | ??? |
+ *
+ * {@code
+ * |l |s |p0 p1| e| r|
+ * | ??? |
+ *
+ *
+ * a[i] <= k : left <= i <= right, or (left - 1)
+ *
+ *
+ *
+ *
+ *
+ * {@code
+ * int i = Arrays.binarySearch(a, left, right + 1, k);
+ * if (i < 0) {
+ * i = ~i - 1;
+ * }
+ * }
+ *
+ * {@code
+ * int[] keys = ...
+ * // [i0, i1] contains all keys
+ * int i0 = 0;
+ * int i1 = keys.length - 1;
+ * // Update: [i0, i1] contains all keys <= k
+ * i1 = searchLessOrEqual(keys, i0, i1, k);
+ * }
+ *
+ * @param a Data.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param k Key.
+ * @return largest index {@code i} such that {@code a[i] <= k}, or {@code left - 1} if no
+ * such index exists
+ */
+ static int searchLessOrEqual(int[] a, int left, int right, int k) {
+ int l = left;
+ int r = right;
+ while (l <= r) {
+ // Middle value
+ final int m = (l + r) >>> 1;
+ final int v = a[m];
+ // Test:
+ // l------m------r
+ // v k update left
+ // k v update right
+
+ // Full binary search
+ // Run time is up to log2(n) (fast exit on a match) but has more comparisons
+ if (v < k) {
+ l = m + 1;
+ } else if (v > k) {
+ r = m - 1;
+ } else {
+ // Equal
+ return m;
+ }
+
+ // Modified search that does not expect a match
+ // Run time is log2(n). Benchmarks as the same speed.
+ //if (v > k) {
+ // r = m - 1;
+ //} else {
+ // l = m + 1;
+ //}
+ }
+ // Return largest known value below:
+ // r is always moved downward when a middle index value is too high
+ return r;
+ }
+
+ /**
+ * Search the data for the smallest index {@code i} where {@code a[i]} is
+ * greater-than-or-equal to the {@code key}; else return {@code right + 1}.
+ *
+ * a[i] >= k : left <= i <= right, or (right + 1)
+ *
+ *
+ *
+ *
+ *
+ * {@code
+ * int i = Arrays.binarySearch(a, left, right + 1, k);
+ * if (i < 0) {
+ * i = ~i;
+ * }
+ * }
+ *
+ * {@code
+ * int[] keys = ...
+ * // [i0, i1] contains all keys
+ * int i0 = 0;
+ * int i1 = keys.length - 1;
+ * // Update: [i0, i1] contains all keys >= k
+ * i0 = searchGreaterOrEqual(keys, i0, i1, k);
+ * }
+ *
+ * @param a Data.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param k Key.
+ * @return largest index {@code i} such that {@code a[i] >= k}, or {@code right + 1} if no
+ * such index exists
+ */
+ static int searchGreaterOrEqual(int[] a, int left, int right, int k) {
+ int l = left;
+ int r = right;
+ while (l <= r) {
+ // Middle value
+ final int m = (l + r) >>> 1;
+ final int v = a[m];
+ // Test:
+ // l------m------r
+ // v k update left
+ // k v update right
+
+ // Full binary search
+ // Run time is up to log2(n) (fast exit on a match) but has more comparisons
+ if (v < k) {
+ l = m + 1;
+ } else if (v > k) {
+ r = m - 1;
+ } else {
+ // Equal
+ return m;
+ }
+
+ // Modified search that does not expect a match
+ // Run time is log2(n). Benchmarks as the same speed.
+ //if (v < k) {
+ // l = m + 1;
+ //} else {
+ // r = m - 1;
+ //}
+ }
+ // Smallest known value above
+ // l is always moved upward when a middle index value is too low
+ return l;
+ }
+
+ /**
+ * Creates the source of random numbers in {@code [0, n)}.
+ * This is configurable via the control flags.
+ *
+ * @param n Data length.
+ * @param k Target index.
+ * @return the RNG
+ */
+ private IntUnaryOperator createRNG(int n, int k) {
+ // Configurable
+ if ((controlFlags & FLAG_MSWS) != 0) {
+ // Middle-Square Weyl Sequence is fastest int generator
+ final UniformRandomProvider rng = RandomSource.MSWS.create(n * 31L + k);
+ if ((controlFlags & FLAG_BIASED_RANDOM) != 0) {
+ // result = i * [0, 2^32) / 2^32
+ return i -> (int) ((i * Integer.toUnsignedLong(rng.nextInt())) >>> Integer.SIZE);
+ }
+ return rng::nextInt;
+ }
+ if ((controlFlags & FLAG_SPLITTABLE_RANDOM) != 0) {
+ final SplittableRandom rng = new SplittableRandom(n * 31L + k);
+ if ((controlFlags & FLAG_BIASED_RANDOM) != 0) {
+ // result = i * [0, 2^32) / 2^32
+ return i -> (int) ((i * Integer.toUnsignedLong(rng.nextInt())) >>> Integer.SIZE);
+ }
+ return rng::nextInt;
+ }
+ return createFastRNG(n, k);
+ }
+
+ /**
+ * Creates the source of random numbers in {@code [0, n)}.
+ *
+ *
+ *
+ *
+ * @param name Name.
+ * @param prefix Method prefix.
+ * @param qs Minimum quickselect size (if non-zero).
+ * @return the {@link KthSelector} instance
+ */
+ static KthSelector createKthSelector(String name, String prefix, int qs) {
+ final String[] s = {name};
+ final PivotingStrategy sp = getEnumOrElse(s, PivotingStrategy.class, Partition.PIVOTING_STRATEGY);
+ final int minQuickSelectSize = qs != 0 ? qs : getMinQuickSelectSize(s);
+ // Check for unharvested parameters
+ for (int i = prefix.length(); i < s[0].length(); i++) {
+ if (s[0].charAt(i) != '_') {
+ throw new IllegalStateException(
+ String.format("Unharvested KthSelector parameters: %s -> %s", name, s[0]));
+ }
+ }
+ return new KthSelector(sp, minQuickSelectSize);
+ }
+
+ /**
+ * Creates the {@link Partition}. Parameters are derived from the {@code name}.
+ *
+ *
+ *
+ *
+ * @param name Name.
+ * @param prefix Method prefix.
+ * @param qs Minimum quickselect size (if non-zero).
+ * @param ec Minimum edgeselect constant (if non-zero); also used for linear sort select size.
+ * @return the {@link Partition} instance
+ */
+ static Partition createPartition(String name, String prefix, int qs, int ec) {
+ if (!name.startsWith(prefix)) {
+ throw new IllegalArgumentException("Invalid prefix: " + prefix + " for " + name);
+ }
+ final String[] s = {name.substring(prefix.length())};
+ final PivotingStrategy sp = getEnumOrElse(s, PivotingStrategy.class, Partition.PIVOTING_STRATEGY);
+ final DualPivotingStrategy dp = getEnumOrElse(s, DualPivotingStrategy.class, Partition.DUAL_PIVOTING_STRATEGY);
+ final int minQuickSelectSize = qs != 0 ? qs : getMinQuickSelectSize(s);
+ final int edgeSelectConstant = ec != 0 ? ec : getEdgeSelectConstant(s);
+ final int linearSortSelectConstant = ec != 0 ? ec : getLinearSortSelectConstant(s);
+ final int subSamplingSize = getSubSamplingSize(s);
+ final KeyStrategy keyStrategy = getEnumOrElse(s, KeyStrategy.class, Partition.KEY_STRATEGY);
+ final PairedKeyStrategy pairedKeyStrategy =
+ getEnumOrElse(s, PairedKeyStrategy.class, Partition.PAIRED_KEY_STRATEGY);
+ final double recursionMultiple = getRecursionMultiple(s);
+ final int recursionConstant = getRecursionConstant(s);
+ final int compressionLevel = getCompressionLevel(s);
+ final int controlFlags = getControlFlags(s);
+ final SPStrategy spStrategy = getEnumOrElse(s, SPStrategy.class, Partition.SP_STRATEGY);
+ final ExpandStrategy expandStrategy = getEnumOrElse(s, ExpandStrategy.class, Partition.EXPAND_STRATEGY);
+ final LinearStrategy linearStrategy = getEnumOrElse(s, LinearStrategy.class, Partition.LINEAR_STRATEGY);
+ final EdgeSelectStrategy esStrategy = getEnumOrElse(s, EdgeSelectStrategy.class, Partition.EDGE_STRATEGY);
+ final StopperStrategy stopStrategy = getEnumOrElse(s, StopperStrategy.class, Partition.STOPPER_STRATEGY);
+ final AdaptMode adaptMode = getEnumOrElse(s, AdaptMode.class, Partition.ADAPT_MODE);
+ // Check for unharvested parameters
+ for (int i = s[0].length(); --i >= 0;) {
+ if (s[0].charAt(i) != '_') {
+ throw new IllegalStateException(
+ String.format("Unharvested Partition parameters: %s -> %s", name, prefix + s[0]));
+ }
+ }
+ final Partition p = new Partition(sp, dp, minQuickSelectSize,
+ edgeSelectConstant, subSamplingSize);
+ // Some values do not have to be final as they are not used within optimised
+ // partitioning code.
+ p.setKeyStrategy(keyStrategy);
+ p.setPairedKeyStrategy(pairedKeyStrategy);
+ p.setRecursionMultiple(recursionMultiple);
+ p.setRecursionConstant(recursionConstant);
+ p.setCompression(compressionLevel);
+ p.setControlFlags(controlFlags);
+ p.setSPStrategy(spStrategy);
+ p.setExpandStrategy(expandStrategy);
+ p.setLinearStrategy(linearStrategy);
+ p.setEdgeSelectStrategy(esStrategy);
+ p.setStopperStrategy(stopStrategy);
+ p.setLinearSortSelectSize(linearSortSelectConstant);
+ p.setAdaptMode(adaptMode);
+
+ return p;
+ }
+
+ /**
+ * Gets the minimum size for the recursive quickselect partition algorithm.
+ * Below this size the algorithm will change strategy for partitioning,
+ * e.g. change to a full sort.
+ *
+ * @param name Algorithm name (updated in-place to remove the parameter).
+ * @return the minimum quickselect size
+ */
+ static int getMinQuickSelectSize(String[] name) {
+ final Matcher m = QS_PATTERN.matcher(name[0]);
+ if (m.find()) {
+ final int i = Integer.parseInt(name[0], m.start(1), m.end(1), 10);
+ name[0] = name[0].substring(0, m.start()) + name[0].substring(m.end(), name[0].length());
+ return i;
+ }
+ return Partition.MIN_QUICKSELECT_SIZE;
+ }
+
+ /**
+ * Gets the constant for the edgeselect distance-from-end computation.
+ *
+ * @param name Algorithm name (updated in-place to remove the parameter).
+ * @return the edgeselect constant
+ */
+ static int getEdgeSelectConstant(String[] name) {
+ final Matcher m = EC_PATTERN.matcher(name[0]);
+ if (m.find()) {
+ final int i = Integer.parseInt(name[0], m.start(1), m.end(1), 10);
+ name[0] = name[0].substring(0, m.start()) + name[0].substring(m.end(), name[0].length());
+ return i;
+ }
+ return Partition.EDGESELECT_CONSTANT;
+ }
+
+ /**
+ * Gets the constant for the sortselect distance-from-end computation for linearselect.
+ *
+ * @param name Algorithm name (updated in-place to remove the parameter).
+ * @return the sortselect constant
+ */
+ static int getLinearSortSelectConstant(String[] name) {
+ final Matcher m = LC_PATTERN.matcher(name[0]);
+ if (m.find()) {
+ final int i = Integer.parseInt(name[0], m.start(1), m.end(1), 10);
+ name[0] = name[0].substring(0, m.start()) + name[0].substring(m.end(), name[0].length());
+ return i;
+ }
+ return Partition.LINEAR_SORTSELECT_SIZE;
+ }
+
+ /**
+ * Gets the minimum size for single-pivot sub-sampling (using the Floyd-Rivest algorithm).
+ *
+ * @param name Algorithm name (updated in-place to remove the parameter).
+ * @return the sub-sampling size
+ */
+ static int getSubSamplingSize(String[] name) {
+ final Matcher m = SU_PATTERN.matcher(name[0]);
+ if (m.find()) {
+ final int i = Integer.parseInt(name[0], m.start(1), m.end(1), 10);
+ name[0] = name[0].substring(0, m.start()) + name[0].substring(m.end(), name[0].length());
+ return i;
+ }
+ return Partition.SUBSAMPLING_SIZE;
+ }
+
+ /**
+ * Gets the recursion multiplication factor.
+ *
+ * @param name Algorithm name (updated in-place to remove the parameter).
+ * @return the recursion multiple
+ */
+ static double getRecursionMultiple(String[] name) {
+ final Matcher m = RM_PATTERN.matcher(name[0]);
+ if (m.find()) {
+ final double d = Double.parseDouble(m.group(1));
+ name[0] = name[0].substring(0, m.start()) + name[0].substring(m.end(), name[0].length());
+ return d;
+ }
+ return Partition.RECURSION_MULTIPLE;
+ }
+
+ /**
+ * Gets the recursion constant.
+ *
+ * @param name Algorithm name (updated in-place to remove the parameter).
+ * @return the recursion constant
+ */
+ static int getRecursionConstant(String[] name) {
+ final Matcher m = RC_PATTERN.matcher(name[0]);
+ if (m.find()) {
+ final int i = Integer.parseInt(name[0], m.start(1), m.end(1), 10);
+ name[0] = name[0].substring(0, m.start()) + name[0].substring(m.end(), name[0].length());
+ return i;
+ }
+ return Partition.RECURSION_CONSTANT;
+ }
+
+ /**
+ * Gets the compression level for {@link CompressedIndexSet}.
+ *
+ * @param name Algorithm name (updated in-place to remove the parameter).
+ * @return the compression
+ */
+ static int getCompressionLevel(String[] name) {
+ final Matcher m = CL_PATTERN.matcher(name[0]);
+ if (m.find()) {
+ final int i = Integer.parseInt(name[0], m.start(1), m.end(1), 10);
+ name[0] = name[0].substring(0, m.start()) + name[0].substring(m.end(), name[0].length());
+ return i;
+ }
+ return Partition.COMPRESSION_LEVEL;
+ }
+
+ /**
+ * Gets the control flags. These are used to enable additional features, for example
+ * random sampling in the Floyd-Rivest algorithm.
+ *
+ * @param name Algorithm name (updated in-place to remove the parameter).
+ * @return the control flags
+ */
+ static int getControlFlags(String[] name) {
+ return getControlFlags(name, Partition.CONTROL_FLAGS);
+ }
+
+ /**
+ * Gets the control flags. These are used to enable additional features, for example
+ * random sampling in the Floyd-Rivest algorithm.
+ *
+ * @param name Algorithm name (updated in-place to remove the parameter).
+ * @param defaultValue Default value.
+ * @return the control flags
+ */
+ static int getControlFlags(String[] name, int defaultValue) {
+ final Matcher m = CF_PATTERN.matcher(name[0]);
+ if (m.find()) {
+ final int i = Integer.parseInt(name[0], m.start(1), m.end(1), 10);
+ name[0] = name[0].substring(0, m.start()) + name[0].substring(m.end(), name[0].length());
+ return i;
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Gets the option flags. These are used to enable additional features, and can be
+ * used separately to the control flags.
+ *
+ * @param name Algorithm name (updated in-place to remove the parameter).
+ * @return the option flags
+ */
+ static int getOptionFlags(String[] name) {
+ return getOptionFlags(name, Partition.OPTION_FLAGS);
+ }
+
+ /**
+ * Gets the option flags. These are used to enable additional features, and can be
+ * used separately to the control flags.
+ *
+ * @param name Algorithm name (updated in-place to remove the parameter).
+ * @param defaultValue Default value.
+ * @return the option flags
+ */
+ static int getOptionFlags(String[] name, int defaultValue) {
+ final Matcher m = OF_PATTERN.matcher(name[0]);
+ if (m.find()) {
+ final int i = Integer.parseInt(name[0], m.start(1), m.end(1), 10);
+ name[0] = name[0].substring(0, m.start()) + name[0].substring(m.end(), name[0].length());
+ return i;
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Gets the enum from the name. The enum name must be prefixed with an underscore.
+ *
+ * @param {@code
+ * i < p < j
+ * data[i] <= data[p] <= data[j]
+ * }
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Partition:
+ * 0------k1----------k2------k3---------N
+ *
+ * Iteration 1:
+ * 0------k1------p--------p---------p---N
+ *
+ * Iteration 2:
+ * l---k2---r
+ * or: l-----------k2-----------------N
+ *
+ * Iteration 3:
+ * l--k3-----r
+ * or: l-------k3---------N
+ *
+ *
+ * {@code
+ * i < p < j
+ * data[i] <= data[p] <= data[j]
+ * }
+ *
+ * {@code
+ * left <= p <= right
+ * }
+ *
+ *
+ *
+ *
+ * @return the effect
+ */
+ abstract int samplingEffect();
+}
diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/ScanningKeyInterval.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/ScanningKeyInterval.java
new file mode 100644
index 000000000..d5b491652
--- /dev/null
+++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/arrays/ScanningKeyInterval.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.examples.jmh.arrays;
+
+/**
+ * An {@link SearchableInterval} backed by an array of ordered keys. The interval is searched using
+ * a linear scan of the data. The scan start point is chosen from reference points within the data.
+ *
+ *
+ * Partition:
+ * 0------k1---k2-------k3--------------k4-N
+ *
+ * Iteration 1:
+ * 0------ppppppp----p------p--p-------p---N
+ *
+ * Iteration 2:
+ * ------- Partition (p, k3, p)
+ * ss sssssss Sort these regions
+ * -- Partition (p, k4, N)
+ *
+ *
+ * @since 1.2
+ */
+interface ScanningPivotCache extends PivotCache {
+ /**
+ * Move the start (inclusive) of the range of indices supported.
+ *
+ *
+ *
+ *
+ * {@code
+ * l-----------ka-kb----------r
+ * lower <--|
+ * |--> upper
+ *
+ * lower < ka
+ * upper > kb
+ * }
+ *
+ * {@code
+ * upper = nextIndex(kb + 1);
+ * lower = previousIndex(ka - 1);
+ * }
+ *
+ *
+ *
+ *
+ * {@code
+ * lo-----------ka-kb----------hi
+ * lower <--|
+ * |--> upper
+ *
+ * index(lower) < ka
+ * index(upper) > kb
+ * }
+ *
+ * {@code
+ * upper = next(hi, kb + 1);
+ * lower = previous(lo, ka - 1);
+ * }
+ *
+ *
+ *
+ *
+ *
+ * AbstractDataSource s = new AbstractDataSource() {
+ * protected int getLength() {
+ * return 123;
+ * }
+ * };
+ * s.setDistribution(Distribution.SAWTOOTH, Distribution.SHUFFLE);
+ * s.setModification(Modification.REVERSE_FRONT);
+ * s.setRange(2);
+ * s.setup();
+ * for (int i = 0; i < s.size(); i++) {
+ * s.getData(i);
+ * }
+ *
+ *
+ *
+ *
+ * {@code
+ * cut
+ * |
+ * -------k1--------k2---------k3---- ... ---------kn--------
+ * <-- scan previous
+ * scan next -->
+ * }
+ *
+ * {@code
+ * i0 != i1 != i2
+ * data[i0] < data[i1] < data[i2]
+ * }
+ *
+ * {@code
+ * a != b != c
+ * data[a] < data[b] < data[c]
+ * }
+ *
+ * {@code
+ * a != b != c
+ * data[a] < data[b] < data[c]
+ * }
+ *
+ * {@code
+ * i0 != i1 != i2 != i3
+ * data[i0] < data[i1] < data[i2] < data[i3]
+ * }
+ *
+ * {@code
+ * i0 != i1 != i2 != i3 != i4
+ * data[i0] < data[i1] < data[i2] < data[i3] < data[i4]
+ * }
+ *
+ * {@code
+ * i0 != i1 != i2 != i3 != i4
+ * data[i0] < data[i1] < data[i2] < data[i3] < data[i4]
+ * }
+ *
+ * {@code
+ * i0 != i1 != i2 != i3 != i4
+ * data[i0] < data[i1] < data[i2] < data[i3] < data[i4]
+ * }
+ *
+ * {@code
+ * i0 != i1 != i2 != i3 != i4 != i5 != i6
+ * data[i0] < data[i1] < data[i2] < data[i3] < data[i4] < data[i5] < data[i6]
+ * }
+ *
+ * {@code
+ * i0 != i1 != i2 != i3 != i4 != i5 != i6 != i7
+ * data[i0] < data[i1] < data[i2] < data[i3] < data[i4] < data[i5] < data[i6] < data[i7]
+ * }
+ *
+ * {@code
+ * data[i] <= data[i + i] <= data[i + 2] ...
+ * }
+ *
+ * {@code
+ * int[] indices = ...
+ * IndexSet sortUnique(indices);
+ * int min = indices[0];
+ * int max = indices[indices.length - 1]
+ * if (max < 0) {
+ * max = ~max;
+ * }
+ * }
+ *
+ * {@code
+ * int[] indices = ...
+ * IndexSet sortUnique(indices);
+ * int min = indices[0];
+ * int max = indices[indices.length - 1]
+ * if (max < 0) {
+ * max = ~max;
+ * }
+ * }
+ *
+ * {@code
+ * int[] indices = ...
+ * int n sortIndices(indices, indices.length);
+ * int min = indices[0];
+ * int max = indices[n - 1]
+ * }
+ *
+ * {@code
+ * int[] indices = ...
+ * int n sortIndices(indices, indices.length);
+ * int min = indices[0];
+ * int max = indices[n - 1]
+ * }
+ *
+ *
+ *
+ *
+ * {@code
+ * l-----------ka-kb----------r
+ * ra <--| |--> lb
+ *
+ * ra < ka
+ * lb > kb
+ * }
+ *
+ *
+ *
+ *
+ * {@code
+ * l-----------k----------r
+ * |--> l
+ * }
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the new left
+ */
+ int updateLeft(int k);
+
+ /**
+ * Update the interval so {@code right <= k}.
+ *
+ * {@code
+ * l-----------k----------r
+ * r <--|
+ * }
+ *
+ * @param k Index to start checking from (inclusive).
+ * @return the new right
+ */
+ int updateRight(int k);
+
+ /**
+ * Split the interval using two splitting indices. Returns the left interval that occurs
+ * before the specified split index {@code ka}, and updates the current interval left bound
+ * to after the specified split index {@code kb}.
+ *
+ * {@code
+ * l-----------ka-kb----------r
+ * r1 <--| |--> l1
+ *
+ * r1 < ka
+ * l1 > kb
+ * }
+ *
+ * {@code
+ * l-----------ka-kb----------r
+ * r1 <--| |--> l1
+ *
+ * r1 < ka
+ * l1 > kb
+ * }
+ *
+ *
+ * -------ijkl------
+ *
+ *
+ * @param c Compression.
+ * @param left Lower bound of the set of compressed indices.
+ * @param i Index.
+ * @return the lower bound of the index range
+ */
+ private static int getCompressedIndexLow(int c, int left, int i) {
+ return (((i - left) >>> c) << c) + left;
+ }
+
+ /**
+ * Gets the upper bound of the index range covered by the compressed index.
+ * A compressed index covers a range of real indices. For example the
+ * indices i, j, k, and l are all represented by the same compressed index
+ * with a compression level of 2.
+ *
+ * right
+ * |
+ * -------ijkl------
+ *
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * @param a Data array to use to find out the Kth value.
+ * @param left Lower bound (inclusive).
+ * @param right Upper bound (inclusive).
+ * @param ka Lower index to select.
+ * @param kb Upper index to select.
+ */
+ void partition(double[] a, int left, int right, int ka, int kb);
+ }
+
+ /**
+ * Partition function. Used to test different implementations.
+ */
+ private interface DoublePartitionFunction {
+ /**
+ * Partition the array such that indices {@code k} correspond to their correctly
+ * sorted value in the equivalent fully sorted array. For all indices {@code k}
+ * and any index {@code i}:
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * {@code
+ * data[i < k] <= data[k] <= data[k < i]
+ * }
+ *
+ * @param a Values.
+ * @param k Indices.
+ */
+ void partition(double[] a, int... k);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testSortNaN(double[] values) {
+ final double[] sorted = sort(values);
+ final int last = Partition.sortNaN(values);
+ // index of last non-NaN
+ int i = sorted.length;
+ while (--i >= 0) {
+ if (!Double.isNaN(sorted[i])) {
+ break;
+ }
+ }
+ Assertions.assertEquals(i, last);
+ // Check the data is the same
+ Arrays.sort(values);
+ Assertions.assertArrayEquals(sorted, values, "Data destroyed");
+ }
+
+ static Stream{@code
+ * jshell> printSubSamplingSize(0, 1000000, 5000)
+ * 5000 [0, 1000000] (k=0.005 * 1000001) -> [4843, 9843] (k=0.032 * 5001)
+ * 5000 [4843, 9843] (k=0.032 * 5001) -> [4977, 5124] (k=0.162 * 148)
+ *
+ * jshell> printSubSamplingSize(0, 1000000, 500000)
+ * 500000 [0, 1000000] (k=0.500 * 1000001) -> [497631, 502631] (k=0.474 * 5001)
+ * 500000 [497631, 502631] (k=0.474 * 5001) -> [499913, 500059] (k=0.599 * 147)
+ * }
+ *
+ * @param l Left bound (inclusive).
+ * @param r Right bound (inclusive).
+ * @param k Target index.
+ */
+ static void printSubSamplingSize(int l, int r, int k) {
+ int n = r - l;
+ if (n > 600) {
+ // Floyd-Rivest: use SELECT recursively on a sample of size S to get an estimate
+ // for the (k-l+1)-th smallest element into a[k], biased slightly so that the
+ // (k-l+1)-th element is expected to lie in the smaller set after partitioning.
+ ++n;
+ final int i = k - l + 1;
+ final double z = Math.log(n);
+ // s ~ sub-sample size ~ 0.5 * n^2/3
+ final double s = 0.5 * Math.exp(0.6666666666666666 * z);
+ final double sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * Integer.signum(i - (n >> 1));
+ final int ll = Math.max(l, (int) (k - i * s / n + sd));
+ final int rr = Math.min(r, (int) (k + (n - i) * s / n + sd));
+ // CHECKSTYLE: stop regex
+ System.out.printf("%d [%d, %d] (k=%.3f * %d) -> [%d, %d] (k=%.3f * %d)%n",
+ k, l, r, (double) i / n, n, ll, rr, (double) (k - ll + 1) / (rr - ll + 1), rr - ll + 1);
+ // CHECKSTYLE: start regex
+ printSubSamplingSize(ll, rr, k);
+ }
+ }
+
+ /**
+ * Prints the size of the Kiwiel Floyd-Rivest recursive subset samples.
+ * This method is for information purposes. It is intended to be pasted into JShell
+ * and called with various parameters. Example:
+ *
+ * {@code
+ * jshell> printSubSamplingSize(0, 1000000, 5000)
+ * 5000 [0, 1000000] (k=0.005 * 1000001) -> [4843, 9843] (k=0.032 * 5001)
+ * 5000 [4843, 9843] (k=0.032 * 5001) -> [4977, 5124] (k=0.162 * 148)
+ *
+ * jshell> printSubSamplingSize(0, 1000000, 500000)
+ * 500000 [0, 1000000] (k=0.500 * 1000001) -> [497631, 502631] (k=0.474 * 5001)
+ * 500000 [497631, 502631] (k=0.474 * 5001) -> [499913, 500059] (k=0.599 * 147)
+ * }
+ *
+ * @param l Left bound (inclusive).
+ * @param r Right bound (inclusive).
+ * @param k Target index.
+ */
+ static void printKSubSamplingSize(int l, int r, int k) {
+ int n = r - l;
+ if (n > 600) {
+ // Floyd-Rivest sub-sampling
+ ++n;
+ // Step 1: Choose sample size s <= n-1 and gap g > 0
+ final double z = Math.log(n);
+ // sample size = alpha * n^(2/3) * ln(n)^1/3 (4.1)
+ // sample size = alpha * n^(2/3) (4.17; original Floyd-Rivest size)
+ final double s = 0.5 * Math.exp(0.6666666666666666 * z) * Math.cbrt(z);
+ //final double s = 0.5 * Math.exp(0.6666666666666666 * z);
+ // gap = sqrt(beta * s * ln(n))
+ final double g = Math.sqrt(0.25 * s * z);
+ final int rs = (int) (l + s - 1);
+ // Step 3: pivot selection
+ final double isn = (k - l + 1) * s / n;
+ final int ku = (int) Math.max(Math.floor(l - 1 + isn - g), l);
+ final int kv = (int) Math.min(Math.ceil(l - 1 + isn + g), rs);
+ // CHECKSTYLE: stop regex
+ System.out.printf("%d [%d, %d] (k=%.3f * %d) -> [0, %d, %d, %d] (ku=%.3f; kv=%.3f)%n",
+ k, l, r, (double) (k - l + 1) / n, n, ku, kv, rs, (double) (ku + 1) / (rs + 1), (double) (kv + 1) / (rs + 1));
+ // CHECKSTYLE: start regex
+ printKSubSamplingSize(0, rs, ku);
+ printKSubSamplingSize(0, rs, kv);
+ }
+ }
+
+ /**
+ * This is not a test. It runs the introselect algorithm with data that may trigger excess
+ * recursion when using the Floyd-Rivest algorithm. Use of a random sample can avoid
+ * excess recursion when the local data is non-representative of the range to partition.
+ */
+ @ParameterizedTest
+ @MethodSource
+ @Disabled("Used for testing")
+ void testFloydRivestRecursion(int n, int subSamplingSize, PivotingStrategy sp, int controlFlags,
+ PairedKeyStrategy pk, double recursionMultiple, int recursionConstant) {
+ final AbstractDataSource source = new AbstractDataSource() {
+ @Override
+ protected int getLength() {
+ return n;
+ }
+ };
+ source.setRange(0);
+ source.setup();
+ // Target the "median"
+ final int[] k = {source.getLength() >> 1};
+ final Partition p = new Partition(sp, QS, EC, subSamplingSize)
+ .setPairedKeyStrategy(pk)
+ .setRecursionMultiple(recursionMultiple)
+ .setRecursionConstant(recursionConstant);
+ p.setControlFlags(controlFlags);
+ final ArrayList