Skip to content

Commit

Permalink
Added radix sorting for integers, array of strings and generic variant (
Browse files Browse the repository at this point in the history
  • Loading branch information
soywiz committed Nov 24, 2023
1 parent df37e23 commit 6c3759c
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ internal expect fun anyIdentityHashCode(obj: Any?): Int
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
public annotation class KdsInternalApi

internal fun <T> Array<T>.fill(value: T) { for (n in indices) this[n] = value }
internal fun IntArray.fill(value: Int) { for (n in indices) this[n] = value }
@PublishedApi internal fun <T> Array<T>.fill(value: T) { for (n in indices) this[n] = value }
@PublishedApi internal fun IntArray.fill(value: Int) { for (n in indices) this[n] = value }

internal inline fun <T> contentHashCode(size: Int, gen: (index: Int) -> T): Int {
@PublishedApi internal inline fun <T> contentHashCode(size: Int, gen: (index: Int) -> T): Int {
var result = 1
for (n in 0 until size) result = 31 * result + gen(n).hashCode()
return result
}

internal inline fun hashCoder(count: Int, gen: (index: Int) -> Int): Int {
@PublishedApi internal inline fun hashCoder(count: Int, gen: (index: Int) -> Int): Int {
var out = 0
for (n in 0 until count) {
out *= 7
Expand Down
72 changes: 72 additions & 0 deletions korge-foundation/src/common/korlibs/datastructure/_RadixSort.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package korlibs.datastructure

import korlibs.datastructure.internal.*
import korlibs.memory.*
import kotlin.math.*

fun <T : CharSequence> Array<T>.sortedRadix(start: Int = 0, end: Int = this.size, default: Char = '\u0000', transform: (Char) -> Char = { it }): Array<T> = this.copyOf().also { it.sortRadix(start, end, default, transform) }
fun <T : CharSequence> Array<T>.sortRadix(start: Int = 0, end: Int = this.size, default: Char = '\u0000', transform: (Char) -> Char = { it }) {
val maxLength = (start ..< end).maxOfOrNull { this[it].length } ?: return
val maxChar = maxOf((start ..< end).maxOfOrNull { this[it].max().code } ?: return, default.code)
val temp = arrayOfNulls<CharSequence>(end - start)
//println("maxChar=$maxChar, usedBits=${maxChar.usedBits()}, offsets=${offsets.size}")
//println(maxChar)
radixSortGeneric(
start, end,
0, maxLength,
get = { this[it] },
setTemp = { n, it -> temp[n] = it },
flip = { arraycopy(temp, 0, this, start, temp.size) },
getRadix = { n, it -> transform(it.getOrElse(maxLength - 1 - n) { default }).code and 0xFFFF },
noffsets = 1 shl (maxChar.usedBits())
)
}

fun IntArray.sortedArrayRadix(start: Int = 0, end: Int = this.size, bits: Int = 16): IntArray = this.copyOf().also { it.sortRadix(start, end, bits) }

fun IntArray.sortRadix(start: Int = 0, end: Int = this.size, bits: Int = 16) {
val maxBits = this.maxBits(start, end)
val bits = bits.coerceIn(1, min(16, maxBits))
val temp = IntArray(end - start)
val mask = (1 shl bits) - 1

radixSortGeneric(
start, end,
0, ceil(maxBits.toFloat() / bits.toFloat()).toInt(),
get = { this[it] },
setTemp = { n, it -> temp[n - start] = it },
flip = { arraycopy(temp, 0, this, start, temp.size) },
getRadix = { n, it -> (it ushr (n * bits)) and mask },
noffsets = 1 shl bits
)
}

inline fun <T> radixSortGeneric(start: Int, end: Int, stepStart: Int, stepEnd: Int, get: (index: Int) -> T, setTemp: (index: Int, v: T) -> Unit, flip: () -> Unit, getRadix: (index: Int, v: T) -> Int, noffsets: Int) {
val offsets = IntArray(noffsets)
for (n in stepStart..<stepEnd) {
_radixSortStep(start, end, get = get, setTemp = setTemp, getRadix = { getRadix(n, it) }, offsets)
offsets.fill(0)
flip()
}
}

@PublishedApi
internal inline fun <T> _radixSortStep(start: Int, end: Int, get: (Int) -> T, setTemp: (Int, T) -> Unit, getRadix: (T) -> Int, offsets: IntArray) {
for (n in start..<end) offsets[getRadix(get(n))]++
for (i in 1..<offsets.size) offsets[i] += offsets[i - 1]
for (i in start..<end) {
val v = get(end - 1 - i)
val index = getRadix(v)
setTemp(offsets[index] - 1, v)
offsets[index]--
}
}

private fun Int.usedBits(): Int = 32 - this.countLeadingZeros()

private fun IntArray.maxBits(start: Int = 0, end: Int = size): Int {
var maxNum = 0u
for (n in start..< end) maxNum = max(maxNum, this[n].toUInt())
val maxBits = maxNum.toInt().usedBits()
return maxBits
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package korlibs.datastructure

import korlibs.time.*
import kotlin.test.*
import kotlin.random.*

class RadixSortTest {
@Test
fun testStringArray() {
val array1: Array<String> = arrayOf("abc", "aaa", "bca", "acc", "bbb", "cad", "caa", "dddd", "aaaa", "AAA", "BBB")
val original: Array<String> = array1.copyOf()
val items: Array<String> = array1.sortedRadix(transform = { it.lowercaseChar() })
val items2: Array<String> = array1.sortedArrayWith { a, b -> a.compareTo(b, ignoreCase = true) } as Array<String>
assertContentEquals(original, array1)
assertContentEquals(items, items2)
}

@Test
fun testInts() {
val random = Random(-1L)
val ints = IntArray(100_000) { random.nextInt() and 0x7FFFFFFF }
//repeat(10) { println(measureTime { ints.sortedArrayRadix() }) }
//repeat(10) { println(measureTime { ints.sortedArray() }) }
assertContentEquals(
ints.sortedArrayRadix(),
ints.sortedArray()
)
}
}

0 comments on commit 6c3759c

Please sign in to comment.