Trevor Hackman
03/08/2022, 10:53 PMbinarySearchIndexed
With a signature of perhaps
fun <T : Comparable<T>> List<T>.binarySearchIndexed(
fromIndex: Int = 0,
toIndex: Int = size,
comparison: (T, Int) -> Int,
): Int
Thread in Slack ConversationDerek Peirce
03/09/2022, 3:19 AMComparator
?Trevor Hackman
03/09/2022, 4:00 AMDerek Peirce
03/09/2022, 4:45 AMgildor
03/09/2022, 4:58 AMTrevor Hackman
03/09/2022, 6:09 PMDerek Peirce
03/09/2022, 6:11 PMTrevor Hackman
03/09/2022, 6:55 PMDerek Peirce
03/10/2022, 4:39 AMinline fun binarySearch(min: Int = 0, max: Int, comparison: (Int) -> Int) : Int {
var low = min
var high = max
var mid: Int
while(low <= high) {
mid = low + ((high - low) / 2)
val result = comparison(mid)
when {
result > 0 -> low = mid + 1 // element is greater than middle element of array, so it will be in right half of array
result == 0 -> return mid // found the element
result < 0 -> high = mid - 1 // element is less than middle element of array, so it will be in left half of the array.
}
}
return -1
}
fun IntArray.binarySearch(element: Int) = binarySearch(
min = 0,
max = size - 1,
comparison = { index -> element.compareTo(this[index]) }
)
Trevor Hackman
03/11/2022, 5:07 AMTrevor Hackman
03/11/2022, 5:09 AMDerek Peirce
03/11/2022, 5:27 AMbinarySearch
takes a Comparator
argument, which has a specific purpose of comparing two objects which should not depend on their indices. Meanwhile, the other methods you've listed are general-purpose with no specific semantic for what they need to do, so many use cases appear in which the index as an argument is useful.Trevor Hackman
03/12/2022, 5:51 PMDerek Peirce
03/12/2022, 5:53 PMIntArray
for List<T>
is trivial. What does your method look like?Trevor Hackman
03/12/2022, 5:57 PMfun <T : Comparable<T>> List<T>.binarySearchIndexed(
fromIndex: Int = 0,
toIndex: Int = size,
comparison: (T, Int) -> Int,
): Int
Derek Peirce
03/12/2022, 5:57 PMTrevor Hackman
03/12/2022, 6:00 PM/**
* Searches this list or its range for an element for which the given [comparison] function returns zero using the binary search algorithm.
*
* The list is expected to be sorted so that the signs of the [comparison] function's return values ascend on the list elements,
* i.e. negative values come before zero and zeroes come before positive values.
* Otherwise, the result is undefined.
*
* If the list contains multiple elements for which [comparison] returns zero, there is no guarantee which one will be found.
*
* @param comparison function that returns zero when called on the list element being searched.
* On the elements coming before the target element, the function must return negative values;
* on the elements coming after the target element, the function must return positive values.
*
* @return the index of the found element, if it is contained in the list within the specified range;
* otherwise, the inverted insertion point `(-insertion point - 1)`.
* The insertion point is defined as the index at which the element should be inserted,
* so that the list (or the specified subrange of list) still remains sorted.
* @sample samples.collections.Collections.Lists.binarySearchWithComparisonFunction
*/
public fun <T> List<T>.binarySearch(fromIndex: Int = 0, toIndex: Int = size, comparison: (T) -> Int): Int {
rangeCheck(size, fromIndex, toIndex)
var low = fromIndex
var high = toIndex - 1
while (low <= high) {
val mid = (low + high).ushr(1) // safe from overflows
val midVal = get(mid)
val cmp = comparison(midVal)
if (cmp < 0)
low = mid + 1
else if (cmp > 0)
high = mid - 1
else
return mid // key found
}
return -(low + 1) // key not found
}
Derek Peirce
03/12/2022, 6:03 PMcomparison(midVal)
replaced with comparison(mid, midVal)
, correct?Derek Peirce
03/12/2022, 7:05 PMbinarySearch
as a base, is this what you're looking for?
fun <T> List<T>.binarySearchIndexed(
fromIndex: Int = 0,
toIndex: Int = size,
comparison: (T, Int) -> Int,
): Int = binarySearch(
min = fromIndex,
max = toIndex - 1,
comparison = { index -> comparison(get(index), index) }
)
With this example call:
fun main() {
println(listOf<Int>(1, 2, 3, 6, 8, 10, 11, 16, 20).binarySearchIndexed { element, index ->
println("checking $element at $index");
11.compareTo(element)
})
}
which yields:
checking 8 at 4
checking 11 at 6
6
Trevor Hackman
03/12/2022, 7:20 PMTrevor Hackman
03/12/2022, 7:28 PMlist.mapIndexed { index, it -> it to index }.binarySearch { ... }
But you lose log time complexity because you had to do a linear time map.Derek Peirce
03/12/2022, 7:29 PMTrevor Hackman
03/12/2022, 7:39 PMTrevor Hackman
03/12/2022, 7:40 PMDerek Peirce
03/12/2022, 7:54 PMbinarySearchIndexed
is still an obscure enough use case that it doesn't warrant being in stdlib, but the general case binarySearch
with be used by the existing stdlib methods and also used to easily write your own binarySearchIndexed
.