package org.threadly.util; import java.util.List; import java.util.function.Function; /** * A collection of utilities for searching and sorting against collections and other data * providers. * * @since 5.0.0 */ public class SortUtils { protected static final short MAX_STEPS_TILL_B_SEARCH_SWITCH = 5; /** * This function uses the binary search and adds a small amount of logic such that it determines * the placement index for a given item. It is designed to always return the index after any * values which equal the provided insertion value. * * @param values List of values to search over and find desired value * @param insertionValue value in relation to functions provided values to search for insertion point * @param randomAccessList boolean for optimization with binary search * @return the index to insert the key into the list */ public static int getInsertionEndIndex(List<Long> values, long insertionValue, boolean randomAccessList) { return getInsertionEndIndex(values::get, values.size() - 1, insertionValue, randomAccessList); } /** * This function uses the binary search and adds a small amount of logic such that it determines * the placement index for a given value. It is designed to always return the index after any * values which equal the provided insertion value. * * @param valueProvider Function which will provide values for requested indexes * @param absoluteMax maximum index (inclusive) to search within * @param insertionValue value in relation to functions provided values to search for insertion point * @param randomAccessList boolean for optimization with binary search * @return the index to insert the key into the list */ public static int getInsertionEndIndex(Function<Integer, Long> valueProvider, int absoluteMax, long insertionValue, boolean randomAccessList) { int searchResult = binarySearch(valueProvider, absoluteMax, insertionValue, randomAccessList); if (searchResult >= 0) { while (searchResult <= absoluteMax && valueProvider.apply(searchResult) == insertionValue) { searchResult++; } return searchResult; } else { return Math.abs(searchResult) - 1; } } /** * A faster binary search algorithm for sorting a list. This algorithm works by actually * knowing the values and making smart decisions about how far to jump in the list based on * those values. Which is why this can not take in a comparable interface like Collections * does. This was adapted from code posted from this blog post: http://ochafik.com/blog/?p=106 * * @param values List of values to search over and find desired value * @param insertionValue value in relation to functions provided values to search for insertion point * @param randomAccessList {@code true} to optimize for list that have cheap random access * @return index where found, or -(insertion point) - 1 if not found */ public static int binarySearch(List<Long> values, long insertionValue, boolean randomAccessList) { return binarySearch(values::get, values.size() - 1, insertionValue, randomAccessList); } /** * A faster binary search algorithm for sorting a list. This algorithm works by actually * knowing the values and making smart decisions about how far to jump in the list based on * those values. Which is why this can not take in a comparable interface like Collections * does. This was adapted from code posted from this blog post: http://ochafik.com/blog/?p=106 * * @param valueProvider Function which will provide values for requested indexes * @param absoluteMax maximum index (inclusive) to search within * @param insertionValue value in relation to functions provided values to search for insertion point * @param randomAccessList {@code true} to optimize for list that have cheap random access * @return index where found, or -(insertion point) - 1 if not found */ public static int binarySearch(Function<Integer, Long> valueProvider, int absoluteMax, long insertionValue, boolean randomAccessList) { if (absoluteMax < 0) { return -1; } int min = 0; int max = absoluteMax; long minVal = valueProvider.apply(min); long maxVal = valueProvider.apply(max); short nPreviousSteps = 1; while (true) { if (insertionValue <= minVal) { return insertionValue == minVal ? min : -1 - min; } else if (insertionValue >= maxVal) { return insertionValue == maxVal ? max : -2 - max; } int pivot; // A typical binarySearch algorithm uses pivot = (min + max) / 2. // The pivot we use here tries to be smarter and to choose a pivot // close to the expected location of the key. This reduces dramatically // the number of steps needed to get to the key. However, it does not // work well with a logarithmic distribution of values. When the key is // not found quickly the smart way, we switch to the standard pivot. if (nPreviousSteps > MAX_STEPS_TILL_B_SEARCH_SWITCH) { pivot = (min + max) >> 1; // stop increasing nPreviousSteps from now on } else { // We cannot do the following operations in int precision, because there might be overflows. // using a float is better performing than using a long (even on 64bit) pivot = min + (int)((insertionValue - (float)minVal) / (maxVal - (float)minVal) * (max - min)); nPreviousSteps++; } long pivotVal = valueProvider.apply(pivot); if (insertionValue > pivotVal) { min = pivot + 1; if (min > absoluteMax) { return absoluteMax + 1; } minVal = valueProvider.apply(min); if (randomAccessList) { // if cheap to check, we should see what the value is at this point max--; maxVal = valueProvider.apply(max); } } else if (insertionValue < pivotVal) { max = pivot - 1; if (max < 0) { return 0; } maxVal = valueProvider.apply(max); if (randomAccessList) { // if cheap to check, we should see what the value is at this point min++; minVal = valueProvider.apply(min); } } else { return pivot; } } } }