Provide stable sorting when JS engine sorting doesn't look stable

#KT-12473
This commit is contained in:
Ilya Gorbunov
2019-01-10 19:29:53 +03:00
parent 7c3c454654
commit 51eb21d44b
5 changed files with 176 additions and 31 deletions
@@ -1159,8 +1159,7 @@ public actual fun IntArray.sort(): Unit {
* Sorts the array in-place.
*/
public actual fun LongArray.sort(): Unit {
if (size > 1)
sort { a: Long, b: Long -> a.compareTo(b) }
if (size > 1) sort { a: Long, b: Long -> a.compareTo(b) }
}
/**
@@ -1207,16 +1206,14 @@ public actual fun CharArray.sort(): Unit {
* Sorts the array in-place according to the natural order of its elements.
*/
public actual fun <T : Comparable<T>> Array<out T>.sort(): Unit {
if (size > 1)
sort { a: T, b: T -> a.compareTo(b) }
if (size > 1) sortArray(this)
}
/**
* Sorts the array in-place according to the order specified by the given [comparison] function.
*/
@kotlin.internal.InlineOnly
public inline fun <T> Array<out T>.sort(noinline comparison: (a: T, b: T) -> Int): Unit {
asDynamic().sort(comparison)
public fun <T> Array<out T>.sort(comparison: (a: T, b: T) -> Int): Unit {
if (size > 1) sortArrayWith(this, comparison)
}
/**
@@ -1279,8 +1276,7 @@ public inline fun CharArray.sort(noinline comparison: (a: Char, b: Char) -> Int)
* Sorts the array in-place according to the order specified by the given [comparator].
*/
public actual fun <T> Array<out T>.sortWith(comparator: Comparator<in T>): Unit {
if (size > 1)
sort { a, b -> comparator.compare(a, b) }
if (size > 1) sortArrayWith(this, comparator)
}
/**
@@ -0,0 +1,104 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.collections
internal fun <T> sortArrayWith(array: Array<out T>, comparison: (T, T) -> Int) {
if (getStableSortingIsSupported()) {
array.asDynamic().sort(comparison)
} else {
mergeSort(array.unsafeCast<Array<T>>(), 0, array.lastIndex, Comparator(comparison))
}
}
internal fun <T> sortArrayWith(array: Array<out T>, comparator: Comparator<in T>) {
if (getStableSortingIsSupported()) {
val comparison = { a: T, b: T -> comparator.compare(a, b) }
array.asDynamic().sort(comparison)
} else {
mergeSort(array.unsafeCast<Array<T>>(), 0, array.lastIndex, comparator)
}
}
internal fun <T : Comparable<T>> sortArray(array: Array<out T>) {
if (getStableSortingIsSupported()) {
val comparison = { a: T, b: T -> a.compareTo(b) }
array.asDynamic().sort(comparison)
} else {
mergeSort(array.unsafeCast<Array<T>>(), 0, array.lastIndex, naturalOrder())
}
}
private var _stableSortingIsSupported: Boolean? = null
private fun getStableSortingIsSupported(): Boolean {
_stableSortingIsSupported?.let { return it }
_stableSortingIsSupported = false
val array = js("[]").unsafeCast<Array<Int>>()
// known implementations may use stable sort for arrays of up to 512 elements
// so we create slightly more elements to test stability
for (index in 0 until 600) array.asDynamic().push(index)
val comparison = { a: Int, b: Int -> (a and 3) - (b and 3) }
array.asDynamic().sort(comparison)
for (index in 1 until array.size) {
val a = array[index - 1]
val b = array[index]
if ((a and 3) == (b and 3) && a >= b) return false
}
_stableSortingIsSupported = true
return true
}
private fun <T> mergeSort(array: Array<T>, start: Int, endInclusive: Int, comparator: Comparator<in T>) {
val buffer = arrayOfNulls<Any?>(array.size).unsafeCast<Array<T>>()
val result = mergeSort(array, buffer, start, endInclusive, comparator)
if (result !== array) {
result.forEachIndexed { i, v -> array[i] = v }
}
}
// Both start and end are inclusive indices.
private fun <T> mergeSort(array: Array<T>, buffer: Array<T>, start: Int, end: Int, comparator: Comparator<in T>): Array<T> {
if (start == end) {
return array
}
val median = (start + end) / 2
val left = mergeSort(array, buffer, start, median, comparator)
val right = mergeSort(array, buffer, median + 1, end, comparator)
val target = if (left === buffer) array else buffer
// Merge.
var leftIndex = start
var rightIndex = median + 1
for (i in start..end) {
when {
leftIndex <= median && rightIndex <= end -> {
val leftValue = left[leftIndex]
val rightValue = right[rightIndex]
if (comparator.compare(leftValue, rightValue) <= 0) {
target[i] = leftValue
leftIndex++
} else {
target[i] = rightValue
rightIndex++
}
}
leftIndex <= median -> {
target[i] = left[leftIndex]
leftIndex++
}
else /* rightIndex <= end */ -> {
target[i] = right[rightIndex]
rightIndex++
}
}
}
return target
}