diff --git a/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/.gitignore b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/.gitignore new file mode 100644 index 0000000..c834055 --- /dev/null +++ b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/.gitignore @@ -0,0 +1,4 @@ +.gradle/ +.idea/ +build/ +kotlin-js-store/ diff --git a/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/build.gradle.kts b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/build.gradle.kts new file mode 100644 index 0000000..bc32982 --- /dev/null +++ b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/build.gradle.kts @@ -0,0 +1,66 @@ +plugins { + kotlin("multiplatform") version "1.7.10" + `maven-publish` +} + +group = "io.github.holygrailsortproject" +version = "1.0" + +repositories { + mavenCentral() +} + +publishing { + repositories { + maven { + name = "gaming32" + credentials(PasswordCredentials::class) + + val baseUri = "https://maven.jemnetworks.com" + url = uri(baseUri + if (version.toString().endsWith("-SNAPSHOT")) "/snapshots" else "/releases") + } + } +} + +kotlin { + jvm { + compilations.all { + kotlinOptions.jvmTarget = "1.8" + } + withJava() + testRuns["test"].executionTask.configure { + useJUnitPlatform() + } + } + js(BOTH) { + browser { + commonWebpackConfig { + cssSupport.enabled = true + } + } + } + val hostOs = System.getProperty("os.name") + val isMingwX64 = hostOs.startsWith("Windows") + val nativeTarget = when { + hostOs == "Mac OS X" -> macosX64("native") + hostOs == "Linux" -> linuxX64("native") + isMingwX64 -> mingwX64("native") + else -> throw GradleException("Host OS is not supported in Kotlin/Native.") + } + + + sourceSets { + val commonMain by getting + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + val jvmMain by getting + val jvmTest by getting + val jsMain by getting + val jsTest by getting + val nativeMain by getting + val nativeTest by getting + } +} diff --git a/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradle.properties b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradle.properties new file mode 100644 index 0000000..a32ea3a --- /dev/null +++ b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradle.properties @@ -0,0 +1,3 @@ +kotlin.code.style=official +kotlin.mpp.stability.nowarn=true +kotlin.js.generate.executable.default=false diff --git a/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradle/wrapper/gradle-wrapper.jar b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..41d9927 Binary files /dev/null and b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradle/wrapper/gradle-wrapper.properties b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..aa991fc --- /dev/null +++ b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradlew b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradlew.bat b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/settings.gradle.kts b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/settings.gradle.kts new file mode 100644 index 0000000..50476e0 --- /dev/null +++ b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/settings.gradle.kts @@ -0,0 +1,3 @@ + +rootProject.name = "rewritten-grailsort" + diff --git a/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/src/commonMain/kotlin/io/github/holygrailsortproject/rewrittengrailsort/grailSort.kt b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/src/commonMain/kotlin/io/github/holygrailsortproject/rewrittengrailsort/grailSort.kt new file mode 100644 index 0000000..86b8e18 --- /dev/null +++ b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/src/commonMain/kotlin/io/github/holygrailsortproject/rewrittengrailsort/grailSort.kt @@ -0,0 +1,1078 @@ +package io.github.holygrailsortproject.rewrittengrailsort + +const val GRAIL_STATIC_EXT_BUFFER_LEN = 512 + +private fun MutableList.swap(a: Int, b: Int) { + this[b] = set(a, this[b]) +} + +private fun MutableList.blockSwap(a: Int, b: Int, blockLen: Int) { + for (i in 0 until blockLen) { + swap(a + i, b + i) + } +} + +private fun MutableList.rotate(start: Int, leftLen: Int, rightLen: Int) { + var currentStart = start + var currentLeft = leftLen + var currentRight = rightLen + while (currentLeft > 0 && currentRight > 0) { + if (currentLeft <= currentRight) { + blockSwap(currentStart, currentStart + currentLeft, currentLeft) + currentStart += currentLeft + currentRight -= currentLeft + } else { + blockSwap(currentStart + currentLeft - currentRight, currentStart + currentLeft, currentRight) + currentLeft -= currentRight + } + } +} + +private fun Array.copyInto(list: MutableList, src: Int, dest: Int, length: Int) { + for (i in 0 until length) { + list[dest + i] = this[src + i] + } +} + +private fun Array.copyIntoUnsafe(other: MutableList, src: Int, dest: Int, length: Int) { + // We assume that the caller knows all the elements we're copying have correct nullability + @Suppress("UNCHECKED_CAST") + (this as Array).copyInto(other, src, dest, length) +} + +private fun List.copyInto(array: Array, src: Int, dest: Int, length: Int) { + for (i in 0 until length) { + array[dest + i] = this[src + i] + } +} + +private fun List.copyInto(other: MutableList, src: Int, dest: Int, length: Int) { + if (dest < src) { + for (i in 0 until length) { + other[dest + i] = this[src + i] + } + } else { + for (i in length - 1 downTo 0) { + other[dest + i] = this[src + i] + } + } +} + +private fun > MutableList.insertSort(start: Int, length: Int) { + for (item in 1 until length) { + var right = start + item + var left = right - 1 + + while (left >= start && this[left] > this[right]) { + swap(left, right) + left-- + right-- + } + } +} + +private fun > List.binarySearchLeft(start: Int, length: Int, target: T): Int { + var left = 0 + var right = length + + while (left < right) { + val middle = left + (right - left) / 2 + + if (this[start + middle] < target) { + left = middle + 1 + } else { + right = middle + } + } + + return left +} + +private fun > List.binarySearchRight(start: Int, length: Int, target: T): Int { + var left = 0 + var right = length + + while (left < right) { + val middle = left + (right - left) / 2 + + if (this[start + middle] > target) { + right = middle + } else { + left = middle + 1 + } + } + + return right +} + +internal fun > collectKeys(list: MutableList, idealKeys: Int): Int { + var keysFound = 1 + var firstKey = 0 + var currKey = 1 + + while (currKey < list.size && keysFound < idealKeys) { + val insertPos = list.binarySearchLeft(firstKey, keysFound, list[currKey]) + + // Use compareTo() manually because != uses equals() + if (insertPos == keysFound || list[currKey].compareTo(list[firstKey + insertPos]) != 0) { + list.rotate(firstKey, keysFound, currKey - (firstKey + keysFound)) + firstKey = currKey - keysFound + list.rotate(firstKey + insertPos, keysFound - insertPos, 1) + keysFound++ + } + currKey++ + } + + list.rotate(0, firstKey, keysFound) + return keysFound +} + +private fun > pairwiseSwaps(list: MutableList, start: Int, length: Int) { + var index = 1 + while (index < length) { + val right = start + index + val left = right - 1 + + if (list[left] > list[right]) { + list.swap(left - 2, right) + list.swap(right - 2, left) + } else { + list.swap(left - 2, left) + list.swap(right - 2, right) + } + + index += 2 + } + + val left = start + index - 1 + if (left < start + length) { + list.swap(left - 2, left) + } +} + +private fun > pairwiseWrites(list: MutableList, start: Int, length: Int) { + var index = 1 + while (index < length) { + val right = start + index + val left = right - 1 + + if (list[left] > list[right]) { + list[left - 2] = list[right] + list[right - 2] = list[left] + } else { + list[left - 2] = list[left] + list[right - 2] = list[right] + } + + index += 2 + } + + val left = start + index - 1 + if (left < start + length) { + list[left - 2] = list[left] + } +} + +private fun > mergeForwards( + list: MutableList, + start: Int, + leftLen: Int, + rightLen: Int, + bufferOffset: Int +) { + var buffer = start - bufferOffset + var left = start + val middle = start + leftLen + var right = middle + val end = middle + rightLen + + while (right < end) { + if (left == middle || list[left] > list[right]) { + list.swap(buffer++, right++) + } else { + list.swap(buffer++, left++) + } + } + + if (buffer != left) { + list.blockSwap(buffer, left, middle - left) + } +} + +private fun > mergeBackwards( + list: MutableList, + start: Int, + leftLen: Int, + rightLen: Int, + bufferOffset: Int +) { + val end = start - 1 + var left = end + leftLen + val middle = left + var right = middle + rightLen + var buffer = right + bufferOffset + + while (left > end) { + if (right == middle || list[left] > list[right]) { + list.swap(buffer--, left--) + } else { + list.swap(buffer--, right--) + } + } + + if (right != buffer) { + while (right > middle) { + list.swap(buffer--, right--) + } + } +} + +private fun > mergeOutOfPlace( + list: MutableList, + start: Int, + leftLen: Int, + rightLen: Int, + bufferOffset: Int +) { + var buffer = start - bufferOffset + var left = start + val middle = start + leftLen + var right = middle + val end = middle + rightLen + + while (right < end) { + if (left == middle || list[left] > list[right]) { + list[buffer++] = list[right++] + } else { + list[buffer++] = list[left++] + } + } + + if (buffer != left) { + while (left < middle) { + list[buffer++] = list[left++] + } + } +} + +private fun > buildInPlace( + list: MutableList, + start: Int, + length: Int, + currentLen: Int, + bufferLen: Int +) { + var currentStart = start + + var mergeLen = currentLen + while (mergeLen < bufferLen) { + val fullMerge = 2 * mergeLen + + val mergeEnd = currentStart + length - fullMerge + val bufferOffset = mergeLen + + var mergeIndex = currentStart + while (mergeIndex <= mergeEnd) { + mergeForwards(list, mergeIndex, mergeLen, mergeLen, bufferOffset) + mergeIndex += fullMerge + } + + val leftOver = length - (mergeIndex - currentStart) + + if (leftOver > mergeLen) { + mergeForwards(list, mergeIndex, mergeLen, leftOver - mergeLen, bufferOffset) + } else { + list.rotate(mergeIndex - mergeLen, mergeLen, leftOver) + } + + currentStart -= mergeLen + mergeLen *= 2 + } + + val fullMerge = 2 * bufferLen + val lastBlock = length % fullMerge + val lastOffset = currentStart + length - lastBlock + + if (lastBlock <= bufferLen) { + list.rotate(lastOffset, lastBlock, bufferLen) + } else { + mergeBackwards(list, lastOffset, bufferLen, lastBlock - bufferLen, bufferLen) + } + + for (mergeIndex in lastOffset - fullMerge downTo currentStart step fullMerge) { + mergeBackwards(list, mergeIndex, bufferLen, bufferLen, bufferLen) + } +} + +private fun > blockSelectSort( + list: MutableList, + firstKey: Int, + start: Int, + medianKey: Int, + blockCount: Int, + blockLen: Int +): Int { + var currentMedian = medianKey + + for (firstBlock in 0 until blockCount) { + var selectBlock = firstBlock + + for (currBlock in (firstBlock + 1) until blockCount) { + val compare = list[start + currBlock * blockLen].compareTo(list[start + selectBlock * blockLen]) + + if (compare < 0 || (compare == 0 && list[firstKey + currBlock] < list[firstBlock + selectBlock])) { + selectBlock = currBlock + } + } + + if (selectBlock != firstBlock) { + list.blockSwap(start + firstBlock * blockLen, start + selectBlock * blockLen, blockLen) + list.swap(firstKey + firstBlock, firstKey + selectBlock) + + if (currentMedian == firstBlock) { + currentMedian = selectBlock + } else if (currentMedian == selectBlock) { + currentMedian = firstBlock + } + } + } + + return currentMedian +} + +private fun inPlaceBufferReset(list: MutableList, start: Int, length: Int, bufferOffset: Int) { + var buffer = start + length - 1 + var index = buffer - bufferOffset + while (buffer >= start) { + list.swap(index--, buffer--) + } +} + +private fun outOfPlaceBufferReset(list: MutableList, start: Int, length: Int, bufferOffset: Int) { + var buffer = start + length - 1 + var index = buffer - bufferOffset + while (buffer >= start) { + list[buffer--] = list[index--] + } +} + +private fun inPlaceBufferRewind(list: MutableList, start: Int, leftBlock: Int, buffer: Int) { + var currentLeft = leftBlock + var currentBuffer = buffer + while (currentLeft >= start) { + list.swap(currentBuffer--, currentLeft--) + } +} + +private fun outOfPlaceBufferRewind(list: MutableList, start: Int, leftBlock: Int, buffer: Int) { + var currentBuffer = buffer + var currentLeft = leftBlock + while (currentLeft >= start) { + list[currentBuffer--] = list[currentLeft--] + } +} + +private fun > countLastMergeBlocks(list: List, offset: Int, blockCount: Int, blockLen: Int): Int { + var blocksToMerge = 0 + + val lastRightFrag = offset + blockCount * blockLen + var prevLeftBlock = lastRightFrag - blockLen + + while (blocksToMerge < blockCount && list[lastRightFrag] < list[prevLeftBlock]) { + blocksToMerge++ + prevLeftBlock -= blockLen + } + + return blocksToMerge +} + +private fun > lazyMerge(list: MutableList, start: Int, leftLen: Int, rightLen: Int) { + var currentLeft = leftLen + var currentRight = rightLen + if (currentLeft < currentRight) { + var middle = start + currentLeft + var currentStart = start + + while (currentLeft != 0) { + val mergeLen = list.binarySearchLeft(middle, currentRight, list[currentStart]) + + if (mergeLen != 0) { + list.rotate(currentStart, currentLeft, mergeLen) + + currentStart += mergeLen + middle += mergeLen + currentRight -= mergeLen + } + + if (currentRight == 0) { + break + } else { + do { + currentStart++ + currentLeft-- + } while (currentLeft != 0 && list[currentStart] <= list[middle]) + } + } + } else { + var end = start + currentLeft + currentRight - 1 + + while (currentRight != 0) { + val mergeLen = list.binarySearchRight(start, currentLeft, list[end]) + + if (mergeLen != currentLeft) { + list.rotate(start + mergeLen, currentLeft - mergeLen, currentRight) + + end -= currentLeft - mergeLen + currentLeft = mergeLen + } + + if (currentLeft == 0) { + break + } else { + val middle = start + currentLeft + do { + currentRight-- + end-- + } while (currentRight != 0 && list[middle - 1] <= list[end]) + } + } + } +} + +private typealias Subarray = Boolean +private const val SUBARRAY_LEFT: Subarray = false +private const val SUBARRAY_RIGHT: Subarray = true + +private fun > getSubarray(list: List, currentKey: Int, medianKey: Int) = + if (list[currentKey] < list[medianKey]) { + SUBARRAY_LEFT + } else { + SUBARRAY_RIGHT + } + +class GrailSort> { + + private lateinit var extBuffer: Array + private var currBlockLen: Int = 0 + private var currBlockOrigin: Subarray = false + + private fun smartMerge( + list: MutableList, + start: Int, + leftLen: Int, + leftOrigin: Subarray, + rightLen: Int, + bufferOffset: Int + ) { + var buffer = start - bufferOffset + var left = start + val middle = start + leftLen + var right = middle + val end = middle + rightLen + + if (leftOrigin == SUBARRAY_LEFT) { + while (left < middle && right < end) { + if (list[left] <= list[right]) { + list.swap(buffer++, left++) + } else { + list.swap(buffer++, right++) + } + } + } else { + while (left < middle && right < end) { + if (list[left] < list[right]) { + list.swap(buffer++, left++) + } else { + list.swap(buffer++, right++) + } + } + } + + if (left < middle) { + currBlockLen = middle - left + inPlaceBufferRewind(list, left, middle - 1, end - 1) + } else { + currBlockLen = end - right + currBlockOrigin = !leftOrigin + } + } + + private fun smartLazyMerge(list: MutableList, start: Int, leftLen: Int, leftOrigin: Subarray, rightLen: Int) { + var currentStart = start + var currentLeft = leftLen + var currentRight = rightLen + var middle = currentStart + currentLeft + + if (leftOrigin == SUBARRAY_LEFT) { + if (list[middle - 1] > list[middle]) { + while (currentLeft != 0) { + val mergeLen = list.binarySearchLeft(middle, currentRight, list[currentStart]) + + if (mergeLen != 0) { + list.rotate(currentStart, currentLeft, mergeLen) + + currentStart += mergeLen + middle += mergeLen + currentRight -= mergeLen + } + + if (currentRight == 0) { + currBlockLen = currentLeft + return + } + do { + currentStart++ + currentLeft-- + } while (currentLeft != 0 && list[start] <= list[middle]) + } + } + } else { + if (list[middle - 1] >= list[middle]) { + while (currentLeft != 0) { + val mergeLen = list.binarySearchRight(middle, currentRight, list[currentStart]) + + if (mergeLen != 0) { + list.rotate(currentStart, currentLeft, mergeLen) + + currentStart += mergeLen + middle += mergeLen + currentRight -= mergeLen + } + + if (rightLen == 0) { + currBlockLen = currentLeft + return + } + do { + currentStart++ + currentLeft-- + } while (leftLen != 0 && list[start] < list[middle]) + } + } + } + + currBlockLen = currentRight + currBlockOrigin = !leftOrigin + } + + private fun smartMergeOutOfPlace( + list: MutableList, + start: Int, + leftLen: Int, + leftOrigin: Subarray, + rightLen: Int, + bufferOffset: Int + ) { + var buffer = start - bufferOffset + var left = start + val middle = start + leftLen + var right = middle + val end = middle + rightLen + + if (leftOrigin == SUBARRAY_LEFT) { + while (left < middle && right < end) { + if (list[left] <= list[right]) { + list[buffer++] = list[left++] + } else { + list[buffer++] = list[right++] + } + } + } else { + while (left < middle && right < end) { + if (list[left] < list[right]) { + list[buffer++] = list[left++] + } else { + list[buffer++] = list[right++] + } + } + } + + if (left < middle) { + currBlockLen = middle - left + outOfPlaceBufferRewind(list, left, middle - 1, end - 1) + } else { + currBlockLen = end - right + currBlockOrigin = !leftOrigin + } + } + + private fun mergeBlocks( + list: MutableList, + firstKey: Int, + medianKey: Int, + start: Int, + blockCount: Int, + blockLen: Int, + lastMergeBlocks: Int, + lastLen: Int + ) { + var buffer: Int + + var currBlock: Int + var nextBlock = start + blockLen + + currBlockLen = blockLen + currBlockOrigin = getSubarray(list, firstKey, medianKey) + + for (keyIndex in 1 until blockCount) { + currBlock = nextBlock - currBlockLen + val nextBlockOrigin = getSubarray(list, firstKey + keyIndex, medianKey) + + if (nextBlockOrigin == currBlockOrigin) { + buffer = currBlock - blockLen + + list.blockSwap(buffer, currBlock, currBlockLen) + currBlockLen = blockLen + } else { + smartMerge(list, currBlock, currBlockLen, currBlockOrigin, blockLen, blockLen) + } + + nextBlock += blockLen + } + + currBlock = nextBlock - currBlockLen + buffer = currBlock - blockLen + + if (lastLen != 0) { + if (currBlockOrigin == SUBARRAY_RIGHT) { + list.blockSwap(buffer, currBlock, currBlockLen) + + currBlock = nextBlock + currBlockLen = blockLen * lastMergeBlocks + currBlockOrigin = SUBARRAY_LEFT + } else { + currBlockLen += blockLen * lastMergeBlocks + } + + mergeForwards(list, currBlock, currBlockLen, lastLen, blockLen) + } else { + list.blockSwap(buffer, currBlock, currBlockLen) + } + } + + private fun lazyMergeBlocks( + list: MutableList, + firstKey: Int, + medianKey: Int, + start: Int, + blockCount: Int, + blockLen: Int, + lastMergeBlocks: Int, + lastLen: Int + ) { + var currBlock: Int + var nextBlock = start + blockLen + + currBlockLen = blockLen + currBlockOrigin = getSubarray(list, firstKey, medianKey) + + for (keyIndex in 1 until blockCount) { + currBlock = nextBlock - currBlockLen + val nextBlockOrigin = getSubarray(list, firstKey + keyIndex, medianKey) + + if (nextBlockOrigin == currBlockOrigin) { + currBlockLen = blockLen + } else if (blockLen != 0 && currBlockLen != 0) { + smartLazyMerge(list, currBlock, currBlockLen, currBlockOrigin, blockLen) + } + + nextBlock += blockLen + } + + currBlock = nextBlock - currBlockLen + + if (lastLen != 0) { + if (currBlockOrigin == SUBARRAY_RIGHT) { + currBlock = nextBlock + currBlockLen = blockLen * lastMergeBlocks + currBlockOrigin = SUBARRAY_LEFT + } else { + currBlockLen += blockLen * lastMergeBlocks + } + + lazyMerge(list, currBlock, currBlockLen, lastLen) + } + } + + private fun combineOutOfPlace( + list: MutableList, + firstKey: Int, + start: Int, + length: Int, + subarrayLen: Int, + blockLen: Int, + mergeCount: Int, + lastSubarrays: Int + ) { + list.copyInto(extBuffer, start - blockLen, 0, blockLen) + + val fullMerge = 2 * subarrayLen + var blockCount = fullMerge / blockLen + + for (mergeIndex in 0 until mergeCount) { + val offset = start + mergeIndex * fullMerge + + list.insertSort(firstKey, blockCount) + + var medianKey = subarrayLen / blockLen + medianKey = blockSelectSort(list, firstKey, offset, medianKey, blockCount, blockLen) + + mergeBlocksOutOfPlace(list, firstKey, firstKey + medianKey, offset, blockCount, blockLen, 0, 0) + } + + if (lastSubarrays != 0) { + val offset = start + mergeCount * fullMerge + blockCount = lastSubarrays / blockLen + + list.insertSort(firstKey, blockCount + 1) + + var medianKey = subarrayLen / blockLen + medianKey = blockSelectSort(list, firstKey, offset, medianKey, blockCount, blockLen) + + val lastFragment = lastSubarrays - blockCount * blockLen + val lastMergeBlocks = if (lastFragment != 0) { + countLastMergeBlocks(list, offset, blockCount, blockLen) + } else { + 0 + } + + val smartMerges = blockCount - lastMergeBlocks + + if (smartMerges == 0) { + val leftLen = lastMergeBlocks * blockLen + + mergeOutOfPlace(list, offset, leftLen, lastFragment, blockLen) + } else { + mergeBlocksOutOfPlace( + list, firstKey, firstKey + medianKey, offset, smartMerges, blockLen, lastMergeBlocks, lastFragment + ) + } + } + + outOfPlaceBufferReset(list, start, length, blockLen) + extBuffer.copyIntoUnsafe(list, 0, start - blockLen, blockLen) + } + + private fun mergeBlocksOutOfPlace( + list: MutableList, + firstKey: Int, + medianKey: Int, + start: Int, + blockCount: Int, + blockLen: Int, + lastMergeBlocks: Int, + lastLen: Int + ) { + var nextBlock = start + blockLen + + currBlockLen = blockLen + currBlockOrigin = getSubarray(list, firstKey, medianKey) + + for (keyIndex in 1 until blockCount) { + val currBlock = nextBlock - currBlockLen + val nextBlockOrigin = getSubarray(list, firstKey + keyIndex, medianKey) + + if (nextBlockOrigin == currBlockOrigin) { + val buffer = currBlock - blockLen + + list.copyInto(list, currBlock, buffer, currBlockLen) + currBlockLen = blockLen + } else { + smartMergeOutOfPlace(list, currBlock, currBlockLen, currBlockOrigin, blockLen, blockLen) + } + + nextBlock += blockLen + } + + var currBlock = nextBlock - currBlockLen + val buffer = currBlock - blockLen + + if (lastLen != 0) { + if (currBlockOrigin == SUBARRAY_RIGHT) { + list.copyInto(list, currBlock, buffer, currBlockLen) + + currBlock = nextBlock + currBlockLen = blockLen * lastMergeBlocks + currBlockOrigin = SUBARRAY_LEFT + } else { + currBlockLen += blockLen * lastMergeBlocks + } + + mergeOutOfPlace(list, currBlock, currBlockLen, lastLen, blockLen) + } else { + list.copyInto(list, currBlock, buffer, currBlockLen) + } + } + + private fun combineInPlace( + list: MutableList, + firstKey: Int, + start: Int, + length: Int, + subarrayLen: Int, + blockLen: Int, + mergeCount: Int, + lastSubarrays: Int, + buffer: Boolean + ) { + val fullMerge = 2 * subarrayLen + var blockCount = fullMerge / blockLen + + for (mergeIndex in 0 until mergeCount) { + val offset = start + mergeIndex * fullMerge + + list.insertSort(firstKey, blockCount) + + var medianKey = subarrayLen / blockLen + medianKey = blockSelectSort(list, firstKey, offset, medianKey, blockCount, blockLen) + + if (buffer) { + mergeBlocks(list, firstKey, firstKey + medianKey, offset, blockCount, blockLen, 0, 0) + } else { + lazyMergeBlocks(list, firstKey, firstKey + medianKey, offset, blockCount, blockLen, 0, 0) + } + } + + if (lastSubarrays != 0) { + val offset = start + mergeCount * fullMerge + blockCount = lastSubarrays / blockLen + + list.insertSort(firstKey, blockCount + 1) + + var medianKey = subarrayLen / blockLen + medianKey = blockSelectSort(list, firstKey, offset, medianKey, blockCount, blockLen) + + val lastFragment = lastSubarrays - blockCount * blockLen + val lastMergeBlocks = if (lastFragment != 0) { + countLastMergeBlocks(list, offset, blockCount, blockLen) + } else { + 0 + } + + val smartMerges = blockCount - lastMergeBlocks + + if (smartMerges == 0) { + val leftLen = lastMergeBlocks * blockLen + + if (buffer) { + mergeForwards(list, offset, leftLen, lastFragment, blockLen) + } else { + lazyMerge(list, offset, leftLen, lastFragment) + } + } else { + if (buffer) { + mergeBlocks( + list, firstKey, firstKey + medianKey, offset, + smartMerges, blockLen, lastMergeBlocks, lastFragment + ) + } else { + lazyMergeBlocks( + list, firstKey, firstKey + medianKey, offset, + smartMerges, blockLen, lastMergeBlocks, lastFragment + ) + } + } + } + + if (buffer) { + inPlaceBufferReset(list, start, length, blockLen) + } + } + + private fun combineBlocks( + list: MutableList, + firstKey: Int, + start: Int, + length: Int, + subarrayLen: Int, + blockLen: Int, + buffer: Boolean + ) { + + val fullMerge = 2 * subarrayLen + val mergeCount = length / fullMerge + var lastSubarrays = length - (fullMerge * mergeCount) + + var realLength = length + if (lastSubarrays <= subarrayLen) { + realLength -= lastSubarrays + lastSubarrays = 0 + } + + if (buffer && this::extBuffer.isInitialized && blockLen <= extBuffer.size) { + combineOutOfPlace(list, firstKey, start, realLength, subarrayLen, blockLen, mergeCount, lastSubarrays) + } else { + combineInPlace(list, firstKey, start, realLength, subarrayLen, blockLen, mergeCount, lastSubarrays, buffer) + } + } + + private fun buildOutOfPlace(list: MutableList, start: Int, length: Int, bufferLen: Int, extLen: Int) { + list.copyInto(extBuffer, start - extLen, 0, extLen) + + pairwiseWrites(list, start, length) + var currentStart = start - 2 + + var mergeLen = 2 + while (mergeLen < extLen) { + val fullMerge = 2 * mergeLen + + val mergeEnd = currentStart + length - fullMerge + val bufferOffset = mergeLen + + var mergeIndex = currentStart + while (mergeIndex <= mergeEnd) { + mergeOutOfPlace(list, mergeIndex, mergeLen, mergeLen, bufferOffset) + mergeIndex += fullMerge + } + + val leftOver = length - (mergeIndex - currentStart) + + if (leftOver > mergeLen) { + mergeOutOfPlace(list, mergeIndex, mergeLen, leftOver - mergeLen, bufferOffset) + } else { + list.copyInto(list, mergeIndex, mergeIndex - mergeLen, leftOver) + } + + currentStart -= mergeLen + mergeLen *= 2 + } + + extBuffer.copyIntoUnsafe(list, 0, currentStart + length, extLen) + buildInPlace(list, currentStart, length, mergeLen, bufferLen) + } + + private fun buildBlocks(list: MutableList, start: Int, length: Int, bufferLen: Int) { + if (this::extBuffer.isInitialized) { + val extLen = if (bufferLen < extBuffer.size) { + bufferLen + } else { + var extLen = 1 + while (extLen * 2 < extBuffer.size) { + extLen *= 2 + } + extLen + } + + buildOutOfPlace(list, start, length, bufferLen, extLen) + } else { + pairwiseSwaps(list, start, length) + buildInPlace(list, start - 2, length, 2, bufferLen) + } + } + + fun commonSort(list: MutableList, extBuffer: Array?) { + if (list.size < 16) { + list.insertSort(0, list.size) + return + } + + var blockLen = 1 + while (blockLen * blockLen < list.size) { + blockLen *= 2 + } + + var keyLen = (list.size - 1) / blockLen + 1 + val idealKeys = keyLen + blockLen + + val keysFound = collectKeys(list, idealKeys) + + val idealBuffer: Boolean + if (keysFound < idealKeys) { + if (keysFound < 4) { + list.lazyStableSort() + return + } else { + keyLen = blockLen + blockLen = 0 + idealBuffer = false + while (keyLen > keysFound) { + keyLen /= 2 + } + } + } else { + idealBuffer = true + } + + val bufferEnd = blockLen + keyLen + var subarrayLen = if (idealBuffer) { + blockLen + } else { + keyLen + } + + if (idealBuffer && extBuffer != null) { + this.extBuffer = extBuffer + } + + buildBlocks(list, bufferEnd, list.size - bufferEnd, subarrayLen) + + while (list.size - bufferEnd > 2 * subarrayLen) { + subarrayLen *= 2 + + var currentBlockLen = blockLen + var scrollingBuffer = idealBuffer + + if (!idealBuffer) { + val keyBuffer = keyLen / 2 + + if (keyBuffer >= 2 * subarrayLen / keyBuffer) { + currentBlockLen = keyBuffer + scrollingBuffer = true + } else { + currentBlockLen = 2 * subarrayLen / keyLen + } + } + + combineBlocks(list, 0, bufferEnd, list.size - bufferEnd, subarrayLen, currentBlockLen, scrollingBuffer) + } + + list.insertSort(0, bufferEnd) + lazyMerge(list, 0, bufferEnd, list.size - bufferEnd) + } +} + +enum class GrailSortType { + IN_PLACE, STATIC_OOP, DYNAMIC_OOP +} + +fun > MutableList.lazyStableSort() { + for (index in 1 until size step 2) { + val left = index - 1 + + if (this[left] > this[index]) { + swap(left, index) + } + } + var mergeLen = 2 + while (mergeLen < size) { + val fullMerge = 2 * mergeLen + val mergeEnd = size - fullMerge + + var mergeIndex = 0 + while (mergeIndex <= mergeEnd) { + lazyMerge(this, mergeIndex, mergeLen, mergeLen) + mergeIndex += fullMerge + } + + val leftOver = size - mergeIndex + if (leftOver > mergeLen) { + lazyMerge(this, mergeIndex, mergeLen, leftOver - mergeLen) + } + + mergeLen *= 2 + } +} + +inline fun > MutableList.grailSort( + type: GrailSortType = GrailSortType.IN_PLACE +) = GrailSort().commonSort(this, when (type) { + GrailSortType.IN_PLACE -> null + GrailSortType.STATIC_OOP -> arrayOfNulls(GRAIL_STATIC_EXT_BUFFER_LEN) + GrailSortType.DYNAMIC_OOP -> { + var bufferLen = 1 + while (bufferLen * bufferLen < size) { + bufferLen *= 2 + } + arrayOfNulls(bufferLen) + } +}) diff --git a/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/src/commonTest/kotlin/io/github/holygrailsortproject/rewrittengrailsort/GrailSortTests.kt b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/src/commonTest/kotlin/io/github/holygrailsortproject/rewrittengrailsort/GrailSortTests.kt new file mode 100644 index 0000000..1f8bf3c --- /dev/null +++ b/Kotlin/Gaming32's Rewritten Grailsort for Kotlin/src/commonTest/kotlin/io/github/holygrailsortproject/rewrittengrailsort/GrailSortTests.kt @@ -0,0 +1,152 @@ +package io.github.holygrailsortproject.rewrittengrailsort + +import kotlin.math.min +import kotlin.random.Random +import kotlin.random.nextInt +import kotlin.test.Test +import kotlin.test.fail +import kotlin.time.DurationUnit +import kotlin.time.ExperimentalTime +import kotlin.time.measureTime + +@OptIn(ExperimentalTime::class) +class GrailSortTests { + private fun > mismatchComparable(a: List, b: List): Int { + if (a.size != b.size) { + return min(a.size, b.size) + } + for (i in a.indices) { + if (a[i].compareTo(b[i]) != 0) { + return i + } + } + return -1 + } + + private fun > assertMatching(list: List, verifier: List) { + val i = mismatchComparable(list, verifier) + if (i != -1) { + fail("list[$i] != verifier[$i]") + } + } + + private fun > List.unorderedAt(): Int { + for (i in 1 until size) { + if (this[i - 1] > this[i]) { + return i + } + } + return -1 + } + + private inline fun > testSort( + banner: String, + list: MutableList, + sorter: MutableList.() -> Unit + ) { + println("--- $banner ---") + val copy = list.sorted() + if (list.size <= 32) { + println("Before: $list") + } else { + println("(${list.size} numbers)") + } + val timeTaken = measureTime { list.sorter() } + if (list.size <= 32) { + println("After: $list") + } + val unordered = list.unorderedAt() + if (unordered != -1) { + fail("list[${unordered - 1}] > list[$unordered]") + } + println("Took ${timeTaken.toDouble(DurationUnit.MILLISECONDS)}ms") + assertMatching(list, copy) + } + + private fun createRandomList(length: Int, unique: Int = length): MutableList { + val result = mutableListOf() + for (i in 1..length) { + result.add(Random.nextInt(unique)) + } + return result + } + + @Test + fun testSmallGrailSort() = + testSort("testSmallGrailSort", mutableListOf(11, 10, 15, 0, 0, 14, 3, 9, 12, 9, 4, 0, 13, 2, 4)) { + grailSort() + } + + @Test + fun testMediumGrailSort() = + testSort("testMediumGrailSort", createRandomList(1 shl 14)) { grailSort() } + + @Test + fun testGrailSort() = + testSort("testGrailSort", createRandomList(1 shl 20)) { grailSort() } + + @Test + fun testGrailSortNonPow2() = + testSort("testGrailSortNonPow2", createRandomList((1 shl 20) - 13)) { grailSort() } + + @Test + fun testStrategy3() = + testSort("testStrategy3", createRandomList(1 shl 20, 3)) { grailSort() } + + @Test + fun testStrategy3NonPow2() = + testSort("testStrategy3NonPow2", createRandomList((1 shl 20) - 13, 3)) { grailSort() } + + @Test + fun testGrailSortStaticOOP() = + testSort("testGrailSortStaticOOP", createRandomList(1 shl 20)) { grailSort(GrailSortType.STATIC_OOP) } + + @Test + fun testGrailSortStaticOOPNonPow2() = + testSort("testGrailSortStaticOOPNonPow2", createRandomList((1 shl 20) - 13)) { + grailSort(GrailSortType.STATIC_OOP) + } + + @Test + fun testGrailSortDynamicOOP() = + testSort("testGrailSortDynamicOOP", createRandomList(1 shl 20)) { grailSort(GrailSortType.DYNAMIC_OOP) } + + @Test + fun testGrailSortDynamicOOPNonPow2() = + testSort("testGrailSortDynamicOOPNonPow2", createRandomList((1 shl 20) - 13)) { + grailSort(GrailSortType.DYNAMIC_OOP) + } + + @Test + fun testCollectKeys() { + val list = mutableListOf() + for (i in 1..24) { + list.add(Random.nextInt(0 until 24)) + } + var blockLen = 1 + while (blockLen * blockLen < list.size) { + blockLen *= 2 + } + val keyLen = (list.size - 1) / blockLen + 1 + val idealKeys = keyLen + blockLen + val keyCount = collectKeys(list, idealKeys) + val unordered = list.unorderedAt() + if (unordered < keyCount) { + fail("List is unordered at $unordered, which is before the end of the keys") + } + } + + @Test + fun testLazyStableSort() = + testSort("testLazyStableSort", mutableListOf(11, 10, 15, 0, 0, 14, 3, 9, 12, 9, 4, 0, 13, 2, 4)) { + lazyStableSort() + } + + @Test + fun testBigLazyStableSort() = + testSort("testBigLazyStableSort", createRandomList(16384)) { lazyStableSort() } + + @Test + fun testBigLazyStableSortNonPow2() = + testSort("testBigLazyStableSortNonPow2", createRandomList(16371)) { lazyStableSort() } +}