• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16package std.core;
17
18// NOTE: autogenerated file
19
20function bubbleSort(arr: FixedArray<boolean>, startIndex: int, endIndex: int): void {
21    let was = true
22    while (was) {
23        was = false
24        for (let i = startIndex; i < endIndex - 1; i++) {
25            if (((arr[i + 1]) ? 1 : 0) < ((arr[i]) ? 1 : 0)) {
26                const tmp = arr[i + 1]
27                arr[i + 1] = arr[i]
28                arr[i] = tmp
29                was = true
30            }
31        }
32    }
33}
34
35function insertionSort(arr: FixedArray<boolean>, startIndex: int, endIndex: int, initIndex: int = startIndex): void {
36    if (startIndex != initIndex) {
37        // arr[startIndex - 1] exists and is less than or equal to all elements in range
38        for (let i = startIndex + 1; i < endIndex; i++) {
39            const tmp = arr[i]
40            let pos = i
41            while (((tmp) ? 1 : 0) < ((arr[pos - 1]) ? 1 : 0)) {
42                arr[pos] = arr[pos - 1]
43                pos--
44            }
45            arr[pos] = tmp
46        }
47        return
48    }
49    for (let i = startIndex + 1; i < endIndex; i++) {
50        const tmp = arr[i]
51        if (((tmp) ? 1 : 0) < ((arr[startIndex]) ? 1 : 0)) {
52            for (let j = i; j > startIndex; j--) {
53                arr[j] = arr[j - 1]
54            }
55            arr[startIndex] = tmp
56        } else {
57            let pos = i
58            while (((tmp) ? 1 : 0) < ((arr[pos - 1]) ? 1 : 0)) {
59                arr[pos] = arr[pos - 1]
60                pos--
61            }
62            arr[pos] = tmp
63        }
64    }
65}
66
67/**
68 * sorts arr in-place
69 *
70 * @param arr an array to sort
71 *
72 * @param startIndex an index to start sorting with, inclusive
73 *
74 * @param endIndex an index to end sorting, exclusive
75 *
76 * @example: sort array arr
77 * ```
78 * sort(arr, 0, arr.length)
79 * ```
80 */
81export function sort(arr: FixedArray<boolean>, startIndex: int, endIndex: int): void {
82    if (!checkRange(arr.length, startIndex, endIndex)) {
83        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
84    }
85
86    countSortBools(arr, startIndex, endIndex)
87}
88
89function bubbleSort(arr: FixedArray<boolean>, startIndex: int, endIndex: int, mustPrecede: (lhs: boolean, rhs: boolean) => boolean): void {
90    let was = true
91    while (was) {
92        was = false
93        for (let i = startIndex; i < endIndex - 1; i++) {
94            if (mustPrecede(arr[i + 1], arr[i])) {
95                const tmp = arr[i + 1]
96                arr[i + 1] = arr[i]
97                arr[i] = tmp
98                was = true
99            }
100        }
101    }
102}
103
104function insertionSort(arr: FixedArray<boolean>, startIndex: int, endIndex: int, mustPrecede: (lhs: boolean, rhs: boolean) => boolean, initIndex: int = startIndex): void {
105    if (startIndex != initIndex) {
106        // arr[startIndex - 1] exists and is less than or equal to all elements in range
107        for (let i = startIndex + 1; i < endIndex; i++) {
108            const tmp = arr[i]
109            let pos = i
110            while (mustPrecede(tmp, arr[pos - 1])) {
111                arr[pos] = arr[pos - 1]
112                pos--
113            }
114            arr[pos] = tmp
115        }
116        return
117    }
118    for (let i = startIndex + 1; i < endIndex; i++) {
119        const tmp = arr[i]
120        if (mustPrecede(tmp, arr[startIndex])) {
121            for (let j = i; j > startIndex; j--) {
122                arr[j] = arr[j - 1]
123            }
124            arr[startIndex] = tmp
125        } else {
126            let pos = i
127            while (mustPrecede(tmp, arr[pos - 1])) {
128                arr[pos] = arr[pos - 1]
129                pos--
130            }
131            arr[pos] = tmp
132        }
133    }
134}
135
136/**
137 * sorts arr in-place
138 *
139 * @param arr an array to sort
140 *
141 * @param startIndex an index to start sorting with, inclusive
142 *
143 * @param endIndex an index to end sorting, exclusive
144 *
145 * @example: sort array arr
146 * ```
147 * sort(arr, 0, arr.length)
148 * ```
149 */
150export function sort_subarray(arr: FixedArray<boolean>, startIndex: int, endIndex: int, mustPrecede: (lhs: boolean, rhs: boolean) => boolean): void {
151    if (!checkRange(arr.length, startIndex, endIndex)) {
152        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
153    }
154
155    if (mustPrecede(false, true)) {
156        countSortBools(arr, startIndex, endIndex)
157    } else {
158        countSortBoolsInv(arr, startIndex, endIndex)
159    }
160}
161
162/**
163 * sorts arr in-place
164 *
165 * @param arr an array to sort
166 */
167export function sort_subarray(arr: FixedArray<boolean>, mustPrecede: (lhs: boolean, rhs: boolean) => boolean): void {
168    sort_subarray(arr, 0, arr.length, mustPrecede);
169}
170
171export function sort_subarray(arr: FixedArray<boolean>, startIndex: int, mustPrecede: (lhs: boolean, rhs: boolean) => boolean): void {
172    sort_subarray(arr, startIndex, arr.length, mustPrecede)
173}
174
175function countSortTruthCnt(arr: FixedArray<boolean>, startIndex: int, endIndex: int): int {
176    let truthCnt = 0
177    for (let i = startIndex; i < endIndex; i++) {
178        if (arr[i]) {
179            truthCnt++
180        }
181    }
182    return truthCnt
183}
184
185function countSortBools(arr: FixedArray<boolean>, startIndex: int, endIndex: int): void {
186    const truthCnt = countSortTruthCnt(arr, startIndex, endIndex)
187    for (let i = startIndex; i < endIndex - truthCnt; i++) {
188            arr[i] = false
189        }
190    for (let i = 0; i < truthCnt; i++) {
191        arr[endIndex - truthCnt + i] = true
192    }
193}
194function countSortBoolsInv(arr: FixedArray<boolean>, startIndex: int, endIndex: int): void {
195    const truthCnt = countSortTruthCnt(arr, startIndex, endIndex)
196    for (let i = 0; i < truthCnt; i++) {
197        arr[startIndex + i] = true
198    }
199    for (let i = startIndex + truthCnt; i < endIndex; i++) {
200        arr[i] = false
201    }
202}
203
204// ======== tests section ========
205
206/** note: used for tests, {@link test_sortAllOn} */
207class test_SortDataboolean {
208    name: string
209    arr: FixedArray<boolean>
210
211    constructor(name: string, arr: FixedArray<boolean>) {
212        this.name = name
213        this.arr = arr
214    }
215}
216
217/** note: used for tests, {@link test_sortAllOn} */
218function test_sortCopy(arr: FixedArray<boolean>): FixedArray<boolean> {
219    let c : FixedArray<boolean> = new boolean[arr.length]
220    for (let i = 0; i < arr.length; i++) {
221        c[i] = arr[i]
222    }
223    return c
224}
225
226/** note: used for tests, {@link test_sortAllOn} */
227function test_sortAllOnCmpFwd_boolean(l: boolean, r: boolean): boolean {
228    return ((l) ? 1 : 0) < ((r) ? 1 : 0)
229}
230
231/** note: used for tests, {@link test_sortAllOn} */
232function test_sortAllOnCmpInv_boolean(l: boolean, r: boolean): boolean {
233    return ((r) ? 1 : 0) < ((l) ? 1 : 0)
234}
235
236/** note: used for tests, {@link test_sortAllOn} */
237function test_printArr(arr: FixedArray<boolean>, startIndex: int, endIndex: int) {
238    for (let i = startIndex; i < endIndex; i++) {
239        console.print(arr[i] + ' ')
240    }
241    console.println('')
242}
243
244/**
245 * Function used to test sorting in standard library tests.
246 * There is only one exported function: `sort`, but for testing
247 * we need access to all sub sorts that are used. Hence this part of "tests"
248 * is located here. All related entities are prefixed whith `test_` to stand out
249 */
250export function test_sortAllOn(arr: FixedArray<boolean>) {
251    // block for comparator ``
252    if (true) {
253        const sorts = new Array<test_SortDataboolean>()
254        const bubbled = test_sortCopy(arr)
255        bubbleSort(bubbled, 0, bubbled.length)
256        sorts.push(new test_SortDataboolean("bubble", bubbled))
257
258        const insertion = test_sortCopy(arr)
259        insertionSort(insertion, 0, insertion.length)
260        sorts.push(new test_SortDataboolean("insertion", insertion))
261
262        const bcnt = test_sortCopy(arr)
263        countSortBools(bcnt, 0, bcnt.length)
264        sorts.push(new test_SortDataboolean("bools cnt()", bcnt))
265
266        const just = test_sortCopy(arr)
267        sort(just)
268        sorts.push(new test_SortDataboolean("sort()", just))
269
270        let sort0 = sorts.at(0)!
271        for (let s = 1; s < sorts.length; s++) {
272            let sortS = sorts.at(s)!
273            for (let i = 0; i < arr.length; i++) {
274                if (sort0.arr[i] != sortS.arr[i]) {
275                    console.println(sort0.name + ': ')
276                    test_printArr(sort0.arr, 0, arr.length)
277                    console.println(sortS.name + ': ')
278                    test_printArr(sortS.arr, 0, arr.length)
279                    throw new Error("sorts are not equal: " + sort0.name + ' ' + sortS.name + ' for boolean')
280                }
281            }
282        }
283    }
284    // block for comparator `est_sortAllOnCmpFwd_boolean`
285    if (true) {
286        const sorts = new Array<test_SortDataboolean>()
287        const bubbled = test_sortCopy(arr)
288        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpFwd_boolean)
289        sorts.push(new test_SortDataboolean("bubble", bubbled))
290
291        const insertion = test_sortCopy(arr)
292        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpFwd_boolean)
293        sorts.push(new test_SortDataboolean("insertion", insertion))
294
295        const bcnt = test_sortCopy(arr)
296        countSortBools(bcnt, 0, bcnt.length)
297        sorts.push(new test_SortDataboolean("bools cnt()", bcnt))
298
299        const just = test_sortCopy(arr)
300        sort_subarray(just, test_sortAllOnCmpFwd_boolean)
301        sorts.push(new test_SortDataboolean("sort()", just))
302
303        let sort0 = sorts.at(0)!
304        for (let s = 1; s < sorts.length; s++) {
305            let sortS = sorts.at(s)!
306            for (let i = 0; i < arr.length; i++) {
307                if (sort0.arr[i] != sortS.arr[i]) {
308                    console.println(sort0.name + ': ')
309                    test_printArr(sort0.arr, 0, arr.length)
310                    console.println(sortS.name + ': ')
311                    test_printArr(sortS.arr, 0, arr.length)
312                    throw new Error("sorts est_sortAllOnCmpFwd_boolean are not equal: " + sort0.name + ' ' + sortS.name + ' for boolean')
313                }
314            }
315        }
316    }
317    // block for comparator `est_sortAllOnCmpInv_boolean`
318    if (true) {
319        const sorts = new Array<test_SortDataboolean>()
320        const bubbled = test_sortCopy(arr)
321        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpInv_boolean)
322        sorts.push(new test_SortDataboolean("bubble", bubbled))
323
324        const insertion = test_sortCopy(arr)
325        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpInv_boolean)
326        sorts.push(new test_SortDataboolean("insertion", insertion))
327
328        const bcnt = test_sortCopy(arr)
329        countSortBoolsInv(bcnt, 0, bcnt.length)
330        sorts.push(new test_SortDataboolean("bools cnt()", bcnt))
331
332        const just = test_sortCopy(arr)
333        sort_subarray(just, test_sortAllOnCmpInv_boolean)
334        sorts.push(new test_SortDataboolean("sort()", just))
335
336        let sort0 = sorts.at(0)!
337        for (let s = 1; s < sorts.length; s++) {
338            let sortS = sorts.at(s)!
339            for (let i = 0; i < arr.length; i++) {
340                if (sort0.arr[i] != sortS.arr[i]) {
341                    console.println(sort0.name + ': ')
342                    test_printArr(sort0.arr, 0, arr.length)
343                    console.println(sortS.name + ': ')
344                    test_printArr(sortS.arr, 0, arr.length)
345                    throw new Error("sorts est_sortAllOnCmpInv_boolean are not equal: " + sort0.name + ' ' + sortS.name + ' for boolean')
346                }
347            }
348        }
349    }
350}
351// ======== end of tests section ========
352
353function copyPart(dst: FixedArray<byte>, counter: int, src: FixedArray<byte>, start: int, end?: int) {
354    if (end == undefined) {
355        end = src.length
356    }
357    for (let i = start; i < end!; ++i) {
358        dst[counter++] = src[i]
359    }
360}
361
362function merge(left: FixedArray<byte>, right: FixedArray<byte>, cmp: (lhs: byte, rhs: byte) => number): FixedArray<byte> {
363    const result:FixedArray<byte> = new byte[right.length + left.length]
364    let leftIndex = 0;
365    let rightIndex = 0;
366    let counter: int = 0
367
368    while (leftIndex < left.length &&
369        rightIndex < right.length) {
370        if (cmp(left[leftIndex], right[rightIndex]) <= 0) {
371            result[counter++] = left[leftIndex];
372            leftIndex++;
373        } else {
374            result[counter++] = right[rightIndex]
375            rightIndex++;
376        }
377    }
378    copyPart(result, counter, left, leftIndex)
379    copyPart(result, counter, right, rightIndex)
380
381    return result
382}
383
384export function mergeSort(array: FixedArray<byte>, cmp: (lhs: byte, rhs: byte) => number, begin: int = 0, end: int = 0): FixedArray<byte> {
385    if (end == 0) {
386        end = array.length
387    }
388    const arrLength = end - begin
389    if (arrLength <= 1) {
390        return array;
391    }
392    const middle = Math.floor(begin + arrLength / 2) as int
393    const leftHalf:FixedArray<byte> = new byte[middle]
394    let counter: int = 0
395    copyPart(leftHalf, counter, array, 0, middle)
396
397    counter = 0
398    const rightHalf:FixedArray<byte> = new byte[arrLength - middle]
399    copyPart(rightHalf, counter, array, middle)
400
401    return merge(mergeSort(leftHalf, cmp), mergeSort(rightHalf, cmp), cmp);
402}
403
404function bubbleSort(arr: FixedArray<byte>, startIndex: int, endIndex: int): void {
405    let was = true
406    while (was) {
407        was = false
408        for (let i = startIndex; i < endIndex - 1; i++) {
409            if ((arr[i + 1] < arr[i])) {
410                const tmp = arr[i + 1]
411                arr[i + 1] = arr[i]
412                arr[i] = tmp
413                was = true
414            }
415        }
416    }
417}
418
419function insertionSort(arr: FixedArray<byte>, startIndex: int, endIndex: int, initIndex: int = startIndex): void {
420    if (startIndex != initIndex) {
421        // arr[startIndex - 1] exists and is less than or equal to all elements in range
422        for (let i = startIndex + 1; i < endIndex; i++) {
423            const tmp = arr[i]
424            let pos = i
425            while ((tmp < arr[pos - 1])) {
426                arr[pos] = arr[pos - 1]
427                pos--
428            }
429            arr[pos] = tmp
430        }
431        return
432    }
433    for (let i = startIndex + 1; i < endIndex; i++) {
434        const tmp = arr[i]
435        if ((tmp < arr[startIndex])) {
436            for (let j = i; j > startIndex; j--) {
437                arr[j] = arr[j - 1]
438            }
439            arr[startIndex] = tmp
440        } else {
441            let pos = i
442            while ((tmp < arr[pos - 1])) {
443                arr[pos] = arr[pos - 1]
444                pos--
445            }
446            arr[pos] = tmp
447        }
448    }
449}
450function heapSortUp(arr: FixedArray<byte>, idxFromStart: int, startIndex: int, heapRoot: int): void {
451    const tmp = arr[startIndex + idxFromStart]
452    while (startIndex + idxFromStart > heapRoot) {
453        const p = (idxFromStart - 1) / 2
454        if (!(arr[startIndex + p] < tmp)) {
455            break
456        }
457        arr[startIndex + idxFromStart] = arr[startIndex + p]
458        idxFromStart = p
459    }
460    arr[startIndex + idxFromStart] = tmp
461}
462
463// Build max heap with root in startIndex given its children are roots of valid heaps
464function heapSortDown(arr: FixedArray<byte>, idxFromStart: int, startIndex: int, endIndex: int): void {
465    let heapRoot = startIndex + idxFromStart
466    let arrIndex = heapRoot
467    let childIndex = startIndex + idxFromStart * 2 + 1
468    const tmp = arr[arrIndex]
469    // Walk heap to bottom and pull max child up on each level
470    while (childIndex + 1 < endIndex) {
471        if ((arr[childIndex] < arr[childIndex + 1])) {
472            childIndex++
473        }
474        arr[arrIndex] = arr[childIndex]
475        arrIndex = childIndex
476        childIndex = childIndex * 2 - startIndex + 1
477    }
478    if (childIndex < endIndex) {
479        arr[arrIndex] = arr[childIndex]
480        arrIndex = childIndex
481    }
482    arr[arrIndex] = tmp
483    // Now heap is valid in all positions but arrIndex
484    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot)
485}
486
487export function heapSort(arr: FixedArray<byte>, startIndex: int, endIndex: int): void {
488    let len = endIndex - startIndex
489    for (let i = len / 2 - 1; i >= 0; i--) {
490        heapSortDown(arr, i, startIndex, endIndex)
491    }
492
493    for (let i = endIndex - 1; i > startIndex; i--) {
494        // move max element to the end of range
495        const tmp = arr[i]
496        arr[i] = arr[startIndex]
497        arr[startIndex] = tmp
498        heapSortDown(arr, 0, startIndex, i)
499    }
500}
501
502// Put median of three array elements to arr[index1]
503function median3(arr: FixedArray<byte>, index1: int, index2: int, index3: int): void {
504    let swap_idx = index2
505    if ((arr[index1] < arr[index2])) {
506        if ((arr[index3] < arr[index1])) {
507            return
508        }
509        if ((arr[index3] < arr[index2])) {
510            swap_idx = index3
511        }
512    } else {
513        if (!(arr[index3] < arr[index1])) {
514            return
515        }
516        if ((arr[index2] < arr[index3])) {
517            swap_idx = index3
518        }
519    }
520    let tmp = arr[index1]
521    arr[index1] = arr[swap_idx]
522    arr[swap_idx] = tmp
523}
524
525// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
526// Elements equal to pivot go to the right
527function quickSortSplit(arr: FixedArray<byte>, startIndex: int, endIndex: int): int {
528    const pivot = arr[startIndex]
529    let i = startIndex + 1
530    let j = endIndex - 1
531    // No bounds check because pivot is median of three elements
532    while ((arr[i] < pivot)) {
533        i++
534    }
535    if (i == startIndex + 1) {
536        while (i < j && !(arr[j] < pivot)) {
537            j--
538        }
539    } else {
540        while (!(arr[j] < pivot)) {
541            j--
542        }
543    }
544    while (i < j) {
545        // Here !(arr[i] < pivot) and (arr[j] < pivot) holds
546        let tmp = arr[i]
547        arr[i] = arr[j]
548        arr[j] = tmp
549        while ((arr[++i] < pivot)) {}
550        while (!(arr[--j] < pivot)) {}
551    }
552    let pivotIndex = i - 1
553    arr[startIndex] = arr[pivotIndex]
554    arr[pivotIndex] = pivot
555
556    return pivotIndex
557}
558
559// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
560// Elements equal to pivot go to the left
561function quickSortSplitLeft(arr: FixedArray<byte>, startIndex: int, endIndex: int): int {
562    const pivot = arr[startIndex]
563    let i = startIndex + 1
564    let j = endIndex - 1
565    // No bounds check because pivot is median of three elements
566    while ((pivot < arr[j])) {
567        j--
568    }
569    if (j + 1 == endIndex) {
570        while (i < j && !(pivot < arr[i])) {
571            i++
572        }
573    } else {
574        while (!(pivot < arr[i])) {
575            i++
576        }
577    }
578    while (i < j) {
579        // Here (pivot < arr[i]) and !(pivot < arr[j]) holds
580        let tmp = arr[i]
581        arr[i] = arr[j]
582        arr[j] = tmp
583        while (!(pivot < arr[++i])) {}
584        while ((pivot < arr[--j])) {}
585    }
586    arr[startIndex] = arr[j]
587    arr[j] = pivot
588
589    return j
590}
591
592function quickSortImpl3(arr: FixedArray<byte>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
593    while (endIndex - startIndex > 3) {
594        if (--maxDepth == 0) {
595            heapSort(arr, startIndex, endIndex)
596            return
597        }
598        // Here we assume that current interval is not the most left in the sorted range
599        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
600            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
601            // after that only the right part needs to be sorted
602            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
603            // with many occurencies of the smallest element
604            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
605            continue
606        }
607
608        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
609        let p = quickSortSplit(arr, startIndex, endIndex)
610        // make a call for the smaller part of array and continue processing the larger part in the loop
611        if (p - startIndex < endIndex - p) {
612            quickSortImpl3(arr, startIndex, p, maxDepth, initIndex)
613            startIndex = p + 1
614        } else {
615            quickSortImpl3(arr, p + 1, endIndex, maxDepth, initIndex)
616            endIndex = p
617        }
618    }
619    insertionSort(arr, startIndex, endIndex, initIndex)
620}
621function quickSortImpl40(arr: FixedArray<byte>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
622    while (endIndex - startIndex > 40) {
623        if (--maxDepth == 0) {
624            heapSort(arr, startIndex, endIndex)
625            return
626        }
627        // Here we assume that current interval is not the most left in the sorted range
628        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
629            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
630            // after that only the right part needs to be sorted
631            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
632            // with many occurencies of the smallest element
633            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
634            continue
635        }
636
637        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
638        let p = quickSortSplit(arr, startIndex, endIndex)
639        // make a call for the smaller part of array and continue processing the larger part in the loop
640        if (p - startIndex < endIndex - p) {
641            quickSortImpl40(arr, startIndex, p, maxDepth, initIndex)
642            startIndex = p + 1
643        } else {
644            quickSortImpl40(arr, p + 1, endIndex, maxDepth, initIndex)
645            endIndex = p
646        }
647    }
648    insertionSort(arr, startIndex, endIndex, initIndex)
649}
650
651function quickSort(arr: FixedArray<byte>, startIndex: int, endIndex: int): void {
652    let size = endIndex - startIndex
653    if (size <= 1) {
654        return
655    }
656    // find log of length to fall back into determenistic O(n logn) sort
657    let bits = 32
658    for (let i = 2; i < 31; i++) {
659        if ((size >> i) == 0) {
660            bits = i
661            break
662        }
663    }
664    quickSortImpl40(arr, startIndex, endIndex, bits * 3)
665}
666
667/**
668 * sorts arr in-place
669 *
670 * @param arr an array to sort
671 *
672 * @param startIndex an index to start sorting with, inclusive
673 *
674 * @param endIndex an index to end sorting, exclusive
675 *
676 * @example: sort array arr
677 * ```
678 * sort(arr, 0, arr.length)
679 * ```
680 */
681export function sort(arr: FixedArray<byte>, startIndex: int, endIndex: int): void {
682    if (!checkRange(arr.length, startIndex, endIndex)) {
683        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
684    }
685
686    if (endIndex - startIndex > 1024) {
687        countSort(arr, startIndex, endIndex)
688    }
689    quickSort(arr, startIndex, endIndex);
690}
691
692function bubbleSort(arr: FixedArray<byte>, startIndex: int, endIndex: int, mustPrecede: (lhs: byte, rhs: byte) => boolean): void {
693    let was = true
694    while (was) {
695        was = false
696        for (let i = startIndex; i < endIndex - 1; i++) {
697            if (mustPrecede(arr[i + 1], arr[i])) {
698                const tmp = arr[i + 1]
699                arr[i + 1] = arr[i]
700                arr[i] = tmp
701                was = true
702            }
703        }
704    }
705}
706
707function insertionSort(arr: FixedArray<byte>, startIndex: int, endIndex: int, mustPrecede: (lhs: byte, rhs: byte) => boolean, initIndex: int = startIndex): void {
708    if (startIndex != initIndex) {
709        // arr[startIndex - 1] exists and is less than or equal to all elements in range
710        for (let i = startIndex + 1; i < endIndex; i++) {
711            const tmp = arr[i]
712            let pos = i
713            while (mustPrecede(tmp, arr[pos - 1])) {
714                arr[pos] = arr[pos - 1]
715                pos--
716            }
717            arr[pos] = tmp
718        }
719        return
720    }
721    for (let i = startIndex + 1; i < endIndex; i++) {
722        const tmp = arr[i]
723        if (mustPrecede(tmp, arr[startIndex])) {
724            for (let j = i; j > startIndex; j--) {
725                arr[j] = arr[j - 1]
726            }
727            arr[startIndex] = tmp
728        } else {
729            let pos = i
730            while (mustPrecede(tmp, arr[pos - 1])) {
731                arr[pos] = arr[pos - 1]
732                pos--
733            }
734            arr[pos] = tmp
735        }
736    }
737}
738function heapSortUp(arr: FixedArray<byte>, idxFromStart: int, startIndex: int, heapRoot: int, mustPrecede: (lhs: byte, rhs: byte) => boolean): void {
739    const tmp = arr[startIndex + idxFromStart]
740    while (startIndex + idxFromStart > heapRoot) {
741        const p = (idxFromStart - 1) / 2
742        if (!mustPrecede(arr[startIndex + p], tmp)) {
743            break
744        }
745        arr[startIndex + idxFromStart] = arr[startIndex + p]
746        idxFromStart = p
747    }
748    arr[startIndex + idxFromStart] = tmp
749}
750
751// Build max heap with root in startIndex given its children are roots of valid heaps
752function heapSortDown(arr: FixedArray<byte>, idxFromStart: int, startIndex: int, endIndex: int, mustPrecede: (lhs: byte, rhs: byte) => boolean): void {
753    let heapRoot = startIndex + idxFromStart
754    let arrIndex = heapRoot
755    let childIndex = startIndex + idxFromStart * 2 + 1
756    const tmp = arr[arrIndex]
757    // Walk heap to bottom and pull max child up on each level
758    while (childIndex + 1 < endIndex) {
759        if (mustPrecede(arr[childIndex], arr[childIndex + 1])) {
760            childIndex++
761        }
762        arr[arrIndex] = arr[childIndex]
763        arrIndex = childIndex
764        childIndex = childIndex * 2 - startIndex + 1
765    }
766    if (childIndex < endIndex) {
767        arr[arrIndex] = arr[childIndex]
768        arrIndex = childIndex
769    }
770    arr[arrIndex] = tmp
771    // Now heap is valid in all positions but arrIndex
772    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot, mustPrecede)
773}
774
775export function heapSort(arr: FixedArray<byte>, startIndex: int, endIndex: int, mustPrecede: (lhs: byte, rhs: byte) => boolean): void {
776    let len = endIndex - startIndex
777    for (let i = len / 2 - 1; i >= 0; i--) {
778        heapSortDown(arr, i, startIndex, endIndex, mustPrecede)
779    }
780
781    for (let i = endIndex - 1; i > startIndex; i--) {
782        // move max element to the end of range
783        const tmp = arr[i]
784        arr[i] = arr[startIndex]
785        arr[startIndex] = tmp
786        heapSortDown(arr, 0, startIndex, i, mustPrecede)
787    }
788}
789
790// Put median of three array elements to arr[index1]
791function median3(arr: FixedArray<byte>, index1: int, index2: int, index3: int, mustPrecede: (lhs: byte, rhs: byte) => boolean): void {
792    let swap_idx = index2
793    if (mustPrecede(arr[index1], arr[index2])) {
794        if (mustPrecede(arr[index3], arr[index1])) {
795            return
796        }
797        if (mustPrecede(arr[index3], arr[index2])) {
798            swap_idx = index3
799        }
800    } else {
801        if (!mustPrecede(arr[index3], arr[index1])) {
802            return
803        }
804        if (mustPrecede(arr[index2], arr[index3])) {
805            swap_idx = index3
806        }
807    }
808    let tmp = arr[index1]
809    arr[index1] = arr[swap_idx]
810    arr[swap_idx] = tmp
811}
812
813// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
814// Elements equal to pivot go to the right
815function quickSortSplit(arr: FixedArray<byte>, startIndex: int, endIndex: int, mustPrecede: (lhs: byte, rhs: byte) => boolean): int {
816    const pivot = arr[startIndex]
817    let i = startIndex + 1
818    let j = endIndex - 1
819    // No bounds check because pivot is median of three elements
820    while (mustPrecede(arr[i], pivot)) {
821        i++
822    }
823    if (i == startIndex + 1) {
824        while (i < j && !mustPrecede(arr[j], pivot)) {
825            j--
826        }
827    } else {
828        while (!mustPrecede(arr[j], pivot)) {
829            j--
830        }
831    }
832    while (i < j) {
833        // Here !mustPrecede(arr[i], pivot) and mustPrecede(arr[j], pivot) holds
834        let tmp = arr[i]
835        arr[i] = arr[j]
836        arr[j] = tmp
837        while (mustPrecede(arr[++i], pivot)) {}
838        while (!mustPrecede(arr[--j], pivot)) {}
839    }
840    let pivotIndex = i - 1
841    arr[startIndex] = arr[pivotIndex]
842    arr[pivotIndex] = pivot
843
844    return pivotIndex
845}
846
847// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
848// Elements equal to pivot go to the left
849function quickSortSplitLeft(arr: FixedArray<byte>, startIndex: int, endIndex: int, mustPrecede: (lhs: byte, rhs: byte) => boolean): int {
850    const pivot = arr[startIndex]
851    let i = startIndex + 1
852    let j = endIndex - 1
853    // No bounds check because pivot is median of three elements
854    while (mustPrecede(pivot, arr[j])) {
855        j--
856    }
857    if (j + 1 == endIndex) {
858        while (i < j && !mustPrecede(pivot, arr[i])) {
859            i++
860        }
861    } else {
862        while (!mustPrecede(pivot, arr[i])) {
863            i++
864        }
865    }
866    while (i < j) {
867        // Here mustPrecede(pivot, arr[i]) and !mustPrecede(pivot, arr[j]) holds
868        let tmp = arr[i]
869        arr[i] = arr[j]
870        arr[j] = tmp
871        while (!mustPrecede(pivot, arr[++i])) {}
872        while (mustPrecede(pivot, arr[--j])) {}
873    }
874    arr[startIndex] = arr[j]
875    arr[j] = pivot
876
877    return j
878}
879
880function quickSortImpl3(arr: FixedArray<byte>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: byte, rhs: byte) => boolean): void {
881    while (endIndex - startIndex > 3) {
882        if (--maxDepth == 0) {
883            heapSort(arr, startIndex, endIndex, mustPrecede)
884            return
885        }
886
887        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
888        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
889        // make a call for the smaller part of array and continue processing the larger part in the loop
890        if (p - startIndex < endIndex - p) {
891            quickSortImpl3(arr, startIndex, p, maxDepth, mustPrecede)
892            startIndex = p + 1
893        } else {
894            quickSortImpl3(arr, p + 1, endIndex, maxDepth, mustPrecede)
895            endIndex = p
896        }
897    }
898    insertionSort(arr, startIndex, endIndex, mustPrecede)
899}
900function quickSortImpl40(arr: FixedArray<byte>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: byte, rhs: byte) => boolean): void {
901    while (endIndex - startIndex > 40) {
902        if (--maxDepth == 0) {
903            heapSort(arr, startIndex, endIndex, mustPrecede)
904            return
905        }
906
907        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
908        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
909        // make a call for the smaller part of array and continue processing the larger part in the loop
910        if (p - startIndex < endIndex - p) {
911            quickSortImpl40(arr, startIndex, p, maxDepth, mustPrecede)
912            startIndex = p + 1
913        } else {
914            quickSortImpl40(arr, p + 1, endIndex, maxDepth, mustPrecede)
915            endIndex = p
916        }
917    }
918    insertionSort(arr, startIndex, endIndex, mustPrecede)
919}
920
921function quickSort(arr: FixedArray<byte>, startIndex: int, endIndex: int, mustPrecede: (lhs: byte, rhs: byte) => boolean): void {
922    let size = endIndex - startIndex
923    if (size <= 1) {
924        return
925    }
926    // find log of length to fall back into determenistic O(n logn) sort
927    let bits = 32
928    for (let i = 2; i < 31; i++) {
929        if ((size >> i) == 0) {
930            bits = i
931            break
932        }
933    }
934    quickSortImpl40(arr, startIndex, endIndex, bits * 3, mustPrecede)
935}
936
937/**
938 * sorts arr in-place
939 *
940 * @param arr an array to sort
941 *
942 * @param startIndex an index to start sorting with, inclusive
943 *
944 * @param endIndex an index to end sorting, exclusive
945 *
946 * @example: sort array arr
947 * ```
948 * sort(arr, 0, arr.length)
949 * ```
950 */
951export function sort_subarray(arr: FixedArray<byte>, startIndex: int, endIndex: int, mustPrecede: (lhs: byte, rhs: byte) => boolean): void {
952    if (!checkRange(arr.length, startIndex, endIndex)) {
953        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
954    }
955
956    quickSort(arr, startIndex, endIndex, mustPrecede);
957}
958
959/**
960 * sorts arr in-place
961 *
962 * @param arr an array to sort
963 */
964export function sort_subarray(arr: FixedArray<byte>, mustPrecede: (lhs: byte, rhs: byte) => boolean): void {
965    sort_subarray(arr, 0, arr.length, mustPrecede);
966}
967
968export function sort_subarray(arr: FixedArray<byte>, startIndex: int, mustPrecede: (lhs: byte, rhs: byte) => boolean): void {
969    sort_subarray(arr, startIndex, arr.length, mustPrecede)
970}
971
972function countSort(arr: FixedArray<byte>, startIndex: int, endIndex: int): void {
973    const cnts : FixedArray<int> = new int[256]
974    for (let i = startIndex; i < endIndex; i++) {
975        cnts[arr[i] + 128]++
976    }
977    let idx = 0
978    for (let i = 0; i < 256; i++) {
979        for (let j = 0; j < cnts[i]; j++) {
980            arr[startIndex + idx++] = (i - 128) as byte
981        }
982    }
983}
984
985// ======== tests section ========
986
987/** note: used for tests, {@link test_sortAllOn} */
988class test_SortDatabyte {
989    name: string
990    arr: FixedArray<byte>
991
992    constructor(name: string, arr: FixedArray<byte>) {
993        this.name = name
994        this.arr = arr
995    }
996}
997
998/** note: used for tests, {@link test_sortAllOn} */
999function test_sortCopy(arr: FixedArray<byte>): FixedArray<byte> {
1000    let c : FixedArray<byte> = new byte[arr.length]
1001    for (let i = 0; i < arr.length; i++) {
1002        c[i] = arr[i]
1003    }
1004    return c
1005}
1006
1007/** note: used for tests, {@link test_sortAllOn} */
1008function test_sortAllOnCmpFwd_byte(l: byte, r: byte): boolean {
1009    return l < r
1010}
1011
1012/** note: used for tests, {@link test_sortAllOn} */
1013function test_sortAllOnCmpInv_byte(l: byte, r: byte): boolean {
1014    return r < l
1015}
1016
1017/** note: used for tests, {@link test_sortAllOn} */
1018function test_printArr(arr: FixedArray<byte>, startIndex: int, endIndex: int) {
1019    for (let i = startIndex; i < endIndex; i++) {
1020        console.print(arr[i] + ' ')
1021    }
1022    console.println('')
1023}
1024
1025/**
1026 * Function used to test sorting in standard library tests.
1027 * There is only one exported function: `sort`, but for testing
1028 * we need access to all sub sorts that are used. Hence this part of "tests"
1029 * is located here. All related entities are prefixed whith `test_` to stand out
1030 */
1031export function test_sortAllOn(arr: FixedArray<byte>) {
1032    // block for comparator ``
1033    if (true) {
1034        const sorts = new Array<test_SortDatabyte>()
1035        const bubbled = test_sortCopy(arr)
1036        bubbleSort(bubbled, 0, bubbled.length)
1037        sorts.push(new test_SortDatabyte("bubble", bubbled))
1038
1039        const insertion = test_sortCopy(arr)
1040        insertionSort(insertion, 0, insertion.length)
1041        sorts.push(new test_SortDatabyte("insertion", insertion))
1042        const cnt = test_sortCopy(arr)
1043        countSort(cnt, 0, cnt.length)
1044        sorts.push(new test_SortDatabyte("cnt()", cnt))
1045
1046        const heaped = test_sortCopy(arr)
1047        heapSort(heaped, 0, heaped.length)
1048        sorts.push(new test_SortDatabyte("heap", heaped))
1049
1050        const quicked = test_sortCopy(arr)
1051        quickSortImpl3(quicked, 0, quicked.length, quicked.length)
1052        sorts.push(new test_SortDatabyte("quick", quicked))
1053
1054        const quickedInsertion = test_sortCopy(arr)
1055        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length)
1056        sorts.push(new test_SortDatabyte("quick with insertion", quickedInsertion))
1057
1058        const just = test_sortCopy(arr)
1059        sort(just)
1060        sorts.push(new test_SortDatabyte("sort()", just))
1061
1062        let sort0 = sorts.at(0)!
1063        for (let s = 1; s < sorts.length; s++) {
1064            let sortS = sorts.at(s)!
1065            for (let i = 0; i < arr.length; i++) {
1066                if (sort0.arr[i] != sortS.arr[i]) {
1067                    console.println(sort0.name + ': ')
1068                    test_printArr(sort0.arr, 0, arr.length)
1069                    console.println(sortS.name + ': ')
1070                    test_printArr(sortS.arr, 0, arr.length)
1071                    throw new Error("sorts are not equal: " + sort0.name + ' ' + sortS.name + ' for byte')
1072                }
1073            }
1074        }
1075    }
1076    // block for comparator `est_sortAllOnCmpFwd_byte`
1077    if (true) {
1078        const sorts = new Array<test_SortDatabyte>()
1079        const bubbled = test_sortCopy(arr)
1080        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpFwd_byte)
1081        sorts.push(new test_SortDatabyte("bubble", bubbled))
1082
1083        const insertion = test_sortCopy(arr)
1084        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpFwd_byte)
1085        sorts.push(new test_SortDatabyte("insertion", insertion))
1086
1087        const heaped = test_sortCopy(arr)
1088        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpFwd_byte)
1089        sorts.push(new test_SortDatabyte("heap", heaped))
1090
1091        const quicked = test_sortCopy(arr)
1092        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpFwd_byte)
1093        sorts.push(new test_SortDatabyte("quick", quicked))
1094
1095        const quickedInsertion = test_sortCopy(arr)
1096        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpFwd_byte)
1097        sorts.push(new test_SortDatabyte("quick with insertion", quickedInsertion))
1098
1099        const just = test_sortCopy(arr)
1100        sort_subarray(just, test_sortAllOnCmpFwd_byte)
1101        sorts.push(new test_SortDatabyte("sort()", just))
1102
1103        let sort0 = sorts.at(0)!
1104        for (let s = 1; s < sorts.length; s++) {
1105            let sortS = sorts.at(s)!
1106            for (let i = 0; i < arr.length; i++) {
1107                if (sort0.arr[i] != sortS.arr[i]) {
1108                    console.println(sort0.name + ': ')
1109                    test_printArr(sort0.arr, 0, arr.length)
1110                    console.println(sortS.name + ': ')
1111                    test_printArr(sortS.arr, 0, arr.length)
1112                    throw new Error("sorts est_sortAllOnCmpFwd_byte are not equal: " + sort0.name + ' ' + sortS.name + ' for byte')
1113                }
1114            }
1115        }
1116    }
1117    // block for comparator `est_sortAllOnCmpInv_byte`
1118    if (true) {
1119        const sorts = new Array<test_SortDatabyte>()
1120        const bubbled = test_sortCopy(arr)
1121        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpInv_byte)
1122        sorts.push(new test_SortDatabyte("bubble", bubbled))
1123
1124        const insertion = test_sortCopy(arr)
1125        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpInv_byte)
1126        sorts.push(new test_SortDatabyte("insertion", insertion))
1127
1128        const heaped = test_sortCopy(arr)
1129        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpInv_byte)
1130        sorts.push(new test_SortDatabyte("heap", heaped))
1131
1132        const quicked = test_sortCopy(arr)
1133        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpInv_byte)
1134        sorts.push(new test_SortDatabyte("quick", quicked))
1135
1136        const quickedInsertion = test_sortCopy(arr)
1137        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpInv_byte)
1138        sorts.push(new test_SortDatabyte("quick with insertion", quickedInsertion))
1139
1140        const just = test_sortCopy(arr)
1141        sort_subarray(just, test_sortAllOnCmpInv_byte)
1142        sorts.push(new test_SortDatabyte("sort()", just))
1143
1144        let sort0 = sorts.at(0)!
1145        for (let s = 1; s < sorts.length; s++) {
1146            let sortS = sorts.at(s)!
1147            for (let i = 0; i < arr.length; i++) {
1148                if (sort0.arr[i] != sortS.arr[i]) {
1149                    console.println(sort0.name + ': ')
1150                    test_printArr(sort0.arr, 0, arr.length)
1151                    console.println(sortS.name + ': ')
1152                    test_printArr(sortS.arr, 0, arr.length)
1153                    throw new Error("sorts est_sortAllOnCmpInv_byte are not equal: " + sort0.name + ' ' + sortS.name + ' for byte')
1154                }
1155            }
1156        }
1157    }
1158}
1159// ======== end of tests section ========
1160
1161function copyPart(dst: FixedArray<short>, counter: int, src: FixedArray<short>, start: int, end?: int) {
1162    if (end == undefined) {
1163        end = src.length
1164    }
1165    for (let i = start; i < end!; ++i) {
1166        dst[counter++] = src[i]
1167    }
1168}
1169
1170function merge(left: FixedArray<short>, right: FixedArray<short>, cmp: (lhs: short, rhs: short) => number): FixedArray<short> {
1171    const result:FixedArray<short> = new short[right.length + left.length]
1172    let leftIndex = 0;
1173    let rightIndex = 0;
1174    let counter: int = 0
1175
1176    while (leftIndex < left.length &&
1177        rightIndex < right.length) {
1178        if (cmp(left[leftIndex], right[rightIndex]) <= 0) {
1179            result[counter++] = left[leftIndex];
1180            leftIndex++;
1181        } else {
1182            result[counter++] = right[rightIndex]
1183            rightIndex++;
1184        }
1185    }
1186    copyPart(result, counter, left, leftIndex)
1187    copyPart(result, counter, right, rightIndex)
1188
1189    return result
1190}
1191
1192export function mergeSort(array: FixedArray<short>, cmp: (lhs: short, rhs: short) => number, begin: int = 0, end: int = 0): FixedArray<short> {
1193    if (end == 0) {
1194        end = array.length
1195    }
1196    const arrLength = end - begin
1197    if (arrLength <= 1) {
1198        return array;
1199    }
1200    const middle = Math.floor(begin + arrLength / 2) as int
1201    const leftHalf:FixedArray<short> = new short[middle]
1202    let counter: int = 0
1203    copyPart(leftHalf, counter, array, 0, middle)
1204
1205    counter = 0
1206    const rightHalf:FixedArray<short> = new short[arrLength - middle]
1207    copyPart(rightHalf, counter, array, middle)
1208
1209    return merge(mergeSort(leftHalf, cmp), mergeSort(rightHalf, cmp), cmp);
1210}
1211
1212function bubbleSort(arr: FixedArray<short>, startIndex: int, endIndex: int): void {
1213    let was = true
1214    while (was) {
1215        was = false
1216        for (let i = startIndex; i < endIndex - 1; i++) {
1217            if ((arr[i + 1] < arr[i])) {
1218                const tmp = arr[i + 1]
1219                arr[i + 1] = arr[i]
1220                arr[i] = tmp
1221                was = true
1222            }
1223        }
1224    }
1225}
1226
1227function insertionSort(arr: FixedArray<short>, startIndex: int, endIndex: int, initIndex: int = startIndex): void {
1228    if (startIndex != initIndex) {
1229        // arr[startIndex - 1] exists and is less than or equal to all elements in range
1230        for (let i = startIndex + 1; i < endIndex; i++) {
1231            const tmp = arr[i]
1232            let pos = i
1233            while ((tmp < arr[pos - 1])) {
1234                arr[pos] = arr[pos - 1]
1235                pos--
1236            }
1237            arr[pos] = tmp
1238        }
1239        return
1240    }
1241    for (let i = startIndex + 1; i < endIndex; i++) {
1242        const tmp = arr[i]
1243        if ((tmp < arr[startIndex])) {
1244            for (let j = i; j > startIndex; j--) {
1245                arr[j] = arr[j - 1]
1246            }
1247            arr[startIndex] = tmp
1248        } else {
1249            let pos = i
1250            while ((tmp < arr[pos - 1])) {
1251                arr[pos] = arr[pos - 1]
1252                pos--
1253            }
1254            arr[pos] = tmp
1255        }
1256    }
1257}
1258function heapSortUp(arr: FixedArray<short>, idxFromStart: int, startIndex: int, heapRoot: int): void {
1259    const tmp = arr[startIndex + idxFromStart]
1260    while (startIndex + idxFromStart > heapRoot) {
1261        const p = (idxFromStart - 1) / 2
1262        if (!(arr[startIndex + p] < tmp)) {
1263            break
1264        }
1265        arr[startIndex + idxFromStart] = arr[startIndex + p]
1266        idxFromStart = p
1267    }
1268    arr[startIndex + idxFromStart] = tmp
1269}
1270
1271// Build max heap with root in startIndex given its children are roots of valid heaps
1272function heapSortDown(arr: FixedArray<short>, idxFromStart: int, startIndex: int, endIndex: int): void {
1273    let heapRoot = startIndex + idxFromStart
1274    let arrIndex = heapRoot
1275    let childIndex = startIndex + idxFromStart * 2 + 1
1276    const tmp = arr[arrIndex]
1277    // Walk heap to bottom and pull max child up on each level
1278    while (childIndex + 1 < endIndex) {
1279        if ((arr[childIndex] < arr[childIndex + 1])) {
1280            childIndex++
1281        }
1282        arr[arrIndex] = arr[childIndex]
1283        arrIndex = childIndex
1284        childIndex = childIndex * 2 - startIndex + 1
1285    }
1286    if (childIndex < endIndex) {
1287        arr[arrIndex] = arr[childIndex]
1288        arrIndex = childIndex
1289    }
1290    arr[arrIndex] = tmp
1291    // Now heap is valid in all positions but arrIndex
1292    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot)
1293}
1294
1295export function heapSort(arr: FixedArray<short>, startIndex: int, endIndex: int): void {
1296    let len = endIndex - startIndex
1297    for (let i = len / 2 - 1; i >= 0; i--) {
1298        heapSortDown(arr, i, startIndex, endIndex)
1299    }
1300
1301    for (let i = endIndex - 1; i > startIndex; i--) {
1302        // move max element to the end of range
1303        const tmp = arr[i]
1304        arr[i] = arr[startIndex]
1305        arr[startIndex] = tmp
1306        heapSortDown(arr, 0, startIndex, i)
1307    }
1308}
1309
1310// Put median of three array elements to arr[index1]
1311function median3(arr: FixedArray<short>, index1: int, index2: int, index3: int): void {
1312    let swap_idx = index2
1313    if ((arr[index1] < arr[index2])) {
1314        if ((arr[index3] < arr[index1])) {
1315            return
1316        }
1317        if ((arr[index3] < arr[index2])) {
1318            swap_idx = index3
1319        }
1320    } else {
1321        if (!(arr[index3] < arr[index1])) {
1322            return
1323        }
1324        if ((arr[index2] < arr[index3])) {
1325            swap_idx = index3
1326        }
1327    }
1328    let tmp = arr[index1]
1329    arr[index1] = arr[swap_idx]
1330    arr[swap_idx] = tmp
1331}
1332
1333// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
1334// Elements equal to pivot go to the right
1335function quickSortSplit(arr: FixedArray<short>, startIndex: int, endIndex: int): int {
1336    const pivot = arr[startIndex]
1337    let i = startIndex + 1
1338    let j = endIndex - 1
1339    // No bounds check because pivot is median of three elements
1340    while ((arr[i] < pivot)) {
1341        i++
1342    }
1343    if (i == startIndex + 1) {
1344        while (i < j && !(arr[j] < pivot)) {
1345            j--
1346        }
1347    } else {
1348        while (!(arr[j] < pivot)) {
1349            j--
1350        }
1351    }
1352    while (i < j) {
1353        // Here !(arr[i] < pivot) and (arr[j] < pivot) holds
1354        let tmp = arr[i]
1355        arr[i] = arr[j]
1356        arr[j] = tmp
1357        while ((arr[++i] < pivot)) {}
1358        while (!(arr[--j] < pivot)) {}
1359    }
1360    let pivotIndex = i - 1
1361    arr[startIndex] = arr[pivotIndex]
1362    arr[pivotIndex] = pivot
1363
1364    return pivotIndex
1365}
1366
1367// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
1368// Elements equal to pivot go to the left
1369function quickSortSplitLeft(arr: FixedArray<short>, startIndex: int, endIndex: int): int {
1370    const pivot = arr[startIndex]
1371    let i = startIndex + 1
1372    let j = endIndex - 1
1373    // No bounds check because pivot is median of three elements
1374    while ((pivot < arr[j])) {
1375        j--
1376    }
1377    if (j + 1 == endIndex) {
1378        while (i < j && !(pivot < arr[i])) {
1379            i++
1380        }
1381    } else {
1382        while (!(pivot < arr[i])) {
1383            i++
1384        }
1385    }
1386    while (i < j) {
1387        // Here (pivot < arr[i]) and !(pivot < arr[j]) holds
1388        let tmp = arr[i]
1389        arr[i] = arr[j]
1390        arr[j] = tmp
1391        while (!(pivot < arr[++i])) {}
1392        while ((pivot < arr[--j])) {}
1393    }
1394    arr[startIndex] = arr[j]
1395    arr[j] = pivot
1396
1397    return j
1398}
1399
1400function quickSortImpl3(arr: FixedArray<short>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
1401    while (endIndex - startIndex > 3) {
1402        if (--maxDepth == 0) {
1403            heapSort(arr, startIndex, endIndex)
1404            return
1405        }
1406        // Here we assume that current interval is not the most left in the sorted range
1407        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
1408            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
1409            // after that only the right part needs to be sorted
1410            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
1411            // with many occurencies of the smallest element
1412            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
1413            continue
1414        }
1415
1416        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
1417        let p = quickSortSplit(arr, startIndex, endIndex)
1418        // make a call for the smaller part of array and continue processing the larger part in the loop
1419        if (p - startIndex < endIndex - p) {
1420            quickSortImpl3(arr, startIndex, p, maxDepth, initIndex)
1421            startIndex = p + 1
1422        } else {
1423            quickSortImpl3(arr, p + 1, endIndex, maxDepth, initIndex)
1424            endIndex = p
1425        }
1426    }
1427    insertionSort(arr, startIndex, endIndex, initIndex)
1428}
1429function quickSortImpl40(arr: FixedArray<short>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
1430    while (endIndex - startIndex > 40) {
1431        if (--maxDepth == 0) {
1432            heapSort(arr, startIndex, endIndex)
1433            return
1434        }
1435        // Here we assume that current interval is not the most left in the sorted range
1436        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
1437            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
1438            // after that only the right part needs to be sorted
1439            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
1440            // with many occurencies of the smallest element
1441            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
1442            continue
1443        }
1444
1445        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
1446        let p = quickSortSplit(arr, startIndex, endIndex)
1447        // make a call for the smaller part of array and continue processing the larger part in the loop
1448        if (p - startIndex < endIndex - p) {
1449            quickSortImpl40(arr, startIndex, p, maxDepth, initIndex)
1450            startIndex = p + 1
1451        } else {
1452            quickSortImpl40(arr, p + 1, endIndex, maxDepth, initIndex)
1453            endIndex = p
1454        }
1455    }
1456    insertionSort(arr, startIndex, endIndex, initIndex)
1457}
1458
1459function quickSort(arr: FixedArray<short>, startIndex: int, endIndex: int): void {
1460    let size = endIndex - startIndex
1461    if (size <= 1) {
1462        return
1463    }
1464    // find log of length to fall back into determenistic O(n logn) sort
1465    let bits = 32
1466    for (let i = 2; i < 31; i++) {
1467        if ((size >> i) == 0) {
1468            bits = i
1469            break
1470        }
1471    }
1472    quickSortImpl40(arr, startIndex, endIndex, bits * 3)
1473}
1474
1475/**
1476 * sorts arr in-place
1477 *
1478 * @param arr an array to sort
1479 *
1480 * @param startIndex an index to start sorting with, inclusive
1481 *
1482 * @param endIndex an index to end sorting, exclusive
1483 *
1484 * @example: sort array arr
1485 * ```
1486 * sort(arr, 0, arr.length)
1487 * ```
1488 */
1489export function sort(arr: FixedArray<short>, startIndex: int, endIndex: int): void {
1490    if (!checkRange(arr.length, startIndex, endIndex)) {
1491        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
1492    }
1493
1494    quickSort(arr, startIndex, endIndex);
1495}
1496
1497function bubbleSort(arr: FixedArray<short>, startIndex: int, endIndex: int, mustPrecede: (lhs: short, rhs: short) => boolean): void {
1498    let was = true
1499    while (was) {
1500        was = false
1501        for (let i = startIndex; i < endIndex - 1; i++) {
1502            if (mustPrecede(arr[i + 1], arr[i])) {
1503                const tmp = arr[i + 1]
1504                arr[i + 1] = arr[i]
1505                arr[i] = tmp
1506                was = true
1507            }
1508        }
1509    }
1510}
1511
1512function insertionSort(arr: FixedArray<short>, startIndex: int, endIndex: int, mustPrecede: (lhs: short, rhs: short) => boolean, initIndex: int = startIndex): void {
1513    if (startIndex != initIndex) {
1514        // arr[startIndex - 1] exists and is less than or equal to all elements in range
1515        for (let i = startIndex + 1; i < endIndex; i++) {
1516            const tmp = arr[i]
1517            let pos = i
1518            while (mustPrecede(tmp, arr[pos - 1])) {
1519                arr[pos] = arr[pos - 1]
1520                pos--
1521            }
1522            arr[pos] = tmp
1523        }
1524        return
1525    }
1526    for (let i = startIndex + 1; i < endIndex; i++) {
1527        const tmp = arr[i]
1528        if (mustPrecede(tmp, arr[startIndex])) {
1529            for (let j = i; j > startIndex; j--) {
1530                arr[j] = arr[j - 1]
1531            }
1532            arr[startIndex] = tmp
1533        } else {
1534            let pos = i
1535            while (mustPrecede(tmp, arr[pos - 1])) {
1536                arr[pos] = arr[pos - 1]
1537                pos--
1538            }
1539            arr[pos] = tmp
1540        }
1541    }
1542}
1543function heapSortUp(arr: FixedArray<short>, idxFromStart: int, startIndex: int, heapRoot: int, mustPrecede: (lhs: short, rhs: short) => boolean): void {
1544    const tmp = arr[startIndex + idxFromStart]
1545    while (startIndex + idxFromStart > heapRoot) {
1546        const p = (idxFromStart - 1) / 2
1547        if (!mustPrecede(arr[startIndex + p], tmp)) {
1548            break
1549        }
1550        arr[startIndex + idxFromStart] = arr[startIndex + p]
1551        idxFromStart = p
1552    }
1553    arr[startIndex + idxFromStart] = tmp
1554}
1555
1556// Build max heap with root in startIndex given its children are roots of valid heaps
1557function heapSortDown(arr: FixedArray<short>, idxFromStart: int, startIndex: int, endIndex: int, mustPrecede: (lhs: short, rhs: short) => boolean): void {
1558    let heapRoot = startIndex + idxFromStart
1559    let arrIndex = heapRoot
1560    let childIndex = startIndex + idxFromStart * 2 + 1
1561    const tmp = arr[arrIndex]
1562    // Walk heap to bottom and pull max child up on each level
1563    while (childIndex + 1 < endIndex) {
1564        if (mustPrecede(arr[childIndex], arr[childIndex + 1])) {
1565            childIndex++
1566        }
1567        arr[arrIndex] = arr[childIndex]
1568        arrIndex = childIndex
1569        childIndex = childIndex * 2 - startIndex + 1
1570    }
1571    if (childIndex < endIndex) {
1572        arr[arrIndex] = arr[childIndex]
1573        arrIndex = childIndex
1574    }
1575    arr[arrIndex] = tmp
1576    // Now heap is valid in all positions but arrIndex
1577    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot, mustPrecede)
1578}
1579
1580export function heapSort(arr: FixedArray<short>, startIndex: int, endIndex: int, mustPrecede: (lhs: short, rhs: short) => boolean): void {
1581    let len = endIndex - startIndex
1582    for (let i = len / 2 - 1; i >= 0; i--) {
1583        heapSortDown(arr, i, startIndex, endIndex, mustPrecede)
1584    }
1585
1586    for (let i = endIndex - 1; i > startIndex; i--) {
1587        // move max element to the end of range
1588        const tmp = arr[i]
1589        arr[i] = arr[startIndex]
1590        arr[startIndex] = tmp
1591        heapSortDown(arr, 0, startIndex, i, mustPrecede)
1592    }
1593}
1594
1595// Put median of three array elements to arr[index1]
1596function median3(arr: FixedArray<short>, index1: int, index2: int, index3: int, mustPrecede: (lhs: short, rhs: short) => boolean): void {
1597    let swap_idx = index2
1598    if (mustPrecede(arr[index1], arr[index2])) {
1599        if (mustPrecede(arr[index3], arr[index1])) {
1600            return
1601        }
1602        if (mustPrecede(arr[index3], arr[index2])) {
1603            swap_idx = index3
1604        }
1605    } else {
1606        if (!mustPrecede(arr[index3], arr[index1])) {
1607            return
1608        }
1609        if (mustPrecede(arr[index2], arr[index3])) {
1610            swap_idx = index3
1611        }
1612    }
1613    let tmp = arr[index1]
1614    arr[index1] = arr[swap_idx]
1615    arr[swap_idx] = tmp
1616}
1617
1618// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
1619// Elements equal to pivot go to the right
1620function quickSortSplit(arr: FixedArray<short>, startIndex: int, endIndex: int, mustPrecede: (lhs: short, rhs: short) => boolean): int {
1621    const pivot = arr[startIndex]
1622    let i = startIndex + 1
1623    let j = endIndex - 1
1624    // No bounds check because pivot is median of three elements
1625    while (mustPrecede(arr[i], pivot)) {
1626        i++
1627    }
1628    if (i == startIndex + 1) {
1629        while (i < j && !mustPrecede(arr[j], pivot)) {
1630            j--
1631        }
1632    } else {
1633        while (!mustPrecede(arr[j], pivot)) {
1634            j--
1635        }
1636    }
1637    while (i < j) {
1638        // Here !mustPrecede(arr[i], pivot) and mustPrecede(arr[j], pivot) holds
1639        let tmp = arr[i]
1640        arr[i] = arr[j]
1641        arr[j] = tmp
1642        while (mustPrecede(arr[++i], pivot)) {}
1643        while (!mustPrecede(arr[--j], pivot)) {}
1644    }
1645    let pivotIndex = i - 1
1646    arr[startIndex] = arr[pivotIndex]
1647    arr[pivotIndex] = pivot
1648
1649    return pivotIndex
1650}
1651
1652// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
1653// Elements equal to pivot go to the left
1654function quickSortSplitLeft(arr: FixedArray<short>, startIndex: int, endIndex: int, mustPrecede: (lhs: short, rhs: short) => boolean): int {
1655    const pivot = arr[startIndex]
1656    let i = startIndex + 1
1657    let j = endIndex - 1
1658    // No bounds check because pivot is median of three elements
1659    while (mustPrecede(pivot, arr[j])) {
1660        j--
1661    }
1662    if (j + 1 == endIndex) {
1663        while (i < j && !mustPrecede(pivot, arr[i])) {
1664            i++
1665        }
1666    } else {
1667        while (!mustPrecede(pivot, arr[i])) {
1668            i++
1669        }
1670    }
1671    while (i < j) {
1672        // Here mustPrecede(pivot, arr[i]) and !mustPrecede(pivot, arr[j]) holds
1673        let tmp = arr[i]
1674        arr[i] = arr[j]
1675        arr[j] = tmp
1676        while (!mustPrecede(pivot, arr[++i])) {}
1677        while (mustPrecede(pivot, arr[--j])) {}
1678    }
1679    arr[startIndex] = arr[j]
1680    arr[j] = pivot
1681
1682    return j
1683}
1684
1685function quickSortImpl3(arr: FixedArray<short>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: short, rhs: short) => boolean): void {
1686    while (endIndex - startIndex > 3) {
1687        if (--maxDepth == 0) {
1688            heapSort(arr, startIndex, endIndex, mustPrecede)
1689            return
1690        }
1691
1692        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
1693        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
1694        // make a call for the smaller part of array and continue processing the larger part in the loop
1695        if (p - startIndex < endIndex - p) {
1696            quickSortImpl3(arr, startIndex, p, maxDepth, mustPrecede)
1697            startIndex = p + 1
1698        } else {
1699            quickSortImpl3(arr, p + 1, endIndex, maxDepth, mustPrecede)
1700            endIndex = p
1701        }
1702    }
1703    insertionSort(arr, startIndex, endIndex, mustPrecede)
1704}
1705function quickSortImpl40(arr: FixedArray<short>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: short, rhs: short) => boolean): void {
1706    while (endIndex - startIndex > 40) {
1707        if (--maxDepth == 0) {
1708            heapSort(arr, startIndex, endIndex, mustPrecede)
1709            return
1710        }
1711
1712        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
1713        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
1714        // make a call for the smaller part of array and continue processing the larger part in the loop
1715        if (p - startIndex < endIndex - p) {
1716            quickSortImpl40(arr, startIndex, p, maxDepth, mustPrecede)
1717            startIndex = p + 1
1718        } else {
1719            quickSortImpl40(arr, p + 1, endIndex, maxDepth, mustPrecede)
1720            endIndex = p
1721        }
1722    }
1723    insertionSort(arr, startIndex, endIndex, mustPrecede)
1724}
1725
1726function quickSort(arr: FixedArray<short>, startIndex: int, endIndex: int, mustPrecede: (lhs: short, rhs: short) => boolean): void {
1727    let size = endIndex - startIndex
1728    if (size <= 1) {
1729        return
1730    }
1731    // find log of length to fall back into determenistic O(n logn) sort
1732    let bits = 32
1733    for (let i = 2; i < 31; i++) {
1734        if ((size >> i) == 0) {
1735            bits = i
1736            break
1737        }
1738    }
1739    quickSortImpl40(arr, startIndex, endIndex, bits * 3, mustPrecede)
1740}
1741
1742/**
1743 * sorts arr in-place
1744 *
1745 * @param arr an array to sort
1746 *
1747 * @param startIndex an index to start sorting with, inclusive
1748 *
1749 * @param endIndex an index to end sorting, exclusive
1750 *
1751 * @example: sort array arr
1752 * ```
1753 * sort(arr, 0, arr.length)
1754 * ```
1755 */
1756export function sort_subarray(arr: FixedArray<short>, startIndex: int, endIndex: int, mustPrecede: (lhs: short, rhs: short) => boolean): void {
1757    if (!checkRange(arr.length, startIndex, endIndex)) {
1758        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
1759    }
1760
1761    quickSort(arr, startIndex, endIndex, mustPrecede);
1762}
1763
1764/**
1765 * sorts arr in-place
1766 *
1767 * @param arr an array to sort
1768 */
1769export function sort_subarray(arr: FixedArray<short>, mustPrecede: (lhs: short, rhs: short) => boolean): void {
1770    sort_subarray(arr, 0, arr.length, mustPrecede);
1771}
1772
1773export function sort_subarray(arr: FixedArray<short>, startIndex: int, mustPrecede: (lhs: short, rhs: short) => boolean): void {
1774    sort_subarray(arr, startIndex, arr.length, mustPrecede)
1775}
1776
1777// ======== tests section ========
1778
1779/** note: used for tests, {@link test_sortAllOn} */
1780class test_SortDatashort {
1781    name: string
1782    arr: FixedArray<short>
1783
1784    constructor(name: string, arr: FixedArray<short>) {
1785        this.name = name
1786        this.arr = arr
1787    }
1788}
1789
1790/** note: used for tests, {@link test_sortAllOn} */
1791function test_sortCopy(arr: FixedArray<short>): FixedArray<short> {
1792    let c : FixedArray<short> = new short[arr.length]
1793    for (let i = 0; i < arr.length; i++) {
1794        c[i] = arr[i]
1795    }
1796    return c
1797}
1798
1799/** note: used for tests, {@link test_sortAllOn} */
1800function test_sortAllOnCmpFwd_short(l: short, r: short): boolean {
1801    return l < r
1802}
1803
1804/** note: used for tests, {@link test_sortAllOn} */
1805function test_sortAllOnCmpInv_short(l: short, r: short): boolean {
1806    return r < l
1807}
1808
1809/** note: used for tests, {@link test_sortAllOn} */
1810function test_printArr(arr: FixedArray<short>, startIndex: int, endIndex: int) {
1811    for (let i = startIndex; i < endIndex; i++) {
1812        console.print(arr[i] + ' ')
1813    }
1814    console.println('')
1815}
1816
1817/**
1818 * Function used to test sorting in standard library tests.
1819 * There is only one exported function: `sort`, but for testing
1820 * we need access to all sub sorts that are used. Hence this part of "tests"
1821 * is located here. All related entities are prefixed whith `test_` to stand out
1822 */
1823export function test_sortAllOn(arr: FixedArray<short>) {
1824    // block for comparator ``
1825    if (true) {
1826        const sorts = new Array<test_SortDatashort>()
1827        const bubbled = test_sortCopy(arr)
1828        bubbleSort(bubbled, 0, bubbled.length)
1829        sorts.push(new test_SortDatashort("bubble", bubbled))
1830
1831        const insertion = test_sortCopy(arr)
1832        insertionSort(insertion, 0, insertion.length)
1833        sorts.push(new test_SortDatashort("insertion", insertion))
1834
1835        const heaped = test_sortCopy(arr)
1836        heapSort(heaped, 0, heaped.length)
1837        sorts.push(new test_SortDatashort("heap", heaped))
1838
1839        const quicked = test_sortCopy(arr)
1840        quickSortImpl3(quicked, 0, quicked.length, quicked.length)
1841        sorts.push(new test_SortDatashort("quick", quicked))
1842
1843        const quickedInsertion = test_sortCopy(arr)
1844        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length)
1845        sorts.push(new test_SortDatashort("quick with insertion", quickedInsertion))
1846
1847        const just = test_sortCopy(arr)
1848        sort(just)
1849        sorts.push(new test_SortDatashort("sort()", just))
1850
1851        let sort0 = sorts.at(0)!
1852        for (let s = 1; s < sorts.length; s++) {
1853            let sortS = sorts.at(s)!
1854            for (let i = 0; i < arr.length; i++) {
1855                if (sort0.arr[i] != sortS.arr[i]) {
1856                    console.println(sort0.name + ': ')
1857                    test_printArr(sort0.arr, 0, arr.length)
1858                    console.println(sortS.name + ': ')
1859                    test_printArr(sortS.arr, 0, arr.length)
1860                    throw new Error("sorts are not equal: " + sort0.name + ' ' + sortS.name + ' for short')
1861                }
1862            }
1863        }
1864    }
1865    // block for comparator `est_sortAllOnCmpFwd_short`
1866    if (true) {
1867        const sorts = new Array<test_SortDatashort>()
1868        const bubbled = test_sortCopy(arr)
1869        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpFwd_short)
1870        sorts.push(new test_SortDatashort("bubble", bubbled))
1871
1872        const insertion = test_sortCopy(arr)
1873        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpFwd_short)
1874        sorts.push(new test_SortDatashort("insertion", insertion))
1875
1876        const heaped = test_sortCopy(arr)
1877        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpFwd_short)
1878        sorts.push(new test_SortDatashort("heap", heaped))
1879
1880        const quicked = test_sortCopy(arr)
1881        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpFwd_short)
1882        sorts.push(new test_SortDatashort("quick", quicked))
1883
1884        const quickedInsertion = test_sortCopy(arr)
1885        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpFwd_short)
1886        sorts.push(new test_SortDatashort("quick with insertion", quickedInsertion))
1887
1888        const just = test_sortCopy(arr)
1889        sort_subarray(just, test_sortAllOnCmpFwd_short)
1890        sorts.push(new test_SortDatashort("sort()", just))
1891
1892        let sort0 = sorts.at(0)!
1893        for (let s = 1; s < sorts.length; s++) {
1894            let sortS = sorts.at(s)!
1895            for (let i = 0; i < arr.length; i++) {
1896                if (sort0.arr[i] != sortS.arr[i]) {
1897                    console.println(sort0.name + ': ')
1898                    test_printArr(sort0.arr, 0, arr.length)
1899                    console.println(sortS.name + ': ')
1900                    test_printArr(sortS.arr, 0, arr.length)
1901                    throw new Error("sorts est_sortAllOnCmpFwd_short are not equal: " + sort0.name + ' ' + sortS.name + ' for short')
1902                }
1903            }
1904        }
1905    }
1906    // block for comparator `est_sortAllOnCmpInv_short`
1907    if (true) {
1908        const sorts = new Array<test_SortDatashort>()
1909        const bubbled = test_sortCopy(arr)
1910        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpInv_short)
1911        sorts.push(new test_SortDatashort("bubble", bubbled))
1912
1913        const insertion = test_sortCopy(arr)
1914        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpInv_short)
1915        sorts.push(new test_SortDatashort("insertion", insertion))
1916
1917        const heaped = test_sortCopy(arr)
1918        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpInv_short)
1919        sorts.push(new test_SortDatashort("heap", heaped))
1920
1921        const quicked = test_sortCopy(arr)
1922        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpInv_short)
1923        sorts.push(new test_SortDatashort("quick", quicked))
1924
1925        const quickedInsertion = test_sortCopy(arr)
1926        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpInv_short)
1927        sorts.push(new test_SortDatashort("quick with insertion", quickedInsertion))
1928
1929        const just = test_sortCopy(arr)
1930        sort_subarray(just, test_sortAllOnCmpInv_short)
1931        sorts.push(new test_SortDatashort("sort()", just))
1932
1933        let sort0 = sorts.at(0)!
1934        for (let s = 1; s < sorts.length; s++) {
1935            let sortS = sorts.at(s)!
1936            for (let i = 0; i < arr.length; i++) {
1937                if (sort0.arr[i] != sortS.arr[i]) {
1938                    console.println(sort0.name + ': ')
1939                    test_printArr(sort0.arr, 0, arr.length)
1940                    console.println(sortS.name + ': ')
1941                    test_printArr(sortS.arr, 0, arr.length)
1942                    throw new Error("sorts est_sortAllOnCmpInv_short are not equal: " + sort0.name + ' ' + sortS.name + ' for short')
1943                }
1944            }
1945        }
1946    }
1947}
1948// ======== end of tests section ========
1949
1950function copyPart(dst: FixedArray<int>, counter: int, src: FixedArray<int>, start: int, end?: int) {
1951    if (end == undefined) {
1952        end = src.length
1953    }
1954    for (let i = start; i < end!; ++i) {
1955        dst[counter++] = src[i]
1956    }
1957}
1958
1959function merge(left: FixedArray<int>, right: FixedArray<int>, cmp: (lhs: int, rhs: int) => number): FixedArray<int> {
1960    const result:FixedArray<int> = new int[right.length + left.length]
1961    let leftIndex = 0;
1962    let rightIndex = 0;
1963    let counter: int = 0
1964
1965    while (leftIndex < left.length &&
1966        rightIndex < right.length) {
1967        if (cmp(left[leftIndex], right[rightIndex]) <= 0) {
1968            result[counter++] = left[leftIndex];
1969            leftIndex++;
1970        } else {
1971            result[counter++] = right[rightIndex]
1972            rightIndex++;
1973        }
1974    }
1975    copyPart(result, counter, left, leftIndex)
1976    copyPart(result, counter, right, rightIndex)
1977
1978    return result
1979}
1980
1981export function mergeSort(array: FixedArray<int>, cmp: (lhs: int, rhs: int) => number, begin: int = 0, end: int = 0): FixedArray<int> {
1982    if (end == 0) {
1983        end = array.length
1984    }
1985    const arrLength = end - begin
1986    if (arrLength <= 1) {
1987        return array;
1988    }
1989    const middle = Math.floor(begin + arrLength / 2) as int
1990    const leftHalf:FixedArray<int> = new int[middle]
1991    let counter: int = 0
1992    copyPart(leftHalf, counter, array, 0, middle)
1993
1994    counter = 0
1995    const rightHalf:FixedArray<int> = new int[arrLength - middle]
1996    copyPart(rightHalf, counter, array, middle)
1997
1998    return merge(mergeSort(leftHalf, cmp), mergeSort(rightHalf, cmp), cmp);
1999}
2000
2001function bubbleSort(arr: FixedArray<int>, startIndex: int, endIndex: int): void {
2002    let was = true
2003    while (was) {
2004        was = false
2005        for (let i = startIndex; i < endIndex - 1; i++) {
2006            if ((arr[i + 1] < arr[i])) {
2007                const tmp = arr[i + 1]
2008                arr[i + 1] = arr[i]
2009                arr[i] = tmp
2010                was = true
2011            }
2012        }
2013    }
2014}
2015
2016function insertionSort(arr: FixedArray<int>, startIndex: int, endIndex: int, initIndex: int = startIndex): void {
2017    if (startIndex != initIndex) {
2018        // arr[startIndex - 1] exists and is less than or equal to all elements in range
2019        for (let i = startIndex + 1; i < endIndex; i++) {
2020            const tmp = arr[i]
2021            let pos = i
2022            while ((tmp < arr[pos - 1])) {
2023                arr[pos] = arr[pos - 1]
2024                pos--
2025            }
2026            arr[pos] = tmp
2027        }
2028        return
2029    }
2030    for (let i = startIndex + 1; i < endIndex; i++) {
2031        const tmp = arr[i]
2032        if ((tmp < arr[startIndex])) {
2033            for (let j = i; j > startIndex; j--) {
2034                arr[j] = arr[j - 1]
2035            }
2036            arr[startIndex] = tmp
2037        } else {
2038            let pos = i
2039            while ((tmp < arr[pos - 1])) {
2040                arr[pos] = arr[pos - 1]
2041                pos--
2042            }
2043            arr[pos] = tmp
2044        }
2045    }
2046}
2047function heapSortUp(arr: FixedArray<int>, idxFromStart: int, startIndex: int, heapRoot: int): void {
2048    const tmp = arr[startIndex + idxFromStart]
2049    while (startIndex + idxFromStart > heapRoot) {
2050        const p = (idxFromStart - 1) / 2
2051        if (!(arr[startIndex + p] < tmp)) {
2052            break
2053        }
2054        arr[startIndex + idxFromStart] = arr[startIndex + p]
2055        idxFromStart = p
2056    }
2057    arr[startIndex + idxFromStart] = tmp
2058}
2059
2060// Build max heap with root in startIndex given its children are roots of valid heaps
2061function heapSortDown(arr: FixedArray<int>, idxFromStart: int, startIndex: int, endIndex: int): void {
2062    let heapRoot = startIndex + idxFromStart
2063    let arrIndex = heapRoot
2064    let childIndex = startIndex + idxFromStart * 2 + 1
2065    const tmp = arr[arrIndex]
2066    // Walk heap to bottom and pull max child up on each level
2067    while (childIndex + 1 < endIndex) {
2068        if ((arr[childIndex] < arr[childIndex + 1])) {
2069            childIndex++
2070        }
2071        arr[arrIndex] = arr[childIndex]
2072        arrIndex = childIndex
2073        childIndex = childIndex * 2 - startIndex + 1
2074    }
2075    if (childIndex < endIndex) {
2076        arr[arrIndex] = arr[childIndex]
2077        arrIndex = childIndex
2078    }
2079    arr[arrIndex] = tmp
2080    // Now heap is valid in all positions but arrIndex
2081    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot)
2082}
2083
2084export function heapSort(arr: FixedArray<int>, startIndex: int, endIndex: int): void {
2085    let len = endIndex - startIndex
2086    for (let i = len / 2 - 1; i >= 0; i--) {
2087        heapSortDown(arr, i, startIndex, endIndex)
2088    }
2089
2090    for (let i = endIndex - 1; i > startIndex; i--) {
2091        // move max element to the end of range
2092        const tmp = arr[i]
2093        arr[i] = arr[startIndex]
2094        arr[startIndex] = tmp
2095        heapSortDown(arr, 0, startIndex, i)
2096    }
2097}
2098
2099// Put median of three array elements to arr[index1]
2100function median3(arr: FixedArray<int>, index1: int, index2: int, index3: int): void {
2101    let swap_idx = index2
2102    if ((arr[index1] < arr[index2])) {
2103        if ((arr[index3] < arr[index1])) {
2104            return
2105        }
2106        if ((arr[index3] < arr[index2])) {
2107            swap_idx = index3
2108        }
2109    } else {
2110        if (!(arr[index3] < arr[index1])) {
2111            return
2112        }
2113        if ((arr[index2] < arr[index3])) {
2114            swap_idx = index3
2115        }
2116    }
2117    let tmp = arr[index1]
2118    arr[index1] = arr[swap_idx]
2119    arr[swap_idx] = tmp
2120}
2121
2122// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
2123// Elements equal to pivot go to the right
2124function quickSortSplit(arr: FixedArray<int>, startIndex: int, endIndex: int): int {
2125    const pivot = arr[startIndex]
2126    let i = startIndex + 1
2127    let j = endIndex - 1
2128    // No bounds check because pivot is median of three elements
2129    while ((arr[i] < pivot)) {
2130        i++
2131    }
2132    if (i == startIndex + 1) {
2133        while (i < j && !(arr[j] < pivot)) {
2134            j--
2135        }
2136    } else {
2137        while (!(arr[j] < pivot)) {
2138            j--
2139        }
2140    }
2141    while (i < j) {
2142        // Here !(arr[i] < pivot) and (arr[j] < pivot) holds
2143        let tmp = arr[i]
2144        arr[i] = arr[j]
2145        arr[j] = tmp
2146        while ((arr[++i] < pivot)) {}
2147        while (!(arr[--j] < pivot)) {}
2148    }
2149    let pivotIndex = i - 1
2150    arr[startIndex] = arr[pivotIndex]
2151    arr[pivotIndex] = pivot
2152
2153    return pivotIndex
2154}
2155
2156// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
2157// Elements equal to pivot go to the left
2158function quickSortSplitLeft(arr: FixedArray<int>, startIndex: int, endIndex: int): int {
2159    const pivot = arr[startIndex]
2160    let i = startIndex + 1
2161    let j = endIndex - 1
2162    // No bounds check because pivot is median of three elements
2163    while ((pivot < arr[j])) {
2164        j--
2165    }
2166    if (j + 1 == endIndex) {
2167        while (i < j && !(pivot < arr[i])) {
2168            i++
2169        }
2170    } else {
2171        while (!(pivot < arr[i])) {
2172            i++
2173        }
2174    }
2175    while (i < j) {
2176        // Here (pivot < arr[i]) and !(pivot < arr[j]) holds
2177        let tmp = arr[i]
2178        arr[i] = arr[j]
2179        arr[j] = tmp
2180        while (!(pivot < arr[++i])) {}
2181        while ((pivot < arr[--j])) {}
2182    }
2183    arr[startIndex] = arr[j]
2184    arr[j] = pivot
2185
2186    return j
2187}
2188
2189function quickSortImpl3(arr: FixedArray<int>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
2190    while (endIndex - startIndex > 3) {
2191        if (--maxDepth == 0) {
2192            heapSort(arr, startIndex, endIndex)
2193            return
2194        }
2195        // Here we assume that current interval is not the most left in the sorted range
2196        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
2197            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
2198            // after that only the right part needs to be sorted
2199            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
2200            // with many occurencies of the smallest element
2201            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
2202            continue
2203        }
2204
2205        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
2206        let p = quickSortSplit(arr, startIndex, endIndex)
2207        // make a call for the smaller part of array and continue processing the larger part in the loop
2208        if (p - startIndex < endIndex - p) {
2209            quickSortImpl3(arr, startIndex, p, maxDepth, initIndex)
2210            startIndex = p + 1
2211        } else {
2212            quickSortImpl3(arr, p + 1, endIndex, maxDepth, initIndex)
2213            endIndex = p
2214        }
2215    }
2216    insertionSort(arr, startIndex, endIndex, initIndex)
2217}
2218function quickSortImpl40(arr: FixedArray<int>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
2219    while (endIndex - startIndex > 40) {
2220        if (--maxDepth == 0) {
2221            heapSort(arr, startIndex, endIndex)
2222            return
2223        }
2224        // Here we assume that current interval is not the most left in the sorted range
2225        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
2226            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
2227            // after that only the right part needs to be sorted
2228            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
2229            // with many occurencies of the smallest element
2230            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
2231            continue
2232        }
2233
2234        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
2235        let p = quickSortSplit(arr, startIndex, endIndex)
2236        // make a call for the smaller part of array and continue processing the larger part in the loop
2237        if (p - startIndex < endIndex - p) {
2238            quickSortImpl40(arr, startIndex, p, maxDepth, initIndex)
2239            startIndex = p + 1
2240        } else {
2241            quickSortImpl40(arr, p + 1, endIndex, maxDepth, initIndex)
2242            endIndex = p
2243        }
2244    }
2245    insertionSort(arr, startIndex, endIndex, initIndex)
2246}
2247
2248function quickSort(arr: FixedArray<int>, startIndex: int, endIndex: int): void {
2249    let size = endIndex - startIndex
2250    if (size <= 1) {
2251        return
2252    }
2253    // find log of length to fall back into determenistic O(n logn) sort
2254    let bits = 32
2255    for (let i = 2; i < 31; i++) {
2256        if ((size >> i) == 0) {
2257            bits = i
2258            break
2259        }
2260    }
2261    quickSortImpl40(arr, startIndex, endIndex, bits * 3)
2262}
2263
2264/**
2265 * sorts arr in-place
2266 *
2267 * @param arr an array to sort
2268 *
2269 * @param startIndex an index to start sorting with, inclusive
2270 *
2271 * @param endIndex an index to end sorting, exclusive
2272 *
2273 * @example: sort array arr
2274 * ```
2275 * sort(arr, 0, arr.length)
2276 * ```
2277 */
2278export function sort(arr: FixedArray<int>, startIndex: int, endIndex: int): void {
2279    if (!checkRange(arr.length, startIndex, endIndex)) {
2280        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
2281    }
2282
2283    quickSort(arr, startIndex, endIndex);
2284}
2285
2286function bubbleSort(arr: FixedArray<int>, startIndex: int, endIndex: int, mustPrecede: (lhs: int, rhs: int) => boolean): void {
2287    let was = true
2288    while (was) {
2289        was = false
2290        for (let i = startIndex; i < endIndex - 1; i++) {
2291            if (mustPrecede(arr[i + 1], arr[i])) {
2292                const tmp = arr[i + 1]
2293                arr[i + 1] = arr[i]
2294                arr[i] = tmp
2295                was = true
2296            }
2297        }
2298    }
2299}
2300
2301function insertionSort(arr: FixedArray<int>, startIndex: int, endIndex: int, mustPrecede: (lhs: int, rhs: int) => boolean, initIndex: int = startIndex): void {
2302    if (startIndex != initIndex) {
2303        // arr[startIndex - 1] exists and is less than or equal to all elements in range
2304        for (let i = startIndex + 1; i < endIndex; i++) {
2305            const tmp = arr[i]
2306            let pos = i
2307            while (mustPrecede(tmp, arr[pos - 1])) {
2308                arr[pos] = arr[pos - 1]
2309                pos--
2310            }
2311            arr[pos] = tmp
2312        }
2313        return
2314    }
2315    for (let i = startIndex + 1; i < endIndex; i++) {
2316        const tmp = arr[i]
2317        if (mustPrecede(tmp, arr[startIndex])) {
2318            for (let j = i; j > startIndex; j--) {
2319                arr[j] = arr[j - 1]
2320            }
2321            arr[startIndex] = tmp
2322        } else {
2323            let pos = i
2324            while (mustPrecede(tmp, arr[pos - 1])) {
2325                arr[pos] = arr[pos - 1]
2326                pos--
2327            }
2328            arr[pos] = tmp
2329        }
2330    }
2331}
2332function heapSortUp(arr: FixedArray<int>, idxFromStart: int, startIndex: int, heapRoot: int, mustPrecede: (lhs: int, rhs: int) => boolean): void {
2333    const tmp = arr[startIndex + idxFromStart]
2334    while (startIndex + idxFromStart > heapRoot) {
2335        const p = (idxFromStart - 1) / 2
2336        if (!mustPrecede(arr[startIndex + p], tmp)) {
2337            break
2338        }
2339        arr[startIndex + idxFromStart] = arr[startIndex + p]
2340        idxFromStart = p
2341    }
2342    arr[startIndex + idxFromStart] = tmp
2343}
2344
2345// Build max heap with root in startIndex given its children are roots of valid heaps
2346function heapSortDown(arr: FixedArray<int>, idxFromStart: int, startIndex: int, endIndex: int, mustPrecede: (lhs: int, rhs: int) => boolean): void {
2347    let heapRoot = startIndex + idxFromStart
2348    let arrIndex = heapRoot
2349    let childIndex = startIndex + idxFromStart * 2 + 1
2350    const tmp = arr[arrIndex]
2351    // Walk heap to bottom and pull max child up on each level
2352    while (childIndex + 1 < endIndex) {
2353        if (mustPrecede(arr[childIndex], arr[childIndex + 1])) {
2354            childIndex++
2355        }
2356        arr[arrIndex] = arr[childIndex]
2357        arrIndex = childIndex
2358        childIndex = childIndex * 2 - startIndex + 1
2359    }
2360    if (childIndex < endIndex) {
2361        arr[arrIndex] = arr[childIndex]
2362        arrIndex = childIndex
2363    }
2364    arr[arrIndex] = tmp
2365    // Now heap is valid in all positions but arrIndex
2366    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot, mustPrecede)
2367}
2368
2369export function heapSort(arr: FixedArray<int>, startIndex: int, endIndex: int, mustPrecede: (lhs: int, rhs: int) => boolean): void {
2370    let len = endIndex - startIndex
2371    for (let i = len / 2 - 1; i >= 0; i--) {
2372        heapSortDown(arr, i, startIndex, endIndex, mustPrecede)
2373    }
2374
2375    for (let i = endIndex - 1; i > startIndex; i--) {
2376        // move max element to the end of range
2377        const tmp = arr[i]
2378        arr[i] = arr[startIndex]
2379        arr[startIndex] = tmp
2380        heapSortDown(arr, 0, startIndex, i, mustPrecede)
2381    }
2382}
2383
2384// Put median of three array elements to arr[index1]
2385function median3(arr: FixedArray<int>, index1: int, index2: int, index3: int, mustPrecede: (lhs: int, rhs: int) => boolean): void {
2386    let swap_idx = index2
2387    if (mustPrecede(arr[index1], arr[index2])) {
2388        if (mustPrecede(arr[index3], arr[index1])) {
2389            return
2390        }
2391        if (mustPrecede(arr[index3], arr[index2])) {
2392            swap_idx = index3
2393        }
2394    } else {
2395        if (!mustPrecede(arr[index3], arr[index1])) {
2396            return
2397        }
2398        if (mustPrecede(arr[index2], arr[index3])) {
2399            swap_idx = index3
2400        }
2401    }
2402    let tmp = arr[index1]
2403    arr[index1] = arr[swap_idx]
2404    arr[swap_idx] = tmp
2405}
2406
2407// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
2408// Elements equal to pivot go to the right
2409function quickSortSplit(arr: FixedArray<int>, startIndex: int, endIndex: int, mustPrecede: (lhs: int, rhs: int) => boolean): int {
2410    const pivot = arr[startIndex]
2411    let i = startIndex + 1
2412    let j = endIndex - 1
2413    // No bounds check because pivot is median of three elements
2414    while (mustPrecede(arr[i], pivot)) {
2415        i++
2416    }
2417    if (i == startIndex + 1) {
2418        while (i < j && !mustPrecede(arr[j], pivot)) {
2419            j--
2420        }
2421    } else {
2422        while (!mustPrecede(arr[j], pivot)) {
2423            j--
2424        }
2425    }
2426    while (i < j) {
2427        // Here !mustPrecede(arr[i], pivot) and mustPrecede(arr[j], pivot) holds
2428        let tmp = arr[i]
2429        arr[i] = arr[j]
2430        arr[j] = tmp
2431        while (mustPrecede(arr[++i], pivot)) {}
2432        while (!mustPrecede(arr[--j], pivot)) {}
2433    }
2434    let pivotIndex = i - 1
2435    arr[startIndex] = arr[pivotIndex]
2436    arr[pivotIndex] = pivot
2437
2438    return pivotIndex
2439}
2440
2441// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
2442// Elements equal to pivot go to the left
2443function quickSortSplitLeft(arr: FixedArray<int>, startIndex: int, endIndex: int, mustPrecede: (lhs: int, rhs: int) => boolean): int {
2444    const pivot = arr[startIndex]
2445    let i = startIndex + 1
2446    let j = endIndex - 1
2447    // No bounds check because pivot is median of three elements
2448    while (mustPrecede(pivot, arr[j])) {
2449        j--
2450    }
2451    if (j + 1 == endIndex) {
2452        while (i < j && !mustPrecede(pivot, arr[i])) {
2453            i++
2454        }
2455    } else {
2456        while (!mustPrecede(pivot, arr[i])) {
2457            i++
2458        }
2459    }
2460    while (i < j) {
2461        // Here mustPrecede(pivot, arr[i]) and !mustPrecede(pivot, arr[j]) holds
2462        let tmp = arr[i]
2463        arr[i] = arr[j]
2464        arr[j] = tmp
2465        while (!mustPrecede(pivot, arr[++i])) {}
2466        while (mustPrecede(pivot, arr[--j])) {}
2467    }
2468    arr[startIndex] = arr[j]
2469    arr[j] = pivot
2470
2471    return j
2472}
2473
2474function quickSortImpl3(arr: FixedArray<int>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: int, rhs: int) => boolean): void {
2475    while (endIndex - startIndex > 3) {
2476        if (--maxDepth == 0) {
2477            heapSort(arr, startIndex, endIndex, mustPrecede)
2478            return
2479        }
2480
2481        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
2482        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
2483        // make a call for the smaller part of array and continue processing the larger part in the loop
2484        if (p - startIndex < endIndex - p) {
2485            quickSortImpl3(arr, startIndex, p, maxDepth, mustPrecede)
2486            startIndex = p + 1
2487        } else {
2488            quickSortImpl3(arr, p + 1, endIndex, maxDepth, mustPrecede)
2489            endIndex = p
2490        }
2491    }
2492    insertionSort(arr, startIndex, endIndex, mustPrecede)
2493}
2494function quickSortImpl40(arr: FixedArray<int>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: int, rhs: int) => boolean): void {
2495    while (endIndex - startIndex > 40) {
2496        if (--maxDepth == 0) {
2497            heapSort(arr, startIndex, endIndex, mustPrecede)
2498            return
2499        }
2500
2501        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
2502        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
2503        // make a call for the smaller part of array and continue processing the larger part in the loop
2504        if (p - startIndex < endIndex - p) {
2505            quickSortImpl40(arr, startIndex, p, maxDepth, mustPrecede)
2506            startIndex = p + 1
2507        } else {
2508            quickSortImpl40(arr, p + 1, endIndex, maxDepth, mustPrecede)
2509            endIndex = p
2510        }
2511    }
2512    insertionSort(arr, startIndex, endIndex, mustPrecede)
2513}
2514
2515function quickSort(arr: FixedArray<int>, startIndex: int, endIndex: int, mustPrecede: (lhs: int, rhs: int) => boolean): void {
2516    let size = endIndex - startIndex
2517    if (size <= 1) {
2518        return
2519    }
2520    // find log of length to fall back into determenistic O(n logn) sort
2521    let bits = 32
2522    for (let i = 2; i < 31; i++) {
2523        if ((size >> i) == 0) {
2524            bits = i
2525            break
2526        }
2527    }
2528    quickSortImpl40(arr, startIndex, endIndex, bits * 3, mustPrecede)
2529}
2530
2531/**
2532 * sorts arr in-place
2533 *
2534 * @param arr an array to sort
2535 *
2536 * @param startIndex an index to start sorting with, inclusive
2537 *
2538 * @param endIndex an index to end sorting, exclusive
2539 *
2540 * @example: sort array arr
2541 * ```
2542 * sort(arr, 0, arr.length)
2543 * ```
2544 */
2545export function sort_subarray(arr: FixedArray<int>, startIndex: int, endIndex: int, mustPrecede: (lhs: int, rhs: int) => boolean): void {
2546    if (!checkRange(arr.length, startIndex, endIndex)) {
2547        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
2548    }
2549
2550    quickSort(arr, startIndex, endIndex, mustPrecede);
2551}
2552
2553/**
2554 * sorts arr in-place
2555 *
2556 * @param arr an array to sort
2557 */
2558export function sort_subarray(arr: FixedArray<int>, mustPrecede: (lhs: int, rhs: int) => boolean): void {
2559    sort_subarray(arr, 0, arr.length, mustPrecede);
2560}
2561
2562export function sort_subarray(arr: FixedArray<int>, startIndex: int, mustPrecede: (lhs: int, rhs: int) => boolean): void {
2563    sort_subarray(arr, startIndex, arr.length, mustPrecede)
2564}
2565
2566// ======== tests section ========
2567
2568/** note: used for tests, {@link test_sortAllOn} */
2569class test_SortDataint {
2570    name: string
2571    arr: FixedArray<int>
2572
2573    constructor(name: string, arr: FixedArray<int>) {
2574        this.name = name
2575        this.arr = arr
2576    }
2577}
2578
2579/** note: used for tests, {@link test_sortAllOn} */
2580function test_sortCopy(arr: FixedArray<int>): FixedArray<int> {
2581    let c : FixedArray<int> = new int[arr.length]
2582    for (let i = 0; i < arr.length; i++) {
2583        c[i] = arr[i]
2584    }
2585    return c
2586}
2587
2588/** note: used for tests, {@link test_sortAllOn} */
2589function test_sortAllOnCmpFwd_int(l: int, r: int): boolean {
2590    return l < r
2591}
2592
2593/** note: used for tests, {@link test_sortAllOn} */
2594function test_sortAllOnCmpInv_int(l: int, r: int): boolean {
2595    return r < l
2596}
2597
2598/** note: used for tests, {@link test_sortAllOn} */
2599function test_printArr(arr: FixedArray<int>, startIndex: int, endIndex: int) {
2600    for (let i = startIndex; i < endIndex; i++) {
2601        console.print(arr[i] + ' ')
2602    }
2603    console.println('')
2604}
2605
2606/**
2607 * Function used to test sorting in standard library tests.
2608 * There is only one exported function: `sort`, but for testing
2609 * we need access to all sub sorts that are used. Hence this part of "tests"
2610 * is located here. All related entities are prefixed whith `test_` to stand out
2611 */
2612export function test_sortAllOn(arr: FixedArray<int>) {
2613    // block for comparator ``
2614    if (true) {
2615        const sorts = new Array<test_SortDataint>()
2616        const bubbled = test_sortCopy(arr)
2617        bubbleSort(bubbled, 0, bubbled.length)
2618        sorts.push(new test_SortDataint("bubble", bubbled))
2619
2620        const insertion = test_sortCopy(arr)
2621        insertionSort(insertion, 0, insertion.length)
2622        sorts.push(new test_SortDataint("insertion", insertion))
2623
2624        const heaped = test_sortCopy(arr)
2625        heapSort(heaped, 0, heaped.length)
2626        sorts.push(new test_SortDataint("heap", heaped))
2627
2628        const quicked = test_sortCopy(arr)
2629        quickSortImpl3(quicked, 0, quicked.length, quicked.length)
2630        sorts.push(new test_SortDataint("quick", quicked))
2631
2632        const quickedInsertion = test_sortCopy(arr)
2633        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length)
2634        sorts.push(new test_SortDataint("quick with insertion", quickedInsertion))
2635
2636        const just = test_sortCopy(arr)
2637        sort(just)
2638        sorts.push(new test_SortDataint("sort()", just))
2639
2640        let sort0 = sorts.at(0)!
2641        for (let s = 1; s < sorts.length; s++) {
2642            let sortS = sorts.at(s)!
2643            for (let i = 0; i < arr.length; i++) {
2644                if (sort0.arr[i] != sortS.arr[i]) {
2645                    console.println(sort0.name + ': ')
2646                    test_printArr(sort0.arr, 0, arr.length)
2647                    console.println(sortS.name + ': ')
2648                    test_printArr(sortS.arr, 0, arr.length)
2649                    throw new Error("sorts are not equal: " + sort0.name + ' ' + sortS.name + ' for int')
2650                }
2651            }
2652        }
2653    }
2654    // block for comparator `est_sortAllOnCmpFwd_int`
2655    if (true) {
2656        const sorts = new Array<test_SortDataint>()
2657        const bubbled = test_sortCopy(arr)
2658        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpFwd_int)
2659        sorts.push(new test_SortDataint("bubble", bubbled))
2660
2661        const insertion = test_sortCopy(arr)
2662        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpFwd_int)
2663        sorts.push(new test_SortDataint("insertion", insertion))
2664
2665        const heaped = test_sortCopy(arr)
2666        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpFwd_int)
2667        sorts.push(new test_SortDataint("heap", heaped))
2668
2669        const quicked = test_sortCopy(arr)
2670        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpFwd_int)
2671        sorts.push(new test_SortDataint("quick", quicked))
2672
2673        const quickedInsertion = test_sortCopy(arr)
2674        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpFwd_int)
2675        sorts.push(new test_SortDataint("quick with insertion", quickedInsertion))
2676
2677        const just = test_sortCopy(arr)
2678        sort_subarray(just, test_sortAllOnCmpFwd_int)
2679        sorts.push(new test_SortDataint("sort()", just))
2680
2681        let sort0 = sorts.at(0)!
2682        for (let s = 1; s < sorts.length; s++) {
2683            let sortS = sorts.at(s)!
2684            for (let i = 0; i < arr.length; i++) {
2685                if (sort0.arr[i] != sortS.arr[i]) {
2686                    console.println(sort0.name + ': ')
2687                    test_printArr(sort0.arr, 0, arr.length)
2688                    console.println(sortS.name + ': ')
2689                    test_printArr(sortS.arr, 0, arr.length)
2690                    throw new Error("sorts est_sortAllOnCmpFwd_int are not equal: " + sort0.name + ' ' + sortS.name + ' for int')
2691                }
2692            }
2693        }
2694    }
2695    // block for comparator `est_sortAllOnCmpInv_int`
2696    if (true) {
2697        const sorts = new Array<test_SortDataint>()
2698        const bubbled = test_sortCopy(arr)
2699        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpInv_int)
2700        sorts.push(new test_SortDataint("bubble", bubbled))
2701
2702        const insertion = test_sortCopy(arr)
2703        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpInv_int)
2704        sorts.push(new test_SortDataint("insertion", insertion))
2705
2706        const heaped = test_sortCopy(arr)
2707        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpInv_int)
2708        sorts.push(new test_SortDataint("heap", heaped))
2709
2710        const quicked = test_sortCopy(arr)
2711        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpInv_int)
2712        sorts.push(new test_SortDataint("quick", quicked))
2713
2714        const quickedInsertion = test_sortCopy(arr)
2715        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpInv_int)
2716        sorts.push(new test_SortDataint("quick with insertion", quickedInsertion))
2717
2718        const just = test_sortCopy(arr)
2719        sort_subarray(just, test_sortAllOnCmpInv_int)
2720        sorts.push(new test_SortDataint("sort()", just))
2721
2722        let sort0 = sorts.at(0)!
2723        for (let s = 1; s < sorts.length; s++) {
2724            let sortS = sorts.at(s)!
2725            for (let i = 0; i < arr.length; i++) {
2726                if (sort0.arr[i] != sortS.arr[i]) {
2727                    console.println(sort0.name + ': ')
2728                    test_printArr(sort0.arr, 0, arr.length)
2729                    console.println(sortS.name + ': ')
2730                    test_printArr(sortS.arr, 0, arr.length)
2731                    throw new Error("sorts est_sortAllOnCmpInv_int are not equal: " + sort0.name + ' ' + sortS.name + ' for int')
2732                }
2733            }
2734        }
2735    }
2736}
2737// ======== end of tests section ========
2738
2739function copyPart(dst: FixedArray<long>, counter: int, src: FixedArray<long>, start: int, end?: int) {
2740    if (end == undefined) {
2741        end = src.length
2742    }
2743    for (let i = start; i < end!; ++i) {
2744        dst[counter++] = src[i]
2745    }
2746}
2747
2748function merge(left: FixedArray<long>, right: FixedArray<long>, cmp: (lhs: long, rhs: long) => number): FixedArray<long> {
2749    const result:FixedArray<long> = new long[right.length + left.length]
2750    let leftIndex = 0;
2751    let rightIndex = 0;
2752    let counter: int = 0
2753
2754    while (leftIndex < left.length &&
2755        rightIndex < right.length) {
2756        if (cmp(left[leftIndex], right[rightIndex]) <= 0) {
2757            result[counter++] = left[leftIndex];
2758            leftIndex++;
2759        } else {
2760            result[counter++] = right[rightIndex]
2761            rightIndex++;
2762        }
2763    }
2764    copyPart(result, counter, left, leftIndex)
2765    copyPart(result, counter, right, rightIndex)
2766
2767    return result
2768}
2769
2770export function mergeSort(array: FixedArray<long>, cmp: (lhs: long, rhs: long) => number, begin: int = 0, end: int = 0): FixedArray<long> {
2771    if (end == 0) {
2772        end = array.length
2773    }
2774    const arrLength = end - begin
2775    if (arrLength <= 1) {
2776        return array;
2777    }
2778    const middle = Math.floor(begin + arrLength / 2) as int
2779    const leftHalf:FixedArray<long> = new long[middle]
2780    let counter: int = 0
2781    copyPart(leftHalf, counter, array, 0, middle)
2782
2783    counter = 0
2784    const rightHalf:FixedArray<long> = new long[arrLength - middle]
2785    copyPart(rightHalf, counter, array, middle)
2786
2787    return merge(mergeSort(leftHalf, cmp), mergeSort(rightHalf, cmp), cmp);
2788}
2789
2790function bubbleSort(arr: FixedArray<long>, startIndex: int, endIndex: int): void {
2791    let was = true
2792    while (was) {
2793        was = false
2794        for (let i = startIndex; i < endIndex - 1; i++) {
2795            if ((arr[i + 1] < arr[i])) {
2796                const tmp = arr[i + 1]
2797                arr[i + 1] = arr[i]
2798                arr[i] = tmp
2799                was = true
2800            }
2801        }
2802    }
2803}
2804
2805function insertionSort(arr: FixedArray<long>, startIndex: int, endIndex: int, initIndex: int = startIndex): void {
2806    if (startIndex != initIndex) {
2807        // arr[startIndex - 1] exists and is less than or equal to all elements in range
2808        for (let i = startIndex + 1; i < endIndex; i++) {
2809            const tmp = arr[i]
2810            let pos = i
2811            while ((tmp < arr[pos - 1])) {
2812                arr[pos] = arr[pos - 1]
2813                pos--
2814            }
2815            arr[pos] = tmp
2816        }
2817        return
2818    }
2819    for (let i = startIndex + 1; i < endIndex; i++) {
2820        const tmp = arr[i]
2821        if ((tmp < arr[startIndex])) {
2822            for (let j = i; j > startIndex; j--) {
2823                arr[j] = arr[j - 1]
2824            }
2825            arr[startIndex] = tmp
2826        } else {
2827            let pos = i
2828            while ((tmp < arr[pos - 1])) {
2829                arr[pos] = arr[pos - 1]
2830                pos--
2831            }
2832            arr[pos] = tmp
2833        }
2834    }
2835}
2836function heapSortUp(arr: FixedArray<long>, idxFromStart: int, startIndex: int, heapRoot: int): void {
2837    const tmp = arr[startIndex + idxFromStart]
2838    while (startIndex + idxFromStart > heapRoot) {
2839        const p = (idxFromStart - 1) / 2
2840        if (!(arr[startIndex + p] < tmp)) {
2841            break
2842        }
2843        arr[startIndex + idxFromStart] = arr[startIndex + p]
2844        idxFromStart = p
2845    }
2846    arr[startIndex + idxFromStart] = tmp
2847}
2848
2849// Build max heap with root in startIndex given its children are roots of valid heaps
2850function heapSortDown(arr: FixedArray<long>, idxFromStart: int, startIndex: int, endIndex: int): void {
2851    let heapRoot = startIndex + idxFromStart
2852    let arrIndex = heapRoot
2853    let childIndex = startIndex + idxFromStart * 2 + 1
2854    const tmp = arr[arrIndex]
2855    // Walk heap to bottom and pull max child up on each level
2856    while (childIndex + 1 < endIndex) {
2857        if ((arr[childIndex] < arr[childIndex + 1])) {
2858            childIndex++
2859        }
2860        arr[arrIndex] = arr[childIndex]
2861        arrIndex = childIndex
2862        childIndex = childIndex * 2 - startIndex + 1
2863    }
2864    if (childIndex < endIndex) {
2865        arr[arrIndex] = arr[childIndex]
2866        arrIndex = childIndex
2867    }
2868    arr[arrIndex] = tmp
2869    // Now heap is valid in all positions but arrIndex
2870    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot)
2871}
2872
2873export function heapSort(arr: FixedArray<long>, startIndex: int, endIndex: int): void {
2874    let len = endIndex - startIndex
2875    for (let i = len / 2 - 1; i >= 0; i--) {
2876        heapSortDown(arr, i, startIndex, endIndex)
2877    }
2878
2879    for (let i = endIndex - 1; i > startIndex; i--) {
2880        // move max element to the end of range
2881        const tmp = arr[i]
2882        arr[i] = arr[startIndex]
2883        arr[startIndex] = tmp
2884        heapSortDown(arr, 0, startIndex, i)
2885    }
2886}
2887
2888// Put median of three array elements to arr[index1]
2889function median3(arr: FixedArray<long>, index1: int, index2: int, index3: int): void {
2890    let swap_idx = index2
2891    if ((arr[index1] < arr[index2])) {
2892        if ((arr[index3] < arr[index1])) {
2893            return
2894        }
2895        if ((arr[index3] < arr[index2])) {
2896            swap_idx = index3
2897        }
2898    } else {
2899        if (!(arr[index3] < arr[index1])) {
2900            return
2901        }
2902        if ((arr[index2] < arr[index3])) {
2903            swap_idx = index3
2904        }
2905    }
2906    let tmp = arr[index1]
2907    arr[index1] = arr[swap_idx]
2908    arr[swap_idx] = tmp
2909}
2910
2911// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
2912// Elements equal to pivot go to the right
2913function quickSortSplit(arr: FixedArray<long>, startIndex: int, endIndex: int): int {
2914    const pivot = arr[startIndex]
2915    let i = startIndex + 1
2916    let j = endIndex - 1
2917    // No bounds check because pivot is median of three elements
2918    while ((arr[i] < pivot)) {
2919        i++
2920    }
2921    if (i == startIndex + 1) {
2922        while (i < j && !(arr[j] < pivot)) {
2923            j--
2924        }
2925    } else {
2926        while (!(arr[j] < pivot)) {
2927            j--
2928        }
2929    }
2930    while (i < j) {
2931        // Here !(arr[i] < pivot) and (arr[j] < pivot) holds
2932        let tmp = arr[i]
2933        arr[i] = arr[j]
2934        arr[j] = tmp
2935        while ((arr[++i] < pivot)) {}
2936        while (!(arr[--j] < pivot)) {}
2937    }
2938    let pivotIndex = i - 1
2939    arr[startIndex] = arr[pivotIndex]
2940    arr[pivotIndex] = pivot
2941
2942    return pivotIndex
2943}
2944
2945// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
2946// Elements equal to pivot go to the left
2947function quickSortSplitLeft(arr: FixedArray<long>, startIndex: int, endIndex: int): int {
2948    const pivot = arr[startIndex]
2949    let i = startIndex + 1
2950    let j = endIndex - 1
2951    // No bounds check because pivot is median of three elements
2952    while ((pivot < arr[j])) {
2953        j--
2954    }
2955    if (j + 1 == endIndex) {
2956        while (i < j && !(pivot < arr[i])) {
2957            i++
2958        }
2959    } else {
2960        while (!(pivot < arr[i])) {
2961            i++
2962        }
2963    }
2964    while (i < j) {
2965        // Here (pivot < arr[i]) and !(pivot < arr[j]) holds
2966        let tmp = arr[i]
2967        arr[i] = arr[j]
2968        arr[j] = tmp
2969        while (!(pivot < arr[++i])) {}
2970        while ((pivot < arr[--j])) {}
2971    }
2972    arr[startIndex] = arr[j]
2973    arr[j] = pivot
2974
2975    return j
2976}
2977
2978function quickSortImpl3(arr: FixedArray<long>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
2979    while (endIndex - startIndex > 3) {
2980        if (--maxDepth == 0) {
2981            heapSort(arr, startIndex, endIndex)
2982            return
2983        }
2984        // Here we assume that current interval is not the most left in the sorted range
2985        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
2986            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
2987            // after that only the right part needs to be sorted
2988            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
2989            // with many occurencies of the smallest element
2990            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
2991            continue
2992        }
2993
2994        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
2995        let p = quickSortSplit(arr, startIndex, endIndex)
2996        // make a call for the smaller part of array and continue processing the larger part in the loop
2997        if (p - startIndex < endIndex - p) {
2998            quickSortImpl3(arr, startIndex, p, maxDepth, initIndex)
2999            startIndex = p + 1
3000        } else {
3001            quickSortImpl3(arr, p + 1, endIndex, maxDepth, initIndex)
3002            endIndex = p
3003        }
3004    }
3005    insertionSort(arr, startIndex, endIndex, initIndex)
3006}
3007function quickSortImpl40(arr: FixedArray<long>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
3008    while (endIndex - startIndex > 40) {
3009        if (--maxDepth == 0) {
3010            heapSort(arr, startIndex, endIndex)
3011            return
3012        }
3013        // Here we assume that current interval is not the most left in the sorted range
3014        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
3015            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
3016            // after that only the right part needs to be sorted
3017            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
3018            // with many occurencies of the smallest element
3019            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
3020            continue
3021        }
3022
3023        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
3024        let p = quickSortSplit(arr, startIndex, endIndex)
3025        // make a call for the smaller part of array and continue processing the larger part in the loop
3026        if (p - startIndex < endIndex - p) {
3027            quickSortImpl40(arr, startIndex, p, maxDepth, initIndex)
3028            startIndex = p + 1
3029        } else {
3030            quickSortImpl40(arr, p + 1, endIndex, maxDepth, initIndex)
3031            endIndex = p
3032        }
3033    }
3034    insertionSort(arr, startIndex, endIndex, initIndex)
3035}
3036
3037function quickSort(arr: FixedArray<long>, startIndex: int, endIndex: int): void {
3038    let size = endIndex - startIndex
3039    if (size <= 1) {
3040        return
3041    }
3042    // find log of length to fall back into determenistic O(n logn) sort
3043    let bits = 32
3044    for (let i = 2; i < 31; i++) {
3045        if ((size >> i) == 0) {
3046            bits = i
3047            break
3048        }
3049    }
3050    quickSortImpl40(arr, startIndex, endIndex, bits * 3)
3051}
3052
3053/**
3054 * sorts arr in-place
3055 *
3056 * @param arr an array to sort
3057 *
3058 * @param startIndex an index to start sorting with, inclusive
3059 *
3060 * @param endIndex an index to end sorting, exclusive
3061 *
3062 * @example: sort array arr
3063 * ```
3064 * sort(arr, 0, arr.length)
3065 * ```
3066 */
3067export function sort(arr: FixedArray<long>, startIndex: int, endIndex: int): void {
3068    if (!checkRange(arr.length, startIndex, endIndex)) {
3069        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
3070    }
3071
3072    quickSort(arr, startIndex, endIndex);
3073}
3074
3075function bubbleSort(arr: FixedArray<long>, startIndex: int, endIndex: int, mustPrecede: (lhs: long, rhs: long) => boolean): void {
3076    let was = true
3077    while (was) {
3078        was = false
3079        for (let i = startIndex; i < endIndex - 1; i++) {
3080            if (mustPrecede(arr[i + 1], arr[i])) {
3081                const tmp = arr[i + 1]
3082                arr[i + 1] = arr[i]
3083                arr[i] = tmp
3084                was = true
3085            }
3086        }
3087    }
3088}
3089
3090function insertionSort(arr: FixedArray<long>, startIndex: int, endIndex: int, mustPrecede: (lhs: long, rhs: long) => boolean, initIndex: int = startIndex): void {
3091    if (startIndex != initIndex) {
3092        // arr[startIndex - 1] exists and is less than or equal to all elements in range
3093        for (let i = startIndex + 1; i < endIndex; i++) {
3094            const tmp = arr[i]
3095            let pos = i
3096            while (mustPrecede(tmp, arr[pos - 1])) {
3097                arr[pos] = arr[pos - 1]
3098                pos--
3099            }
3100            arr[pos] = tmp
3101        }
3102        return
3103    }
3104    for (let i = startIndex + 1; i < endIndex; i++) {
3105        const tmp = arr[i]
3106        if (mustPrecede(tmp, arr[startIndex])) {
3107            for (let j = i; j > startIndex; j--) {
3108                arr[j] = arr[j - 1]
3109            }
3110            arr[startIndex] = tmp
3111        } else {
3112            let pos = i
3113            while (mustPrecede(tmp, arr[pos - 1])) {
3114                arr[pos] = arr[pos - 1]
3115                pos--
3116            }
3117            arr[pos] = tmp
3118        }
3119    }
3120}
3121function heapSortUp(arr: FixedArray<long>, idxFromStart: int, startIndex: int, heapRoot: int, mustPrecede: (lhs: long, rhs: long) => boolean): void {
3122    const tmp = arr[startIndex + idxFromStart]
3123    while (startIndex + idxFromStart > heapRoot) {
3124        const p = (idxFromStart - 1) / 2
3125        if (!mustPrecede(arr[startIndex + p], tmp)) {
3126            break
3127        }
3128        arr[startIndex + idxFromStart] = arr[startIndex + p]
3129        idxFromStart = p
3130    }
3131    arr[startIndex + idxFromStart] = tmp
3132}
3133
3134// Build max heap with root in startIndex given its children are roots of valid heaps
3135function heapSortDown(arr: FixedArray<long>, idxFromStart: int, startIndex: int, endIndex: int, mustPrecede: (lhs: long, rhs: long) => boolean): void {
3136    let heapRoot = startIndex + idxFromStart
3137    let arrIndex = heapRoot
3138    let childIndex = startIndex + idxFromStart * 2 + 1
3139    const tmp = arr[arrIndex]
3140    // Walk heap to bottom and pull max child up on each level
3141    while (childIndex + 1 < endIndex) {
3142        if (mustPrecede(arr[childIndex], arr[childIndex + 1])) {
3143            childIndex++
3144        }
3145        arr[arrIndex] = arr[childIndex]
3146        arrIndex = childIndex
3147        childIndex = childIndex * 2 - startIndex + 1
3148    }
3149    if (childIndex < endIndex) {
3150        arr[arrIndex] = arr[childIndex]
3151        arrIndex = childIndex
3152    }
3153    arr[arrIndex] = tmp
3154    // Now heap is valid in all positions but arrIndex
3155    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot, mustPrecede)
3156}
3157
3158export function heapSort(arr: FixedArray<long>, startIndex: int, endIndex: int, mustPrecede: (lhs: long, rhs: long) => boolean): void {
3159    let len = endIndex - startIndex
3160    for (let i = len / 2 - 1; i >= 0; i--) {
3161        heapSortDown(arr, i, startIndex, endIndex, mustPrecede)
3162    }
3163
3164    for (let i = endIndex - 1; i > startIndex; i--) {
3165        // move max element to the end of range
3166        const tmp = arr[i]
3167        arr[i] = arr[startIndex]
3168        arr[startIndex] = tmp
3169        heapSortDown(arr, 0, startIndex, i, mustPrecede)
3170    }
3171}
3172
3173// Put median of three array elements to arr[index1]
3174function median3(arr: FixedArray<long>, index1: int, index2: int, index3: int, mustPrecede: (lhs: long, rhs: long) => boolean): void {
3175    let swap_idx = index2
3176    if (mustPrecede(arr[index1], arr[index2])) {
3177        if (mustPrecede(arr[index3], arr[index1])) {
3178            return
3179        }
3180        if (mustPrecede(arr[index3], arr[index2])) {
3181            swap_idx = index3
3182        }
3183    } else {
3184        if (!mustPrecede(arr[index3], arr[index1])) {
3185            return
3186        }
3187        if (mustPrecede(arr[index2], arr[index3])) {
3188            swap_idx = index3
3189        }
3190    }
3191    let tmp = arr[index1]
3192    arr[index1] = arr[swap_idx]
3193    arr[swap_idx] = tmp
3194}
3195
3196// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
3197// Elements equal to pivot go to the right
3198function quickSortSplit(arr: FixedArray<long>, startIndex: int, endIndex: int, mustPrecede: (lhs: long, rhs: long) => boolean): int {
3199    const pivot = arr[startIndex]
3200    let i = startIndex + 1
3201    let j = endIndex - 1
3202    // No bounds check because pivot is median of three elements
3203    while (mustPrecede(arr[i], pivot)) {
3204        i++
3205    }
3206    if (i == startIndex + 1) {
3207        while (i < j && !mustPrecede(arr[j], pivot)) {
3208            j--
3209        }
3210    } else {
3211        while (!mustPrecede(arr[j], pivot)) {
3212            j--
3213        }
3214    }
3215    while (i < j) {
3216        // Here !mustPrecede(arr[i], pivot) and mustPrecede(arr[j], pivot) holds
3217        let tmp = arr[i]
3218        arr[i] = arr[j]
3219        arr[j] = tmp
3220        while (mustPrecede(arr[++i], pivot)) {}
3221        while (!mustPrecede(arr[--j], pivot)) {}
3222    }
3223    let pivotIndex = i - 1
3224    arr[startIndex] = arr[pivotIndex]
3225    arr[pivotIndex] = pivot
3226
3227    return pivotIndex
3228}
3229
3230// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
3231// Elements equal to pivot go to the left
3232function quickSortSplitLeft(arr: FixedArray<long>, startIndex: int, endIndex: int, mustPrecede: (lhs: long, rhs: long) => boolean): int {
3233    const pivot = arr[startIndex]
3234    let i = startIndex + 1
3235    let j = endIndex - 1
3236    // No bounds check because pivot is median of three elements
3237    while (mustPrecede(pivot, arr[j])) {
3238        j--
3239    }
3240    if (j + 1 == endIndex) {
3241        while (i < j && !mustPrecede(pivot, arr[i])) {
3242            i++
3243        }
3244    } else {
3245        while (!mustPrecede(pivot, arr[i])) {
3246            i++
3247        }
3248    }
3249    while (i < j) {
3250        // Here mustPrecede(pivot, arr[i]) and !mustPrecede(pivot, arr[j]) holds
3251        let tmp = arr[i]
3252        arr[i] = arr[j]
3253        arr[j] = tmp
3254        while (!mustPrecede(pivot, arr[++i])) {}
3255        while (mustPrecede(pivot, arr[--j])) {}
3256    }
3257    arr[startIndex] = arr[j]
3258    arr[j] = pivot
3259
3260    return j
3261}
3262
3263function quickSortImpl3(arr: FixedArray<long>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: long, rhs: long) => boolean): void {
3264    while (endIndex - startIndex > 3) {
3265        if (--maxDepth == 0) {
3266            heapSort(arr, startIndex, endIndex, mustPrecede)
3267            return
3268        }
3269
3270        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
3271        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
3272        // make a call for the smaller part of array and continue processing the larger part in the loop
3273        if (p - startIndex < endIndex - p) {
3274            quickSortImpl3(arr, startIndex, p, maxDepth, mustPrecede)
3275            startIndex = p + 1
3276        } else {
3277            quickSortImpl3(arr, p + 1, endIndex, maxDepth, mustPrecede)
3278            endIndex = p
3279        }
3280    }
3281    insertionSort(arr, startIndex, endIndex, mustPrecede)
3282}
3283function quickSortImpl40(arr: FixedArray<long>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: long, rhs: long) => boolean): void {
3284    while (endIndex - startIndex > 40) {
3285        if (--maxDepth == 0) {
3286            heapSort(arr, startIndex, endIndex, mustPrecede)
3287            return
3288        }
3289
3290        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
3291        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
3292        // make a call for the smaller part of array and continue processing the larger part in the loop
3293        if (p - startIndex < endIndex - p) {
3294            quickSortImpl40(arr, startIndex, p, maxDepth, mustPrecede)
3295            startIndex = p + 1
3296        } else {
3297            quickSortImpl40(arr, p + 1, endIndex, maxDepth, mustPrecede)
3298            endIndex = p
3299        }
3300    }
3301    insertionSort(arr, startIndex, endIndex, mustPrecede)
3302}
3303
3304function quickSort(arr: FixedArray<long>, startIndex: int, endIndex: int, mustPrecede: (lhs: long, rhs: long) => boolean): void {
3305    let size = endIndex - startIndex
3306    if (size <= 1) {
3307        return
3308    }
3309    // find log of length to fall back into determenistic O(n logn) sort
3310    let bits = 32
3311    for (let i = 2; i < 31; i++) {
3312        if ((size >> i) == 0) {
3313            bits = i
3314            break
3315        }
3316    }
3317    quickSortImpl40(arr, startIndex, endIndex, bits * 3, mustPrecede)
3318}
3319
3320/**
3321 * sorts arr in-place
3322 *
3323 * @param arr an array to sort
3324 *
3325 * @param startIndex an index to start sorting with, inclusive
3326 *
3327 * @param endIndex an index to end sorting, exclusive
3328 *
3329 * @example: sort array arr
3330 * ```
3331 * sort(arr, 0, arr.length)
3332 * ```
3333 */
3334export function sort_subarray(arr: FixedArray<long>, startIndex: int, endIndex: int, mustPrecede: (lhs: long, rhs: long) => boolean): void {
3335    if (!checkRange(arr.length, startIndex, endIndex)) {
3336        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
3337    }
3338
3339    quickSort(arr, startIndex, endIndex, mustPrecede);
3340}
3341
3342/**
3343 * sorts arr in-place
3344 *
3345 * @param arr an array to sort
3346 */
3347export function sort_subarray(arr: FixedArray<long>, mustPrecede: (lhs: long, rhs: long) => boolean): void {
3348    sort_subarray(arr, 0, arr.length, mustPrecede);
3349}
3350
3351export function sort_subarray(arr: FixedArray<long>, startIndex: int, mustPrecede: (lhs: long, rhs: long) => boolean): void {
3352    sort_subarray(arr, startIndex, arr.length, mustPrecede)
3353}
3354
3355// ======== tests section ========
3356
3357/** note: used for tests, {@link test_sortAllOn} */
3358class test_SortDatalong {
3359    name: string
3360    arr: FixedArray<long>
3361
3362    constructor(name: string, arr: FixedArray<long>) {
3363        this.name = name
3364        this.arr = arr
3365    }
3366}
3367
3368/** note: used for tests, {@link test_sortAllOn} */
3369function test_sortCopy(arr: FixedArray<long>): FixedArray<long> {
3370    let c : FixedArray<long> = new long[arr.length]
3371    for (let i = 0; i < arr.length; i++) {
3372        c[i] = arr[i]
3373    }
3374    return c
3375}
3376
3377/** note: used for tests, {@link test_sortAllOn} */
3378function test_sortAllOnCmpFwd_long(l: long, r: long): boolean {
3379    return l < r
3380}
3381
3382/** note: used for tests, {@link test_sortAllOn} */
3383function test_sortAllOnCmpInv_long(l: long, r: long): boolean {
3384    return r < l
3385}
3386
3387/** note: used for tests, {@link test_sortAllOn} */
3388function test_printArr(arr: FixedArray<long>, startIndex: int, endIndex: int) {
3389    for (let i = startIndex; i < endIndex; i++) {
3390        console.print(arr[i] + ' ')
3391    }
3392    console.println('')
3393}
3394
3395/**
3396 * Function used to test sorting in standard library tests.
3397 * There is only one exported function: `sort`, but for testing
3398 * we need access to all sub sorts that are used. Hence this part of "tests"
3399 * is located here. All related entities are prefixed whith `test_` to stand out
3400 */
3401export function test_sortAllOn(arr: FixedArray<long>) {
3402    // block for comparator ``
3403    if (true) {
3404        const sorts = new Array<test_SortDatalong>()
3405        const bubbled = test_sortCopy(arr)
3406        bubbleSort(bubbled, 0, bubbled.length)
3407        sorts.push(new test_SortDatalong("bubble", bubbled))
3408
3409        const insertion = test_sortCopy(arr)
3410        insertionSort(insertion, 0, insertion.length)
3411        sorts.push(new test_SortDatalong("insertion", insertion))
3412
3413        const heaped = test_sortCopy(arr)
3414        heapSort(heaped, 0, heaped.length)
3415        sorts.push(new test_SortDatalong("heap", heaped))
3416
3417        const quicked = test_sortCopy(arr)
3418        quickSortImpl3(quicked, 0, quicked.length, quicked.length)
3419        sorts.push(new test_SortDatalong("quick", quicked))
3420
3421        const quickedInsertion = test_sortCopy(arr)
3422        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length)
3423        sorts.push(new test_SortDatalong("quick with insertion", quickedInsertion))
3424
3425        const just = test_sortCopy(arr)
3426        sort(just)
3427        sorts.push(new test_SortDatalong("sort()", just))
3428
3429        let sort0 = sorts.at(0)!
3430        for (let s = 1; s < sorts.length; s++) {
3431            let sortS = sorts.at(s)!
3432            for (let i = 0; i < arr.length; i++) {
3433                if (sort0.arr[i] != sortS.arr[i]) {
3434                    console.println(sort0.name + ': ')
3435                    test_printArr(sort0.arr, 0, arr.length)
3436                    console.println(sortS.name + ': ')
3437                    test_printArr(sortS.arr, 0, arr.length)
3438                    throw new Error("sorts are not equal: " + sort0.name + ' ' + sortS.name + ' for long')
3439                }
3440            }
3441        }
3442    }
3443    // block for comparator `est_sortAllOnCmpFwd_long`
3444    if (true) {
3445        const sorts = new Array<test_SortDatalong>()
3446        const bubbled = test_sortCopy(arr)
3447        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpFwd_long)
3448        sorts.push(new test_SortDatalong("bubble", bubbled))
3449
3450        const insertion = test_sortCopy(arr)
3451        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpFwd_long)
3452        sorts.push(new test_SortDatalong("insertion", insertion))
3453
3454        const heaped = test_sortCopy(arr)
3455        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpFwd_long)
3456        sorts.push(new test_SortDatalong("heap", heaped))
3457
3458        const quicked = test_sortCopy(arr)
3459        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpFwd_long)
3460        sorts.push(new test_SortDatalong("quick", quicked))
3461
3462        const quickedInsertion = test_sortCopy(arr)
3463        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpFwd_long)
3464        sorts.push(new test_SortDatalong("quick with insertion", quickedInsertion))
3465
3466        const just = test_sortCopy(arr)
3467        sort_subarray(just, test_sortAllOnCmpFwd_long)
3468        sorts.push(new test_SortDatalong("sort()", just))
3469
3470        let sort0 = sorts.at(0)!
3471        for (let s = 1; s < sorts.length; s++) {
3472            let sortS = sorts.at(s)!
3473            for (let i = 0; i < arr.length; i++) {
3474                if (sort0.arr[i] != sortS.arr[i]) {
3475                    console.println(sort0.name + ': ')
3476                    test_printArr(sort0.arr, 0, arr.length)
3477                    console.println(sortS.name + ': ')
3478                    test_printArr(sortS.arr, 0, arr.length)
3479                    throw new Error("sorts est_sortAllOnCmpFwd_long are not equal: " + sort0.name + ' ' + sortS.name + ' for long')
3480                }
3481            }
3482        }
3483    }
3484    // block for comparator `est_sortAllOnCmpInv_long`
3485    if (true) {
3486        const sorts = new Array<test_SortDatalong>()
3487        const bubbled = test_sortCopy(arr)
3488        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpInv_long)
3489        sorts.push(new test_SortDatalong("bubble", bubbled))
3490
3491        const insertion = test_sortCopy(arr)
3492        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpInv_long)
3493        sorts.push(new test_SortDatalong("insertion", insertion))
3494
3495        const heaped = test_sortCopy(arr)
3496        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpInv_long)
3497        sorts.push(new test_SortDatalong("heap", heaped))
3498
3499        const quicked = test_sortCopy(arr)
3500        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpInv_long)
3501        sorts.push(new test_SortDatalong("quick", quicked))
3502
3503        const quickedInsertion = test_sortCopy(arr)
3504        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpInv_long)
3505        sorts.push(new test_SortDatalong("quick with insertion", quickedInsertion))
3506
3507        const just = test_sortCopy(arr)
3508        sort_subarray(just, test_sortAllOnCmpInv_long)
3509        sorts.push(new test_SortDatalong("sort()", just))
3510
3511        let sort0 = sorts.at(0)!
3512        for (let s = 1; s < sorts.length; s++) {
3513            let sortS = sorts.at(s)!
3514            for (let i = 0; i < arr.length; i++) {
3515                if (sort0.arr[i] != sortS.arr[i]) {
3516                    console.println(sort0.name + ': ')
3517                    test_printArr(sort0.arr, 0, arr.length)
3518                    console.println(sortS.name + ': ')
3519                    test_printArr(sortS.arr, 0, arr.length)
3520                    throw new Error("sorts est_sortAllOnCmpInv_long are not equal: " + sort0.name + ' ' + sortS.name + ' for long')
3521                }
3522            }
3523        }
3524    }
3525}
3526// ======== end of tests section ========
3527
3528function copyPart(dst: FixedArray<float>, counter: int, src: FixedArray<float>, start: int, end?: int) {
3529    if (end == undefined) {
3530        end = src.length
3531    }
3532    for (let i = start; i < end!; ++i) {
3533        dst[counter++] = src[i]
3534    }
3535}
3536
3537function merge(left: FixedArray<float>, right: FixedArray<float>, cmp: (lhs: float, rhs: float) => number): FixedArray<float> {
3538    const result:FixedArray<float> = new float[right.length + left.length]
3539    let leftIndex = 0;
3540    let rightIndex = 0;
3541    let counter: int = 0
3542
3543    while (leftIndex < left.length &&
3544        rightIndex < right.length) {
3545        if (cmp(left[leftIndex], right[rightIndex]) <= 0) {
3546            result[counter++] = left[leftIndex];
3547            leftIndex++;
3548        } else {
3549            result[counter++] = right[rightIndex]
3550            rightIndex++;
3551        }
3552    }
3553    copyPart(result, counter, left, leftIndex)
3554    copyPart(result, counter, right, rightIndex)
3555
3556    return result
3557}
3558
3559export function mergeSort(array: FixedArray<float>, cmp: (lhs: float, rhs: float) => number, begin: int = 0, end: int = 0): FixedArray<float> {
3560    if (end == 0) {
3561        end = array.length
3562    }
3563    const arrLength = end - begin
3564    if (arrLength <= 1) {
3565        return array;
3566    }
3567    const middle = Math.floor(begin + arrLength / 2) as int
3568    const leftHalf:FixedArray<float> = new float[middle]
3569    let counter: int = 0
3570    copyPart(leftHalf, counter, array, 0, middle)
3571
3572    counter = 0
3573    const rightHalf:FixedArray<float> = new float[arrLength - middle]
3574    copyPart(rightHalf, counter, array, middle)
3575
3576    return merge(mergeSort(leftHalf, cmp), mergeSort(rightHalf, cmp), cmp);
3577}
3578
3579function bubbleSort(arr: FixedArray<float>, startIndex: int, endIndex: int): void {
3580    let was = true
3581    while (was) {
3582        was = false
3583        for (let i = startIndex; i < endIndex - 1; i++) {
3584            if ((arr[i + 1] < arr[i])) {
3585                const tmp = arr[i + 1]
3586                arr[i + 1] = arr[i]
3587                arr[i] = tmp
3588                was = true
3589            }
3590        }
3591    }
3592}
3593
3594function insertionSort(arr: FixedArray<float>, startIndex: int, endIndex: int, initIndex: int = startIndex): void {
3595    if (startIndex != initIndex) {
3596        // arr[startIndex - 1] exists and is less than or equal to all elements in range
3597        for (let i = startIndex + 1; i < endIndex; i++) {
3598            const tmp = arr[i]
3599            let pos = i
3600            while ((tmp < arr[pos - 1])) {
3601                arr[pos] = arr[pos - 1]
3602                pos--
3603            }
3604            arr[pos] = tmp
3605        }
3606        return
3607    }
3608    for (let i = startIndex + 1; i < endIndex; i++) {
3609        const tmp = arr[i]
3610        if ((tmp < arr[startIndex])) {
3611            for (let j = i; j > startIndex; j--) {
3612                arr[j] = arr[j - 1]
3613            }
3614            arr[startIndex] = tmp
3615        } else {
3616            let pos = i
3617            while ((tmp < arr[pos - 1])) {
3618                arr[pos] = arr[pos - 1]
3619                pos--
3620            }
3621            arr[pos] = tmp
3622        }
3623    }
3624}
3625function heapSortUp(arr: FixedArray<float>, idxFromStart: int, startIndex: int, heapRoot: int): void {
3626    const tmp = arr[startIndex + idxFromStart]
3627    while (startIndex + idxFromStart > heapRoot) {
3628        const p = (idxFromStart - 1) / 2
3629        if (!(arr[startIndex + p] < tmp)) {
3630            break
3631        }
3632        arr[startIndex + idxFromStart] = arr[startIndex + p]
3633        idxFromStart = p
3634    }
3635    arr[startIndex + idxFromStart] = tmp
3636}
3637
3638// Build max heap with root in startIndex given its children are roots of valid heaps
3639function heapSortDown(arr: FixedArray<float>, idxFromStart: int, startIndex: int, endIndex: int): void {
3640    let heapRoot = startIndex + idxFromStart
3641    let arrIndex = heapRoot
3642    let childIndex = startIndex + idxFromStart * 2 + 1
3643    const tmp = arr[arrIndex]
3644    // Walk heap to bottom and pull max child up on each level
3645    while (childIndex + 1 < endIndex) {
3646        if ((arr[childIndex] < arr[childIndex + 1])) {
3647            childIndex++
3648        }
3649        arr[arrIndex] = arr[childIndex]
3650        arrIndex = childIndex
3651        childIndex = childIndex * 2 - startIndex + 1
3652    }
3653    if (childIndex < endIndex) {
3654        arr[arrIndex] = arr[childIndex]
3655        arrIndex = childIndex
3656    }
3657    arr[arrIndex] = tmp
3658    // Now heap is valid in all positions but arrIndex
3659    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot)
3660}
3661
3662export function heapSort(arr: FixedArray<float>, startIndex: int, endIndex: int): void {
3663    let len = endIndex - startIndex
3664    for (let i = len / 2 - 1; i >= 0; i--) {
3665        heapSortDown(arr, i, startIndex, endIndex)
3666    }
3667
3668    for (let i = endIndex - 1; i > startIndex; i--) {
3669        // move max element to the end of range
3670        const tmp = arr[i]
3671        arr[i] = arr[startIndex]
3672        arr[startIndex] = tmp
3673        heapSortDown(arr, 0, startIndex, i)
3674    }
3675}
3676
3677// Put median of three array elements to arr[index1]
3678function median3(arr: FixedArray<float>, index1: int, index2: int, index3: int): void {
3679    let swap_idx = index2
3680    if ((arr[index1] < arr[index2])) {
3681        if ((arr[index3] < arr[index1])) {
3682            return
3683        }
3684        if ((arr[index3] < arr[index2])) {
3685            swap_idx = index3
3686        }
3687    } else {
3688        if (!(arr[index3] < arr[index1])) {
3689            return
3690        }
3691        if ((arr[index2] < arr[index3])) {
3692            swap_idx = index3
3693        }
3694    }
3695    let tmp = arr[index1]
3696    arr[index1] = arr[swap_idx]
3697    arr[swap_idx] = tmp
3698}
3699
3700// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
3701// Elements equal to pivot go to the right
3702function quickSortSplit(arr: FixedArray<float>, startIndex: int, endIndex: int): int {
3703    const pivot = arr[startIndex]
3704    let i = startIndex + 1
3705    let j = endIndex - 1
3706    // No bounds check because pivot is median of three elements
3707    while ((arr[i] < pivot)) {
3708        i++
3709    }
3710    if (i == startIndex + 1) {
3711        while (i < j && !(arr[j] < pivot)) {
3712            j--
3713        }
3714    } else {
3715        while (!(arr[j] < pivot)) {
3716            j--
3717        }
3718    }
3719    while (i < j) {
3720        // Here !(arr[i] < pivot) and (arr[j] < pivot) holds
3721        let tmp = arr[i]
3722        arr[i] = arr[j]
3723        arr[j] = tmp
3724        while ((arr[++i] < pivot)) {}
3725        while (!(arr[--j] < pivot)) {}
3726    }
3727    let pivotIndex = i - 1
3728    arr[startIndex] = arr[pivotIndex]
3729    arr[pivotIndex] = pivot
3730
3731    return pivotIndex
3732}
3733
3734// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
3735// Elements equal to pivot go to the left
3736function quickSortSplitLeft(arr: FixedArray<float>, startIndex: int, endIndex: int): int {
3737    const pivot = arr[startIndex]
3738    let i = startIndex + 1
3739    let j = endIndex - 1
3740    // No bounds check because pivot is median of three elements
3741    while ((pivot < arr[j])) {
3742        j--
3743    }
3744    if (j + 1 == endIndex) {
3745        while (i < j && !(pivot < arr[i])) {
3746            i++
3747        }
3748    } else {
3749        while (!(pivot < arr[i])) {
3750            i++
3751        }
3752    }
3753    while (i < j) {
3754        // Here (pivot < arr[i]) and !(pivot < arr[j]) holds
3755        let tmp = arr[i]
3756        arr[i] = arr[j]
3757        arr[j] = tmp
3758        while (!(pivot < arr[++i])) {}
3759        while ((pivot < arr[--j])) {}
3760    }
3761    arr[startIndex] = arr[j]
3762    arr[j] = pivot
3763
3764    return j
3765}
3766
3767function quickSortImpl3(arr: FixedArray<float>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
3768    while (endIndex - startIndex > 3) {
3769        if (--maxDepth == 0) {
3770            heapSort(arr, startIndex, endIndex)
3771            return
3772        }
3773        // Here we assume that current interval is not the most left in the sorted range
3774        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
3775            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
3776            // after that only the right part needs to be sorted
3777            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
3778            // with many occurencies of the smallest element
3779            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
3780            continue
3781        }
3782
3783        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
3784        let p = quickSortSplit(arr, startIndex, endIndex)
3785        // make a call for the smaller part of array and continue processing the larger part in the loop
3786        if (p - startIndex < endIndex - p) {
3787            quickSortImpl3(arr, startIndex, p, maxDepth, initIndex)
3788            startIndex = p + 1
3789        } else {
3790            quickSortImpl3(arr, p + 1, endIndex, maxDepth, initIndex)
3791            endIndex = p
3792        }
3793    }
3794    insertionSort(arr, startIndex, endIndex, initIndex)
3795}
3796function quickSortImpl40(arr: FixedArray<float>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
3797    while (endIndex - startIndex > 40) {
3798        if (--maxDepth == 0) {
3799            heapSort(arr, startIndex, endIndex)
3800            return
3801        }
3802        // Here we assume that current interval is not the most left in the sorted range
3803        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
3804            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
3805            // after that only the right part needs to be sorted
3806            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
3807            // with many occurencies of the smallest element
3808            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
3809            continue
3810        }
3811
3812        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
3813        let p = quickSortSplit(arr, startIndex, endIndex)
3814        // make a call for the smaller part of array and continue processing the larger part in the loop
3815        if (p - startIndex < endIndex - p) {
3816            quickSortImpl40(arr, startIndex, p, maxDepth, initIndex)
3817            startIndex = p + 1
3818        } else {
3819            quickSortImpl40(arr, p + 1, endIndex, maxDepth, initIndex)
3820            endIndex = p
3821        }
3822    }
3823    insertionSort(arr, startIndex, endIndex, initIndex)
3824}
3825
3826function quickSort(arr: FixedArray<float>, startIndex: int, endIndex: int): void {
3827    let size = endIndex - startIndex
3828    if (size <= 1) {
3829        return
3830    }
3831    // find log of length to fall back into determenistic O(n logn) sort
3832    let bits = 32
3833    for (let i = 2; i < 31; i++) {
3834        if ((size >> i) == 0) {
3835            bits = i
3836            break
3837        }
3838    }
3839    quickSortImpl40(arr, startIndex, endIndex, bits * 3)
3840}
3841
3842/**
3843 * sorts arr in-place
3844 *
3845 * @param arr an array to sort
3846 *
3847 * @param startIndex an index to start sorting with, inclusive
3848 *
3849 * @param endIndex an index to end sorting, exclusive
3850 *
3851 * @example: sort array arr
3852 * ```
3853 * sort(arr, 0, arr.length)
3854 * ```
3855 */
3856export function sort(arr: FixedArray<float>, startIndex: int, endIndex: int): void {
3857    if (!checkRange(arr.length, startIndex, endIndex)) {
3858        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
3859    }
3860
3861    quickSort(arr, startIndex, endIndex);
3862}
3863
3864function bubbleSort(arr: FixedArray<float>, startIndex: int, endIndex: int, mustPrecede: (lhs: float, rhs: float) => boolean): void {
3865    let was = true
3866    while (was) {
3867        was = false
3868        for (let i = startIndex; i < endIndex - 1; i++) {
3869            if (mustPrecede(arr[i + 1], arr[i])) {
3870                const tmp = arr[i + 1]
3871                arr[i + 1] = arr[i]
3872                arr[i] = tmp
3873                was = true
3874            }
3875        }
3876    }
3877}
3878
3879function insertionSort(arr: FixedArray<float>, startIndex: int, endIndex: int, mustPrecede: (lhs: float, rhs: float) => boolean, initIndex: int = startIndex): void {
3880    if (startIndex != initIndex) {
3881        // arr[startIndex - 1] exists and is less than or equal to all elements in range
3882        for (let i = startIndex + 1; i < endIndex; i++) {
3883            const tmp = arr[i]
3884            let pos = i
3885            while (mustPrecede(tmp, arr[pos - 1])) {
3886                arr[pos] = arr[pos - 1]
3887                pos--
3888            }
3889            arr[pos] = tmp
3890        }
3891        return
3892    }
3893    for (let i = startIndex + 1; i < endIndex; i++) {
3894        const tmp = arr[i]
3895        if (mustPrecede(tmp, arr[startIndex])) {
3896            for (let j = i; j > startIndex; j--) {
3897                arr[j] = arr[j - 1]
3898            }
3899            arr[startIndex] = tmp
3900        } else {
3901            let pos = i
3902            while (mustPrecede(tmp, arr[pos - 1])) {
3903                arr[pos] = arr[pos - 1]
3904                pos--
3905            }
3906            arr[pos] = tmp
3907        }
3908    }
3909}
3910function heapSortUp(arr: FixedArray<float>, idxFromStart: int, startIndex: int, heapRoot: int, mustPrecede: (lhs: float, rhs: float) => boolean): void {
3911    const tmp = arr[startIndex + idxFromStart]
3912    while (startIndex + idxFromStart > heapRoot) {
3913        const p = (idxFromStart - 1) / 2
3914        if (!mustPrecede(arr[startIndex + p], tmp)) {
3915            break
3916        }
3917        arr[startIndex + idxFromStart] = arr[startIndex + p]
3918        idxFromStart = p
3919    }
3920    arr[startIndex + idxFromStart] = tmp
3921}
3922
3923// Build max heap with root in startIndex given its children are roots of valid heaps
3924function heapSortDown(arr: FixedArray<float>, idxFromStart: int, startIndex: int, endIndex: int, mustPrecede: (lhs: float, rhs: float) => boolean): void {
3925    let heapRoot = startIndex + idxFromStart
3926    let arrIndex = heapRoot
3927    let childIndex = startIndex + idxFromStart * 2 + 1
3928    const tmp = arr[arrIndex]
3929    // Walk heap to bottom and pull max child up on each level
3930    while (childIndex + 1 < endIndex) {
3931        if (mustPrecede(arr[childIndex], arr[childIndex + 1])) {
3932            childIndex++
3933        }
3934        arr[arrIndex] = arr[childIndex]
3935        arrIndex = childIndex
3936        childIndex = childIndex * 2 - startIndex + 1
3937    }
3938    if (childIndex < endIndex) {
3939        arr[arrIndex] = arr[childIndex]
3940        arrIndex = childIndex
3941    }
3942    arr[arrIndex] = tmp
3943    // Now heap is valid in all positions but arrIndex
3944    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot, mustPrecede)
3945}
3946
3947export function heapSort(arr: FixedArray<float>, startIndex: int, endIndex: int, mustPrecede: (lhs: float, rhs: float) => boolean): void {
3948    let len = endIndex - startIndex
3949    for (let i = len / 2 - 1; i >= 0; i--) {
3950        heapSortDown(arr, i, startIndex, endIndex, mustPrecede)
3951    }
3952
3953    for (let i = endIndex - 1; i > startIndex; i--) {
3954        // move max element to the end of range
3955        const tmp = arr[i]
3956        arr[i] = arr[startIndex]
3957        arr[startIndex] = tmp
3958        heapSortDown(arr, 0, startIndex, i, mustPrecede)
3959    }
3960}
3961
3962// Put median of three array elements to arr[index1]
3963function median3(arr: FixedArray<float>, index1: int, index2: int, index3: int, mustPrecede: (lhs: float, rhs: float) => boolean): void {
3964    let swap_idx = index2
3965    if (mustPrecede(arr[index1], arr[index2])) {
3966        if (mustPrecede(arr[index3], arr[index1])) {
3967            return
3968        }
3969        if (mustPrecede(arr[index3], arr[index2])) {
3970            swap_idx = index3
3971        }
3972    } else {
3973        if (!mustPrecede(arr[index3], arr[index1])) {
3974            return
3975        }
3976        if (mustPrecede(arr[index2], arr[index3])) {
3977            swap_idx = index3
3978        }
3979    }
3980    let tmp = arr[index1]
3981    arr[index1] = arr[swap_idx]
3982    arr[swap_idx] = tmp
3983}
3984
3985// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
3986// Elements equal to pivot go to the right
3987function quickSortSplit(arr: FixedArray<float>, startIndex: int, endIndex: int, mustPrecede: (lhs: float, rhs: float) => boolean): int {
3988    const pivot = arr[startIndex]
3989    let i = startIndex + 1
3990    let j = endIndex - 1
3991    // No bounds check because pivot is median of three elements
3992    while (mustPrecede(arr[i], pivot)) {
3993        i++
3994    }
3995    if (i == startIndex + 1) {
3996        while (i < j && !mustPrecede(arr[j], pivot)) {
3997            j--
3998        }
3999    } else {
4000        while (!mustPrecede(arr[j], pivot)) {
4001            j--
4002        }
4003    }
4004    while (i < j) {
4005        // Here !mustPrecede(arr[i], pivot) and mustPrecede(arr[j], pivot) holds
4006        let tmp = arr[i]
4007        arr[i] = arr[j]
4008        arr[j] = tmp
4009        while (mustPrecede(arr[++i], pivot)) {}
4010        while (!mustPrecede(arr[--j], pivot)) {}
4011    }
4012    let pivotIndex = i - 1
4013    arr[startIndex] = arr[pivotIndex]
4014    arr[pivotIndex] = pivot
4015
4016    return pivotIndex
4017}
4018
4019// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
4020// Elements equal to pivot go to the left
4021function quickSortSplitLeft(arr: FixedArray<float>, startIndex: int, endIndex: int, mustPrecede: (lhs: float, rhs: float) => boolean): int {
4022    const pivot = arr[startIndex]
4023    let i = startIndex + 1
4024    let j = endIndex - 1
4025    // No bounds check because pivot is median of three elements
4026    while (mustPrecede(pivot, arr[j])) {
4027        j--
4028    }
4029    if (j + 1 == endIndex) {
4030        while (i < j && !mustPrecede(pivot, arr[i])) {
4031            i++
4032        }
4033    } else {
4034        while (!mustPrecede(pivot, arr[i])) {
4035            i++
4036        }
4037    }
4038    while (i < j) {
4039        // Here mustPrecede(pivot, arr[i]) and !mustPrecede(pivot, arr[j]) holds
4040        let tmp = arr[i]
4041        arr[i] = arr[j]
4042        arr[j] = tmp
4043        while (!mustPrecede(pivot, arr[++i])) {}
4044        while (mustPrecede(pivot, arr[--j])) {}
4045    }
4046    arr[startIndex] = arr[j]
4047    arr[j] = pivot
4048
4049    return j
4050}
4051
4052function quickSortImpl3(arr: FixedArray<float>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: float, rhs: float) => boolean): void {
4053    while (endIndex - startIndex > 3) {
4054        if (--maxDepth == 0) {
4055            heapSort(arr, startIndex, endIndex, mustPrecede)
4056            return
4057        }
4058
4059        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
4060        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
4061        // make a call for the smaller part of array and continue processing the larger part in the loop
4062        if (p - startIndex < endIndex - p) {
4063            quickSortImpl3(arr, startIndex, p, maxDepth, mustPrecede)
4064            startIndex = p + 1
4065        } else {
4066            quickSortImpl3(arr, p + 1, endIndex, maxDepth, mustPrecede)
4067            endIndex = p
4068        }
4069    }
4070    insertionSort(arr, startIndex, endIndex, mustPrecede)
4071}
4072function quickSortImpl40(arr: FixedArray<float>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: float, rhs: float) => boolean): void {
4073    while (endIndex - startIndex > 40) {
4074        if (--maxDepth == 0) {
4075            heapSort(arr, startIndex, endIndex, mustPrecede)
4076            return
4077        }
4078
4079        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
4080        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
4081        // make a call for the smaller part of array and continue processing the larger part in the loop
4082        if (p - startIndex < endIndex - p) {
4083            quickSortImpl40(arr, startIndex, p, maxDepth, mustPrecede)
4084            startIndex = p + 1
4085        } else {
4086            quickSortImpl40(arr, p + 1, endIndex, maxDepth, mustPrecede)
4087            endIndex = p
4088        }
4089    }
4090    insertionSort(arr, startIndex, endIndex, mustPrecede)
4091}
4092
4093function quickSort(arr: FixedArray<float>, startIndex: int, endIndex: int, mustPrecede: (lhs: float, rhs: float) => boolean): void {
4094    let size = endIndex - startIndex
4095    if (size <= 1) {
4096        return
4097    }
4098    // find log of length to fall back into determenistic O(n logn) sort
4099    let bits = 32
4100    for (let i = 2; i < 31; i++) {
4101        if ((size >> i) == 0) {
4102            bits = i
4103            break
4104        }
4105    }
4106    quickSortImpl40(arr, startIndex, endIndex, bits * 3, mustPrecede)
4107}
4108
4109/**
4110 * sorts arr in-place
4111 *
4112 * @param arr an array to sort
4113 *
4114 * @param startIndex an index to start sorting with, inclusive
4115 *
4116 * @param endIndex an index to end sorting, exclusive
4117 *
4118 * @example: sort array arr
4119 * ```
4120 * sort(arr, 0, arr.length)
4121 * ```
4122 */
4123export function sort_subarray(arr: FixedArray<float>, startIndex: int, endIndex: int, mustPrecede: (lhs: float, rhs: float) => boolean): void {
4124    if (!checkRange(arr.length, startIndex, endIndex)) {
4125        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
4126    }
4127
4128    quickSort(arr, startIndex, endIndex, mustPrecede);
4129}
4130
4131/**
4132 * sorts arr in-place
4133 *
4134 * @param arr an array to sort
4135 */
4136export function sort_subarray(arr: FixedArray<float>, mustPrecede: (lhs: float, rhs: float) => boolean): void {
4137    sort_subarray(arr, 0, arr.length, mustPrecede);
4138}
4139
4140export function sort_subarray(arr: FixedArray<float>, startIndex: int, mustPrecede: (lhs: float, rhs: float) => boolean): void {
4141    sort_subarray(arr, startIndex, arr.length, mustPrecede)
4142}
4143
4144// ======== tests section ========
4145
4146/** note: used for tests, {@link test_sortAllOn} */
4147class test_SortDatafloat {
4148    name: string
4149    arr: FixedArray<float>
4150
4151    constructor(name: string, arr: FixedArray<float>) {
4152        this.name = name
4153        this.arr = arr
4154    }
4155}
4156
4157/** note: used for tests, {@link test_sortAllOn} */
4158function test_sortCopy(arr: FixedArray<float>): FixedArray<float> {
4159    let c : FixedArray<float> = new float[arr.length]
4160    for (let i = 0; i < arr.length; i++) {
4161        c[i] = arr[i]
4162    }
4163    return c
4164}
4165
4166/** note: used for tests, {@link test_sortAllOn} */
4167function test_sortAllOnCmpFwd_float(l: float, r: float): boolean {
4168    return l < r
4169}
4170
4171/** note: used for tests, {@link test_sortAllOn} */
4172function test_sortAllOnCmpInv_float(l: float, r: float): boolean {
4173    return r < l
4174}
4175
4176/** note: used for tests, {@link test_sortAllOn} */
4177function test_printArr(arr: FixedArray<float>, startIndex: int, endIndex: int) {
4178    for (let i = startIndex; i < endIndex; i++) {
4179        console.print(arr[i] + ' ')
4180    }
4181    console.println('')
4182}
4183
4184/**
4185 * Function used to test sorting in standard library tests.
4186 * There is only one exported function: `sort`, but for testing
4187 * we need access to all sub sorts that are used. Hence this part of "tests"
4188 * is located here. All related entities are prefixed whith `test_` to stand out
4189 */
4190export function test_sortAllOn(arr: FixedArray<float>) {
4191    // block for comparator ``
4192    if (true) {
4193        const sorts = new Array<test_SortDatafloat>()
4194        const bubbled = test_sortCopy(arr)
4195        bubbleSort(bubbled, 0, bubbled.length)
4196        sorts.push(new test_SortDatafloat("bubble", bubbled))
4197
4198        const insertion = test_sortCopy(arr)
4199        insertionSort(insertion, 0, insertion.length)
4200        sorts.push(new test_SortDatafloat("insertion", insertion))
4201
4202        const heaped = test_sortCopy(arr)
4203        heapSort(heaped, 0, heaped.length)
4204        sorts.push(new test_SortDatafloat("heap", heaped))
4205
4206        const quicked = test_sortCopy(arr)
4207        quickSortImpl3(quicked, 0, quicked.length, quicked.length)
4208        sorts.push(new test_SortDatafloat("quick", quicked))
4209
4210        const quickedInsertion = test_sortCopy(arr)
4211        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length)
4212        sorts.push(new test_SortDatafloat("quick with insertion", quickedInsertion))
4213
4214        const just = test_sortCopy(arr)
4215        sort(just)
4216        sorts.push(new test_SortDatafloat("sort()", just))
4217
4218        let sort0 = sorts.at(0)!
4219        for (let s = 1; s < sorts.length; s++) {
4220            let sortS = sorts.at(s)!
4221            for (let i = 0; i < arr.length; i++) {
4222                if (sort0.arr[i] != sortS.arr[i]) {
4223                    console.println(sort0.name + ': ')
4224                    test_printArr(sort0.arr, 0, arr.length)
4225                    console.println(sortS.name + ': ')
4226                    test_printArr(sortS.arr, 0, arr.length)
4227                    throw new Error("sorts are not equal: " + sort0.name + ' ' + sortS.name + ' for float')
4228                }
4229            }
4230        }
4231    }
4232    // block for comparator `est_sortAllOnCmpFwd_float`
4233    if (true) {
4234        const sorts = new Array<test_SortDatafloat>()
4235        const bubbled = test_sortCopy(arr)
4236        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpFwd_float)
4237        sorts.push(new test_SortDatafloat("bubble", bubbled))
4238
4239        const insertion = test_sortCopy(arr)
4240        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpFwd_float)
4241        sorts.push(new test_SortDatafloat("insertion", insertion))
4242
4243        const heaped = test_sortCopy(arr)
4244        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpFwd_float)
4245        sorts.push(new test_SortDatafloat("heap", heaped))
4246
4247        const quicked = test_sortCopy(arr)
4248        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpFwd_float)
4249        sorts.push(new test_SortDatafloat("quick", quicked))
4250
4251        const quickedInsertion = test_sortCopy(arr)
4252        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpFwd_float)
4253        sorts.push(new test_SortDatafloat("quick with insertion", quickedInsertion))
4254
4255        const just = test_sortCopy(arr)
4256        sort_subarray(just, test_sortAllOnCmpFwd_float)
4257        sorts.push(new test_SortDatafloat("sort()", just))
4258
4259        let sort0 = sorts.at(0)!
4260        for (let s = 1; s < sorts.length; s++) {
4261            let sortS = sorts.at(s)!
4262            for (let i = 0; i < arr.length; i++) {
4263                if (sort0.arr[i] != sortS.arr[i]) {
4264                    console.println(sort0.name + ': ')
4265                    test_printArr(sort0.arr, 0, arr.length)
4266                    console.println(sortS.name + ': ')
4267                    test_printArr(sortS.arr, 0, arr.length)
4268                    throw new Error("sorts est_sortAllOnCmpFwd_float are not equal: " + sort0.name + ' ' + sortS.name + ' for float')
4269                }
4270            }
4271        }
4272    }
4273    // block for comparator `est_sortAllOnCmpInv_float`
4274    if (true) {
4275        const sorts = new Array<test_SortDatafloat>()
4276        const bubbled = test_sortCopy(arr)
4277        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpInv_float)
4278        sorts.push(new test_SortDatafloat("bubble", bubbled))
4279
4280        const insertion = test_sortCopy(arr)
4281        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpInv_float)
4282        sorts.push(new test_SortDatafloat("insertion", insertion))
4283
4284        const heaped = test_sortCopy(arr)
4285        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpInv_float)
4286        sorts.push(new test_SortDatafloat("heap", heaped))
4287
4288        const quicked = test_sortCopy(arr)
4289        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpInv_float)
4290        sorts.push(new test_SortDatafloat("quick", quicked))
4291
4292        const quickedInsertion = test_sortCopy(arr)
4293        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpInv_float)
4294        sorts.push(new test_SortDatafloat("quick with insertion", quickedInsertion))
4295
4296        const just = test_sortCopy(arr)
4297        sort_subarray(just, test_sortAllOnCmpInv_float)
4298        sorts.push(new test_SortDatafloat("sort()", just))
4299
4300        let sort0 = sorts.at(0)!
4301        for (let s = 1; s < sorts.length; s++) {
4302            let sortS = sorts.at(s)!
4303            for (let i = 0; i < arr.length; i++) {
4304                if (sort0.arr[i] != sortS.arr[i]) {
4305                    console.println(sort0.name + ': ')
4306                    test_printArr(sort0.arr, 0, arr.length)
4307                    console.println(sortS.name + ': ')
4308                    test_printArr(sortS.arr, 0, arr.length)
4309                    throw new Error("sorts est_sortAllOnCmpInv_float are not equal: " + sort0.name + ' ' + sortS.name + ' for float')
4310                }
4311            }
4312        }
4313    }
4314}
4315// ======== end of tests section ========
4316
4317function copyPart(dst: FixedArray<double>, counter: int, src: FixedArray<double>, start: int, end?: int) {
4318    if (end == undefined) {
4319        end = src.length
4320    }
4321    for (let i = start; i < end!; ++i) {
4322        dst[counter++] = src[i]
4323    }
4324}
4325
4326function merge(left: FixedArray<double>, right: FixedArray<double>, cmp: (lhs: double, rhs: double) => number): FixedArray<double> {
4327    const result:FixedArray<double> = new double[right.length + left.length]
4328    let leftIndex = 0;
4329    let rightIndex = 0;
4330    let counter: int = 0
4331
4332    while (leftIndex < left.length &&
4333        rightIndex < right.length) {
4334        if (cmp(left[leftIndex], right[rightIndex]) <= 0) {
4335            result[counter++] = left[leftIndex];
4336            leftIndex++;
4337        } else {
4338            result[counter++] = right[rightIndex]
4339            rightIndex++;
4340        }
4341    }
4342    copyPart(result, counter, left, leftIndex)
4343    copyPart(result, counter, right, rightIndex)
4344
4345    return result
4346}
4347
4348export function mergeSort(array: FixedArray<double>, cmp: (lhs: double, rhs: double) => number, begin: int = 0, end: int = 0): FixedArray<double> {
4349    if (end == 0) {
4350        end = array.length
4351    }
4352    const arrLength = end - begin
4353    if (arrLength <= 1) {
4354        return array;
4355    }
4356    const middle = Math.floor(begin + arrLength / 2) as int
4357    const leftHalf:FixedArray<double> = new double[middle]
4358    let counter: int = 0
4359    copyPart(leftHalf, counter, array, 0, middle)
4360
4361    counter = 0
4362    const rightHalf:FixedArray<double> = new double[arrLength - middle]
4363    copyPart(rightHalf, counter, array, middle)
4364
4365    return merge(mergeSort(leftHalf, cmp), mergeSort(rightHalf, cmp), cmp);
4366}
4367
4368function bubbleSort(arr: FixedArray<double>, startIndex: int, endIndex: int): void {
4369    let was = true
4370    while (was) {
4371        was = false
4372        for (let i = startIndex; i < endIndex - 1; i++) {
4373            if ((arr[i + 1] < arr[i])) {
4374                const tmp = arr[i + 1]
4375                arr[i + 1] = arr[i]
4376                arr[i] = tmp
4377                was = true
4378            }
4379        }
4380    }
4381}
4382
4383function insertionSort(arr: FixedArray<double>, startIndex: int, endIndex: int, initIndex: int = startIndex): void {
4384    if (startIndex != initIndex) {
4385        // arr[startIndex - 1] exists and is less than or equal to all elements in range
4386        for (let i = startIndex + 1; i < endIndex; i++) {
4387            const tmp = arr[i]
4388            let pos = i
4389            while ((tmp < arr[pos - 1])) {
4390                arr[pos] = arr[pos - 1]
4391                pos--
4392            }
4393            arr[pos] = tmp
4394        }
4395        return
4396    }
4397    for (let i = startIndex + 1; i < endIndex; i++) {
4398        const tmp = arr[i]
4399        if ((tmp < arr[startIndex])) {
4400            for (let j = i; j > startIndex; j--) {
4401                arr[j] = arr[j - 1]
4402            }
4403            arr[startIndex] = tmp
4404        } else {
4405            let pos = i
4406            while ((tmp < arr[pos - 1])) {
4407                arr[pos] = arr[pos - 1]
4408                pos--
4409            }
4410            arr[pos] = tmp
4411        }
4412    }
4413}
4414function heapSortUp(arr: FixedArray<double>, idxFromStart: int, startIndex: int, heapRoot: int): void {
4415    const tmp = arr[startIndex + idxFromStart]
4416    while (startIndex + idxFromStart > heapRoot) {
4417        const p = (idxFromStart - 1) / 2
4418        if (!(arr[startIndex + p] < tmp)) {
4419            break
4420        }
4421        arr[startIndex + idxFromStart] = arr[startIndex + p]
4422        idxFromStart = p
4423    }
4424    arr[startIndex + idxFromStart] = tmp
4425}
4426
4427// Build max heap with root in startIndex given its children are roots of valid heaps
4428function heapSortDown(arr: FixedArray<double>, idxFromStart: int, startIndex: int, endIndex: int): void {
4429    let heapRoot = startIndex + idxFromStart
4430    let arrIndex = heapRoot
4431    let childIndex = startIndex + idxFromStart * 2 + 1
4432    const tmp = arr[arrIndex]
4433    // Walk heap to bottom and pull max child up on each level
4434    while (childIndex + 1 < endIndex) {
4435        if ((arr[childIndex] < arr[childIndex + 1])) {
4436            childIndex++
4437        }
4438        arr[arrIndex] = arr[childIndex]
4439        arrIndex = childIndex
4440        childIndex = childIndex * 2 - startIndex + 1
4441    }
4442    if (childIndex < endIndex) {
4443        arr[arrIndex] = arr[childIndex]
4444        arrIndex = childIndex
4445    }
4446    arr[arrIndex] = tmp
4447    // Now heap is valid in all positions but arrIndex
4448    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot)
4449}
4450
4451export function heapSort(arr: FixedArray<double>, startIndex: int, endIndex: int): void {
4452    let len = endIndex - startIndex
4453    for (let i = len / 2 - 1; i >= 0; i--) {
4454        heapSortDown(arr, i, startIndex, endIndex)
4455    }
4456
4457    for (let i = endIndex - 1; i > startIndex; i--) {
4458        // move max element to the end of range
4459        const tmp = arr[i]
4460        arr[i] = arr[startIndex]
4461        arr[startIndex] = tmp
4462        heapSortDown(arr, 0, startIndex, i)
4463    }
4464}
4465
4466// Put median of three array elements to arr[index1]
4467function median3(arr: FixedArray<double>, index1: int, index2: int, index3: int): void {
4468    let swap_idx = index2
4469    if ((arr[index1] < arr[index2])) {
4470        if ((arr[index3] < arr[index1])) {
4471            return
4472        }
4473        if ((arr[index3] < arr[index2])) {
4474            swap_idx = index3
4475        }
4476    } else {
4477        if (!(arr[index3] < arr[index1])) {
4478            return
4479        }
4480        if ((arr[index2] < arr[index3])) {
4481            swap_idx = index3
4482        }
4483    }
4484    let tmp = arr[index1]
4485    arr[index1] = arr[swap_idx]
4486    arr[swap_idx] = tmp
4487}
4488
4489// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
4490// Elements equal to pivot go to the right
4491function quickSortSplit(arr: FixedArray<double>, startIndex: int, endIndex: int): int {
4492    const pivot = arr[startIndex]
4493    let i = startIndex + 1
4494    let j = endIndex - 1
4495    // No bounds check because pivot is median of three elements
4496    while ((arr[i] < pivot)) {
4497        i++
4498    }
4499    if (i == startIndex + 1) {
4500        while (i < j && !(arr[j] < pivot)) {
4501            j--
4502        }
4503    } else {
4504        while (!(arr[j] < pivot)) {
4505            j--
4506        }
4507    }
4508    while (i < j) {
4509        // Here !(arr[i] < pivot) and (arr[j] < pivot) holds
4510        let tmp = arr[i]
4511        arr[i] = arr[j]
4512        arr[j] = tmp
4513        while ((arr[++i] < pivot)) {}
4514        while (!(arr[--j] < pivot)) {}
4515    }
4516    let pivotIndex = i - 1
4517    arr[startIndex] = arr[pivotIndex]
4518    arr[pivotIndex] = pivot
4519
4520    return pivotIndex
4521}
4522
4523// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
4524// Elements equal to pivot go to the left
4525function quickSortSplitLeft(arr: FixedArray<double>, startIndex: int, endIndex: int): int {
4526    const pivot = arr[startIndex]
4527    let i = startIndex + 1
4528    let j = endIndex - 1
4529    // No bounds check because pivot is median of three elements
4530    while ((pivot < arr[j])) {
4531        j--
4532    }
4533    if (j + 1 == endIndex) {
4534        while (i < j && !(pivot < arr[i])) {
4535            i++
4536        }
4537    } else {
4538        while (!(pivot < arr[i])) {
4539            i++
4540        }
4541    }
4542    while (i < j) {
4543        // Here (pivot < arr[i]) and !(pivot < arr[j]) holds
4544        let tmp = arr[i]
4545        arr[i] = arr[j]
4546        arr[j] = tmp
4547        while (!(pivot < arr[++i])) {}
4548        while ((pivot < arr[--j])) {}
4549    }
4550    arr[startIndex] = arr[j]
4551    arr[j] = pivot
4552
4553    return j
4554}
4555
4556function quickSortImpl3(arr: FixedArray<double>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
4557    while (endIndex - startIndex > 3) {
4558        if (--maxDepth == 0) {
4559            heapSort(arr, startIndex, endIndex)
4560            return
4561        }
4562        // Here we assume that current interval is not the most left in the sorted range
4563        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
4564            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
4565            // after that only the right part needs to be sorted
4566            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
4567            // with many occurencies of the smallest element
4568            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
4569            continue
4570        }
4571
4572        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
4573        let p = quickSortSplit(arr, startIndex, endIndex)
4574        // make a call for the smaller part of array and continue processing the larger part in the loop
4575        if (p - startIndex < endIndex - p) {
4576            quickSortImpl3(arr, startIndex, p, maxDepth, initIndex)
4577            startIndex = p + 1
4578        } else {
4579            quickSortImpl3(arr, p + 1, endIndex, maxDepth, initIndex)
4580            endIndex = p
4581        }
4582    }
4583    insertionSort(arr, startIndex, endIndex, initIndex)
4584}
4585function quickSortImpl40(arr: FixedArray<double>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
4586    while (endIndex - startIndex > 40) {
4587        if (--maxDepth == 0) {
4588            heapSort(arr, startIndex, endIndex)
4589            return
4590        }
4591        // Here we assume that current interval is not the most left in the sorted range
4592        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
4593            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
4594            // after that only the right part needs to be sorted
4595            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
4596            // with many occurencies of the smallest element
4597            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
4598            continue
4599        }
4600
4601        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
4602        let p = quickSortSplit(arr, startIndex, endIndex)
4603        // make a call for the smaller part of array and continue processing the larger part in the loop
4604        if (p - startIndex < endIndex - p) {
4605            quickSortImpl40(arr, startIndex, p, maxDepth, initIndex)
4606            startIndex = p + 1
4607        } else {
4608            quickSortImpl40(arr, p + 1, endIndex, maxDepth, initIndex)
4609            endIndex = p
4610        }
4611    }
4612    insertionSort(arr, startIndex, endIndex, initIndex)
4613}
4614
4615function quickSort(arr: FixedArray<double>, startIndex: int, endIndex: int): void {
4616    let size = endIndex - startIndex
4617    if (size <= 1) {
4618        return
4619    }
4620    // find log of length to fall back into determenistic O(n logn) sort
4621    let bits = 32
4622    for (let i = 2; i < 31; i++) {
4623        if ((size >> i) == 0) {
4624            bits = i
4625            break
4626        }
4627    }
4628    quickSortImpl40(arr, startIndex, endIndex, bits * 3)
4629}
4630
4631/**
4632 * sorts arr in-place
4633 *
4634 * @param arr an array to sort
4635 *
4636 * @param startIndex an index to start sorting with, inclusive
4637 *
4638 * @param endIndex an index to end sorting, exclusive
4639 *
4640 * @example: sort array arr
4641 * ```
4642 * sort(arr, 0, arr.length)
4643 * ```
4644 */
4645export function sort(arr: FixedArray<double>, startIndex: int, endIndex: int): void {
4646    if (!checkRange(arr.length, startIndex, endIndex)) {
4647        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
4648    }
4649
4650    quickSort(arr, startIndex, endIndex);
4651}
4652
4653function bubbleSort(arr: FixedArray<double>, startIndex: int, endIndex: int, mustPrecede: (lhs: double, rhs: double) => boolean): void {
4654    let was = true
4655    while (was) {
4656        was = false
4657        for (let i = startIndex; i < endIndex - 1; i++) {
4658            if (mustPrecede(arr[i + 1], arr[i])) {
4659                const tmp = arr[i + 1]
4660                arr[i + 1] = arr[i]
4661                arr[i] = tmp
4662                was = true
4663            }
4664        }
4665    }
4666}
4667
4668function insertionSort(arr: FixedArray<double>, startIndex: int, endIndex: int, mustPrecede: (lhs: double, rhs: double) => boolean, initIndex: int = startIndex): void {
4669    if (startIndex != initIndex) {
4670        // arr[startIndex - 1] exists and is less than or equal to all elements in range
4671        for (let i = startIndex + 1; i < endIndex; i++) {
4672            const tmp = arr[i]
4673            let pos = i
4674            while (mustPrecede(tmp, arr[pos - 1])) {
4675                arr[pos] = arr[pos - 1]
4676                pos--
4677            }
4678            arr[pos] = tmp
4679        }
4680        return
4681    }
4682    for (let i = startIndex + 1; i < endIndex; i++) {
4683        const tmp = arr[i]
4684        if (mustPrecede(tmp, arr[startIndex])) {
4685            for (let j = i; j > startIndex; j--) {
4686                arr[j] = arr[j - 1]
4687            }
4688            arr[startIndex] = tmp
4689        } else {
4690            let pos = i
4691            while (mustPrecede(tmp, arr[pos - 1])) {
4692                arr[pos] = arr[pos - 1]
4693                pos--
4694            }
4695            arr[pos] = tmp
4696        }
4697    }
4698}
4699function heapSortUp(arr: FixedArray<double>, idxFromStart: int, startIndex: int, heapRoot: int, mustPrecede: (lhs: double, rhs: double) => boolean): void {
4700    const tmp = arr[startIndex + idxFromStart]
4701    while (startIndex + idxFromStart > heapRoot) {
4702        const p = (idxFromStart - 1) / 2
4703        if (!mustPrecede(arr[startIndex + p], tmp)) {
4704            break
4705        }
4706        arr[startIndex + idxFromStart] = arr[startIndex + p]
4707        idxFromStart = p
4708    }
4709    arr[startIndex + idxFromStart] = tmp
4710}
4711
4712// Build max heap with root in startIndex given its children are roots of valid heaps
4713function heapSortDown(arr: FixedArray<double>, idxFromStart: int, startIndex: int, endIndex: int, mustPrecede: (lhs: double, rhs: double) => boolean): void {
4714    let heapRoot = startIndex + idxFromStart
4715    let arrIndex = heapRoot
4716    let childIndex = startIndex + idxFromStart * 2 + 1
4717    const tmp = arr[arrIndex]
4718    // Walk heap to bottom and pull max child up on each level
4719    while (childIndex + 1 < endIndex) {
4720        if (mustPrecede(arr[childIndex], arr[childIndex + 1])) {
4721            childIndex++
4722        }
4723        arr[arrIndex] = arr[childIndex]
4724        arrIndex = childIndex
4725        childIndex = childIndex * 2 - startIndex + 1
4726    }
4727    if (childIndex < endIndex) {
4728        arr[arrIndex] = arr[childIndex]
4729        arrIndex = childIndex
4730    }
4731    arr[arrIndex] = tmp
4732    // Now heap is valid in all positions but arrIndex
4733    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot, mustPrecede)
4734}
4735
4736export function heapSort(arr: FixedArray<double>, startIndex: int, endIndex: int, mustPrecede: (lhs: double, rhs: double) => boolean): void {
4737    let len = endIndex - startIndex
4738    for (let i = len / 2 - 1; i >= 0; i--) {
4739        heapSortDown(arr, i, startIndex, endIndex, mustPrecede)
4740    }
4741
4742    for (let i = endIndex - 1; i > startIndex; i--) {
4743        // move max element to the end of range
4744        const tmp = arr[i]
4745        arr[i] = arr[startIndex]
4746        arr[startIndex] = tmp
4747        heapSortDown(arr, 0, startIndex, i, mustPrecede)
4748    }
4749}
4750
4751// Put median of three array elements to arr[index1]
4752function median3(arr: FixedArray<double>, index1: int, index2: int, index3: int, mustPrecede: (lhs: double, rhs: double) => boolean): void {
4753    let swap_idx = index2
4754    if (mustPrecede(arr[index1], arr[index2])) {
4755        if (mustPrecede(arr[index3], arr[index1])) {
4756            return
4757        }
4758        if (mustPrecede(arr[index3], arr[index2])) {
4759            swap_idx = index3
4760        }
4761    } else {
4762        if (!mustPrecede(arr[index3], arr[index1])) {
4763            return
4764        }
4765        if (mustPrecede(arr[index2], arr[index3])) {
4766            swap_idx = index3
4767        }
4768    }
4769    let tmp = arr[index1]
4770    arr[index1] = arr[swap_idx]
4771    arr[swap_idx] = tmp
4772}
4773
4774// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
4775// Elements equal to pivot go to the right
4776function quickSortSplit(arr: FixedArray<double>, startIndex: int, endIndex: int, mustPrecede: (lhs: double, rhs: double) => boolean): int {
4777    const pivot = arr[startIndex]
4778    let i = startIndex + 1
4779    let j = endIndex - 1
4780    // No bounds check because pivot is median of three elements
4781    while (mustPrecede(arr[i], pivot)) {
4782        i++
4783    }
4784    if (i == startIndex + 1) {
4785        while (i < j && !mustPrecede(arr[j], pivot)) {
4786            j--
4787        }
4788    } else {
4789        while (!mustPrecede(arr[j], pivot)) {
4790            j--
4791        }
4792    }
4793    while (i < j) {
4794        // Here !mustPrecede(arr[i], pivot) and mustPrecede(arr[j], pivot) holds
4795        let tmp = arr[i]
4796        arr[i] = arr[j]
4797        arr[j] = tmp
4798        while (mustPrecede(arr[++i], pivot)) {}
4799        while (!mustPrecede(arr[--j], pivot)) {}
4800    }
4801    let pivotIndex = i - 1
4802    arr[startIndex] = arr[pivotIndex]
4803    arr[pivotIndex] = pivot
4804
4805    return pivotIndex
4806}
4807
4808// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
4809// Elements equal to pivot go to the left
4810function quickSortSplitLeft(arr: FixedArray<double>, startIndex: int, endIndex: int, mustPrecede: (lhs: double, rhs: double) => boolean): int {
4811    const pivot = arr[startIndex]
4812    let i = startIndex + 1
4813    let j = endIndex - 1
4814    // No bounds check because pivot is median of three elements
4815    while (mustPrecede(pivot, arr[j])) {
4816        j--
4817    }
4818    if (j + 1 == endIndex) {
4819        while (i < j && !mustPrecede(pivot, arr[i])) {
4820            i++
4821        }
4822    } else {
4823        while (!mustPrecede(pivot, arr[i])) {
4824            i++
4825        }
4826    }
4827    while (i < j) {
4828        // Here mustPrecede(pivot, arr[i]) and !mustPrecede(pivot, arr[j]) holds
4829        let tmp = arr[i]
4830        arr[i] = arr[j]
4831        arr[j] = tmp
4832        while (!mustPrecede(pivot, arr[++i])) {}
4833        while (mustPrecede(pivot, arr[--j])) {}
4834    }
4835    arr[startIndex] = arr[j]
4836    arr[j] = pivot
4837
4838    return j
4839}
4840
4841function quickSortImpl3(arr: FixedArray<double>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: double, rhs: double) => boolean): void {
4842    while (endIndex - startIndex > 3) {
4843        if (--maxDepth == 0) {
4844            heapSort(arr, startIndex, endIndex, mustPrecede)
4845            return
4846        }
4847
4848        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
4849        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
4850        // make a call for the smaller part of array and continue processing the larger part in the loop
4851        if (p - startIndex < endIndex - p) {
4852            quickSortImpl3(arr, startIndex, p, maxDepth, mustPrecede)
4853            startIndex = p + 1
4854        } else {
4855            quickSortImpl3(arr, p + 1, endIndex, maxDepth, mustPrecede)
4856            endIndex = p
4857        }
4858    }
4859    insertionSort(arr, startIndex, endIndex, mustPrecede)
4860}
4861function quickSortImpl40(arr: FixedArray<double>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: double, rhs: double) => boolean): void {
4862    while (endIndex - startIndex > 40) {
4863        if (--maxDepth == 0) {
4864            heapSort(arr, startIndex, endIndex, mustPrecede)
4865            return
4866        }
4867
4868        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
4869        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
4870        // make a call for the smaller part of array and continue processing the larger part in the loop
4871        if (p - startIndex < endIndex - p) {
4872            quickSortImpl40(arr, startIndex, p, maxDepth, mustPrecede)
4873            startIndex = p + 1
4874        } else {
4875            quickSortImpl40(arr, p + 1, endIndex, maxDepth, mustPrecede)
4876            endIndex = p
4877        }
4878    }
4879    insertionSort(arr, startIndex, endIndex, mustPrecede)
4880}
4881
4882function quickSort(arr: FixedArray<double>, startIndex: int, endIndex: int, mustPrecede: (lhs: double, rhs: double) => boolean): void {
4883    let size = endIndex - startIndex
4884    if (size <= 1) {
4885        return
4886    }
4887    // find log of length to fall back into determenistic O(n logn) sort
4888    let bits = 32
4889    for (let i = 2; i < 31; i++) {
4890        if ((size >> i) == 0) {
4891            bits = i
4892            break
4893        }
4894    }
4895    quickSortImpl40(arr, startIndex, endIndex, bits * 3, mustPrecede)
4896}
4897
4898/**
4899 * sorts arr in-place
4900 *
4901 * @param arr an array to sort
4902 *
4903 * @param startIndex an index to start sorting with, inclusive
4904 *
4905 * @param endIndex an index to end sorting, exclusive
4906 *
4907 * @example: sort array arr
4908 * ```
4909 * sort(arr, 0, arr.length)
4910 * ```
4911 */
4912export function sort_subarray(arr: FixedArray<double>, startIndex: int, endIndex: int, mustPrecede: (lhs: double, rhs: double) => boolean): void {
4913    if (!checkRange(arr.length, startIndex, endIndex)) {
4914        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
4915    }
4916
4917    quickSort(arr, startIndex, endIndex, mustPrecede);
4918}
4919
4920/**
4921 * sorts arr in-place
4922 *
4923 * @param arr an array to sort
4924 */
4925export function sort_subarray(arr: FixedArray<double>, mustPrecede: (lhs: double, rhs: double) => boolean): void {
4926    sort_subarray(arr, 0, arr.length, mustPrecede);
4927}
4928
4929export function sort_subarray(arr: FixedArray<double>, startIndex: int, mustPrecede: (lhs: double, rhs: double) => boolean): void {
4930    sort_subarray(arr, startIndex, arr.length, mustPrecede)
4931}
4932
4933// ======== tests section ========
4934
4935/** note: used for tests, {@link test_sortAllOn} */
4936class test_SortDatadouble {
4937    name: string
4938    arr: FixedArray<double>
4939
4940    constructor(name: string, arr: FixedArray<double>) {
4941        this.name = name
4942        this.arr = arr
4943    }
4944}
4945
4946/** note: used for tests, {@link test_sortAllOn} */
4947function test_sortCopy(arr: FixedArray<double>): FixedArray<double> {
4948    let c : FixedArray<double> = new double[arr.length]
4949    for (let i = 0; i < arr.length; i++) {
4950        c[i] = arr[i]
4951    }
4952    return c
4953}
4954
4955/** note: used for tests, {@link test_sortAllOn} */
4956function test_sortAllOnCmpFwd_double(l: double, r: double): boolean {
4957    return l < r
4958}
4959
4960/** note: used for tests, {@link test_sortAllOn} */
4961function test_sortAllOnCmpInv_double(l: double, r: double): boolean {
4962    return r < l
4963}
4964
4965/** note: used for tests, {@link test_sortAllOn} */
4966function test_printArr(arr: FixedArray<double>, startIndex: int, endIndex: int) {
4967    for (let i = startIndex; i < endIndex; i++) {
4968        console.print(arr[i] + ' ')
4969    }
4970    console.println('')
4971}
4972
4973/**
4974 * Function used to test sorting in standard library tests.
4975 * There is only one exported function: `sort`, but for testing
4976 * we need access to all sub sorts that are used. Hence this part of "tests"
4977 * is located here. All related entities are prefixed whith `test_` to stand out
4978 */
4979export function test_sortAllOn(arr: FixedArray<double>) {
4980    // block for comparator ``
4981    if (true) {
4982        const sorts = new Array<test_SortDatadouble>()
4983        const bubbled = test_sortCopy(arr)
4984        bubbleSort(bubbled, 0, bubbled.length)
4985        sorts.push(new test_SortDatadouble("bubble", bubbled))
4986
4987        const insertion = test_sortCopy(arr)
4988        insertionSort(insertion, 0, insertion.length)
4989        sorts.push(new test_SortDatadouble("insertion", insertion))
4990
4991        const heaped = test_sortCopy(arr)
4992        heapSort(heaped, 0, heaped.length)
4993        sorts.push(new test_SortDatadouble("heap", heaped))
4994
4995        const quicked = test_sortCopy(arr)
4996        quickSortImpl3(quicked, 0, quicked.length, quicked.length)
4997        sorts.push(new test_SortDatadouble("quick", quicked))
4998
4999        const quickedInsertion = test_sortCopy(arr)
5000        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length)
5001        sorts.push(new test_SortDatadouble("quick with insertion", quickedInsertion))
5002
5003        const just = test_sortCopy(arr)
5004        sort(just)
5005        sorts.push(new test_SortDatadouble("sort()", just))
5006
5007        let sort0 = sorts.at(0)!
5008        for (let s = 1; s < sorts.length; s++) {
5009            let sortS = sorts.at(s)!
5010            for (let i = 0; i < arr.length; i++) {
5011                if (sort0.arr[i] != sortS.arr[i]) {
5012                    console.println(sort0.name + ': ')
5013                    test_printArr(sort0.arr, 0, arr.length)
5014                    console.println(sortS.name + ': ')
5015                    test_printArr(sortS.arr, 0, arr.length)
5016                    throw new Error("sorts are not equal: " + sort0.name + ' ' + sortS.name + ' for double')
5017                }
5018            }
5019        }
5020    }
5021    // block for comparator `est_sortAllOnCmpFwd_double`
5022    if (true) {
5023        const sorts = new Array<test_SortDatadouble>()
5024        const bubbled = test_sortCopy(arr)
5025        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpFwd_double)
5026        sorts.push(new test_SortDatadouble("bubble", bubbled))
5027
5028        const insertion = test_sortCopy(arr)
5029        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpFwd_double)
5030        sorts.push(new test_SortDatadouble("insertion", insertion))
5031
5032        const heaped = test_sortCopy(arr)
5033        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpFwd_double)
5034        sorts.push(new test_SortDatadouble("heap", heaped))
5035
5036        const quicked = test_sortCopy(arr)
5037        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpFwd_double)
5038        sorts.push(new test_SortDatadouble("quick", quicked))
5039
5040        const quickedInsertion = test_sortCopy(arr)
5041        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpFwd_double)
5042        sorts.push(new test_SortDatadouble("quick with insertion", quickedInsertion))
5043
5044        const just = test_sortCopy(arr)
5045        sort_subarray(just, test_sortAllOnCmpFwd_double)
5046        sorts.push(new test_SortDatadouble("sort()", just))
5047
5048        let sort0 = sorts.at(0)!
5049        for (let s = 1; s < sorts.length; s++) {
5050            let sortS = sorts.at(s)!
5051            for (let i = 0; i < arr.length; i++) {
5052                if (sort0.arr[i] != sortS.arr[i]) {
5053                    console.println(sort0.name + ': ')
5054                    test_printArr(sort0.arr, 0, arr.length)
5055                    console.println(sortS.name + ': ')
5056                    test_printArr(sortS.arr, 0, arr.length)
5057                    throw new Error("sorts est_sortAllOnCmpFwd_double are not equal: " + sort0.name + ' ' + sortS.name + ' for double')
5058                }
5059            }
5060        }
5061    }
5062    // block for comparator `est_sortAllOnCmpInv_double`
5063    if (true) {
5064        const sorts = new Array<test_SortDatadouble>()
5065        const bubbled = test_sortCopy(arr)
5066        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpInv_double)
5067        sorts.push(new test_SortDatadouble("bubble", bubbled))
5068
5069        const insertion = test_sortCopy(arr)
5070        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpInv_double)
5071        sorts.push(new test_SortDatadouble("insertion", insertion))
5072
5073        const heaped = test_sortCopy(arr)
5074        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpInv_double)
5075        sorts.push(new test_SortDatadouble("heap", heaped))
5076
5077        const quicked = test_sortCopy(arr)
5078        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpInv_double)
5079        sorts.push(new test_SortDatadouble("quick", quicked))
5080
5081        const quickedInsertion = test_sortCopy(arr)
5082        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpInv_double)
5083        sorts.push(new test_SortDatadouble("quick with insertion", quickedInsertion))
5084
5085        const just = test_sortCopy(arr)
5086        sort_subarray(just, test_sortAllOnCmpInv_double)
5087        sorts.push(new test_SortDatadouble("sort()", just))
5088
5089        let sort0 = sorts.at(0)!
5090        for (let s = 1; s < sorts.length; s++) {
5091            let sortS = sorts.at(s)!
5092            for (let i = 0; i < arr.length; i++) {
5093                if (sort0.arr[i] != sortS.arr[i]) {
5094                    console.println(sort0.name + ': ')
5095                    test_printArr(sort0.arr, 0, arr.length)
5096                    console.println(sortS.name + ': ')
5097                    test_printArr(sortS.arr, 0, arr.length)
5098                    throw new Error("sorts est_sortAllOnCmpInv_double are not equal: " + sort0.name + ' ' + sortS.name + ' for double')
5099                }
5100            }
5101        }
5102    }
5103}
5104// ======== end of tests section ========
5105
5106function bubbleSort(arr: FixedArray<char>, startIndex: int, endIndex: int): void {
5107    let was = true
5108    while (was) {
5109        was = false
5110        for (let i = startIndex; i < endIndex - 1; i++) {
5111            if ((arr[i + 1] < arr[i])) {
5112                const tmp = arr[i + 1]
5113                arr[i + 1] = arr[i]
5114                arr[i] = tmp
5115                was = true
5116            }
5117        }
5118    }
5119}
5120
5121function insertionSort(arr: FixedArray<char>, startIndex: int, endIndex: int, initIndex: int = startIndex): void {
5122    if (startIndex != initIndex) {
5123        // arr[startIndex - 1] exists and is less than or equal to all elements in range
5124        for (let i = startIndex + 1; i < endIndex; i++) {
5125            const tmp = arr[i]
5126            let pos = i
5127            while ((tmp < arr[pos - 1])) {
5128                arr[pos] = arr[pos - 1]
5129                pos--
5130            }
5131            arr[pos] = tmp
5132        }
5133        return
5134    }
5135    for (let i = startIndex + 1; i < endIndex; i++) {
5136        const tmp = arr[i]
5137        if ((tmp < arr[startIndex])) {
5138            for (let j = i; j > startIndex; j--) {
5139                arr[j] = arr[j - 1]
5140            }
5141            arr[startIndex] = tmp
5142        } else {
5143            let pos = i
5144            while ((tmp < arr[pos - 1])) {
5145                arr[pos] = arr[pos - 1]
5146                pos--
5147            }
5148            arr[pos] = tmp
5149        }
5150    }
5151}
5152function heapSortUp(arr: FixedArray<char>, idxFromStart: int, startIndex: int, heapRoot: int): void {
5153    const tmp = arr[startIndex + idxFromStart]
5154    while (startIndex + idxFromStart > heapRoot) {
5155        const p = (idxFromStart - 1) / 2
5156        if (!(arr[startIndex + p] < tmp)) {
5157            break
5158        }
5159        arr[startIndex + idxFromStart] = arr[startIndex + p]
5160        idxFromStart = p
5161    }
5162    arr[startIndex + idxFromStart] = tmp
5163}
5164
5165// Build max heap with root in startIndex given its children are roots of valid heaps
5166function heapSortDown(arr: FixedArray<char>, idxFromStart: int, startIndex: int, endIndex: int): void {
5167    let heapRoot = startIndex + idxFromStart
5168    let arrIndex = heapRoot
5169    let childIndex = startIndex + idxFromStart * 2 + 1
5170    const tmp = arr[arrIndex]
5171    // Walk heap to bottom and pull max child up on each level
5172    while (childIndex + 1 < endIndex) {
5173        if ((arr[childIndex] < arr[childIndex + 1])) {
5174            childIndex++
5175        }
5176        arr[arrIndex] = arr[childIndex]
5177        arrIndex = childIndex
5178        childIndex = childIndex * 2 - startIndex + 1
5179    }
5180    if (childIndex < endIndex) {
5181        arr[arrIndex] = arr[childIndex]
5182        arrIndex = childIndex
5183    }
5184    arr[arrIndex] = tmp
5185    // Now heap is valid in all positions but arrIndex
5186    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot)
5187}
5188
5189export function heapSort(arr: FixedArray<char>, startIndex: int, endIndex: int): void {
5190    let len = endIndex - startIndex
5191    for (let i = len / 2 - 1; i >= 0; i--) {
5192        heapSortDown(arr, i, startIndex, endIndex)
5193    }
5194
5195    for (let i = endIndex - 1; i > startIndex; i--) {
5196        // move max element to the end of range
5197        const tmp = arr[i]
5198        arr[i] = arr[startIndex]
5199        arr[startIndex] = tmp
5200        heapSortDown(arr, 0, startIndex, i)
5201    }
5202}
5203
5204// Put median of three array elements to arr[index1]
5205function median3(arr: FixedArray<char>, index1: int, index2: int, index3: int): void {
5206    let swap_idx = index2
5207    if ((arr[index1] < arr[index2])) {
5208        if ((arr[index3] < arr[index1])) {
5209            return
5210        }
5211        if ((arr[index3] < arr[index2])) {
5212            swap_idx = index3
5213        }
5214    } else {
5215        if (!(arr[index3] < arr[index1])) {
5216            return
5217        }
5218        if ((arr[index2] < arr[index3])) {
5219            swap_idx = index3
5220        }
5221    }
5222    let tmp = arr[index1]
5223    arr[index1] = arr[swap_idx]
5224    arr[swap_idx] = tmp
5225}
5226
5227// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
5228// Elements equal to pivot go to the right
5229function quickSortSplit(arr: FixedArray<char>, startIndex: int, endIndex: int): int {
5230    const pivot = arr[startIndex]
5231    let i = startIndex + 1
5232    let j = endIndex - 1
5233    // No bounds check because pivot is median of three elements
5234    while ((arr[i] < pivot)) {
5235        i++
5236    }
5237    if (i == startIndex + 1) {
5238        while (i < j && !(arr[j] < pivot)) {
5239            j--
5240        }
5241    } else {
5242        while (!(arr[j] < pivot)) {
5243            j--
5244        }
5245    }
5246    while (i < j) {
5247        // Here !(arr[i] < pivot) and (arr[j] < pivot) holds
5248        let tmp = arr[i]
5249        arr[i] = arr[j]
5250        arr[j] = tmp
5251        while ((arr[++i] < pivot)) {}
5252        while (!(arr[--j] < pivot)) {}
5253    }
5254    let pivotIndex = i - 1
5255    arr[startIndex] = arr[pivotIndex]
5256    arr[pivotIndex] = pivot
5257
5258    return pivotIndex
5259}
5260
5261// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
5262// Elements equal to pivot go to the left
5263function quickSortSplitLeft(arr: FixedArray<char>, startIndex: int, endIndex: int): int {
5264    const pivot = arr[startIndex]
5265    let i = startIndex + 1
5266    let j = endIndex - 1
5267    // No bounds check because pivot is median of three elements
5268    while ((pivot < arr[j])) {
5269        j--
5270    }
5271    if (j + 1 == endIndex) {
5272        while (i < j && !(pivot < arr[i])) {
5273            i++
5274        }
5275    } else {
5276        while (!(pivot < arr[i])) {
5277            i++
5278        }
5279    }
5280    while (i < j) {
5281        // Here (pivot < arr[i]) and !(pivot < arr[j]) holds
5282        let tmp = arr[i]
5283        arr[i] = arr[j]
5284        arr[j] = tmp
5285        while (!(pivot < arr[++i])) {}
5286        while ((pivot < arr[--j])) {}
5287    }
5288    arr[startIndex] = arr[j]
5289    arr[j] = pivot
5290
5291    return j
5292}
5293
5294function quickSortImpl3(arr: FixedArray<char>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
5295    while (endIndex - startIndex > 3) {
5296        if (--maxDepth == 0) {
5297            heapSort(arr, startIndex, endIndex)
5298            return
5299        }
5300        // Here we assume that current interval is not the most left in the sorted range
5301        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
5302            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
5303            // after that only the right part needs to be sorted
5304            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
5305            // with many occurencies of the smallest element
5306            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
5307            continue
5308        }
5309
5310        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
5311        let p = quickSortSplit(arr, startIndex, endIndex)
5312        // make a call for the smaller part of array and continue processing the larger part in the loop
5313        if (p - startIndex < endIndex - p) {
5314            quickSortImpl3(arr, startIndex, p, maxDepth, initIndex)
5315            startIndex = p + 1
5316        } else {
5317            quickSortImpl3(arr, p + 1, endIndex, maxDepth, initIndex)
5318            endIndex = p
5319        }
5320    }
5321    insertionSort(arr, startIndex, endIndex, initIndex)
5322}
5323function quickSortImpl40(arr: FixedArray<char>, startIndex: int, endIndex: int, maxDepth: int, initIndex: int = startIndex): void {
5324    while (endIndex - startIndex > 40) {
5325        if (--maxDepth == 0) {
5326            heapSort(arr, startIndex, endIndex)
5327            return
5328        }
5329        // Here we assume that current interval is not the most left in the sorted range
5330        if (startIndex != initIndex && arr[startIndex - 1] >= arr[startIndex]) {
5331            // We call quickSortSplitLeft here to move all elements equal to pivot (and arr[startIndex - 1]) to the left part;
5332            // after that only the right part needs to be sorted
5333            // If we always used quickSortSplitLeft instead of quickSortSplit, this would not work well for array
5334            // with many occurencies of the smallest element
5335            startIndex = quickSortSplitLeft(arr, startIndex, endIndex) + 1
5336            continue
5337        }
5338
5339        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2)
5340        let p = quickSortSplit(arr, startIndex, endIndex)
5341        // make a call for the smaller part of array and continue processing the larger part in the loop
5342        if (p - startIndex < endIndex - p) {
5343            quickSortImpl40(arr, startIndex, p, maxDepth, initIndex)
5344            startIndex = p + 1
5345        } else {
5346            quickSortImpl40(arr, p + 1, endIndex, maxDepth, initIndex)
5347            endIndex = p
5348        }
5349    }
5350    insertionSort(arr, startIndex, endIndex, initIndex)
5351}
5352
5353function quickSort(arr: FixedArray<char>, startIndex: int, endIndex: int): void {
5354    let size = endIndex - startIndex
5355    if (size <= 1) {
5356        return
5357    }
5358    // find log of length to fall back into determenistic O(n logn) sort
5359    let bits = 32
5360    for (let i = 2; i < 31; i++) {
5361        if ((size >> i) == 0) {
5362            bits = i
5363            break
5364        }
5365    }
5366    quickSortImpl40(arr, startIndex, endIndex, bits * 3)
5367}
5368
5369/**
5370 * sorts arr in-place
5371 *
5372 * @param arr an array to sort
5373 *
5374 * @param startIndex an index to start sorting with, inclusive
5375 *
5376 * @param endIndex an index to end sorting, exclusive
5377 *
5378 * @example: sort array arr
5379 * ```
5380 * sort(arr, 0, arr.length)
5381 * ```
5382 */
5383export function sort(arr: FixedArray<char>, startIndex: int, endIndex: int): void {
5384    if (!checkRange(arr.length, startIndex, endIndex)) {
5385        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
5386    }
5387
5388    quickSort(arr, startIndex, endIndex);
5389}
5390
5391function bubbleSort(arr: FixedArray<char>, startIndex: int, endIndex: int, mustPrecede: (lhs: char, rhs: char) => boolean): void {
5392    let was = true
5393    while (was) {
5394        was = false
5395        for (let i = startIndex; i < endIndex - 1; i++) {
5396            if (mustPrecede(arr[i + 1], arr[i])) {
5397                const tmp = arr[i + 1]
5398                arr[i + 1] = arr[i]
5399                arr[i] = tmp
5400                was = true
5401            }
5402        }
5403    }
5404}
5405
5406function insertionSort(arr: FixedArray<char>, startIndex: int, endIndex: int, mustPrecede: (lhs: char, rhs: char) => boolean, initIndex: int = startIndex): void {
5407    if (startIndex != initIndex) {
5408        // arr[startIndex - 1] exists and is less than or equal to all elements in range
5409        for (let i = startIndex + 1; i < endIndex; i++) {
5410            const tmp = arr[i]
5411            let pos = i
5412            while (mustPrecede(tmp, arr[pos - 1])) {
5413                arr[pos] = arr[pos - 1]
5414                pos--
5415            }
5416            arr[pos] = tmp
5417        }
5418        return
5419    }
5420    for (let i = startIndex + 1; i < endIndex; i++) {
5421        const tmp = arr[i]
5422        if (mustPrecede(tmp, arr[startIndex])) {
5423            for (let j = i; j > startIndex; j--) {
5424                arr[j] = arr[j - 1]
5425            }
5426            arr[startIndex] = tmp
5427        } else {
5428            let pos = i
5429            while (mustPrecede(tmp, arr[pos - 1])) {
5430                arr[pos] = arr[pos - 1]
5431                pos--
5432            }
5433            arr[pos] = tmp
5434        }
5435    }
5436}
5437function heapSortUp(arr: FixedArray<char>, idxFromStart: int, startIndex: int, heapRoot: int, mustPrecede: (lhs: char, rhs: char) => boolean): void {
5438    const tmp = arr[startIndex + idxFromStart]
5439    while (startIndex + idxFromStart > heapRoot) {
5440        const p = (idxFromStart - 1) / 2
5441        if (!mustPrecede(arr[startIndex + p], tmp)) {
5442            break
5443        }
5444        arr[startIndex + idxFromStart] = arr[startIndex + p]
5445        idxFromStart = p
5446    }
5447    arr[startIndex + idxFromStart] = tmp
5448}
5449
5450// Build max heap with root in startIndex given its children are roots of valid heaps
5451function heapSortDown(arr: FixedArray<char>, idxFromStart: int, startIndex: int, endIndex: int, mustPrecede: (lhs: char, rhs: char) => boolean): void {
5452    let heapRoot = startIndex + idxFromStart
5453    let arrIndex = heapRoot
5454    let childIndex = startIndex + idxFromStart * 2 + 1
5455    const tmp = arr[arrIndex]
5456    // Walk heap to bottom and pull max child up on each level
5457    while (childIndex + 1 < endIndex) {
5458        if (mustPrecede(arr[childIndex], arr[childIndex + 1])) {
5459            childIndex++
5460        }
5461        arr[arrIndex] = arr[childIndex]
5462        arrIndex = childIndex
5463        childIndex = childIndex * 2 - startIndex + 1
5464    }
5465    if (childIndex < endIndex) {
5466        arr[arrIndex] = arr[childIndex]
5467        arrIndex = childIndex
5468    }
5469    arr[arrIndex] = tmp
5470    // Now heap is valid in all positions but arrIndex
5471    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot, mustPrecede)
5472}
5473
5474export function heapSort(arr: FixedArray<char>, startIndex: int, endIndex: int, mustPrecede: (lhs: char, rhs: char) => boolean): void {
5475    let len = endIndex - startIndex
5476    for (let i = len / 2 - 1; i >= 0; i--) {
5477        heapSortDown(arr, i, startIndex, endIndex, mustPrecede)
5478    }
5479
5480    for (let i = endIndex - 1; i > startIndex; i--) {
5481        // move max element to the end of range
5482        const tmp = arr[i]
5483        arr[i] = arr[startIndex]
5484        arr[startIndex] = tmp
5485        heapSortDown(arr, 0, startIndex, i, mustPrecede)
5486    }
5487}
5488
5489// Put median of three array elements to arr[index1]
5490function median3(arr: FixedArray<char>, index1: int, index2: int, index3: int, mustPrecede: (lhs: char, rhs: char) => boolean): void {
5491    let swap_idx = index2
5492    if (mustPrecede(arr[index1], arr[index2])) {
5493        if (mustPrecede(arr[index3], arr[index1])) {
5494            return
5495        }
5496        if (mustPrecede(arr[index3], arr[index2])) {
5497            swap_idx = index3
5498        }
5499    } else {
5500        if (!mustPrecede(arr[index3], arr[index1])) {
5501            return
5502        }
5503        if (mustPrecede(arr[index2], arr[index3])) {
5504            swap_idx = index3
5505        }
5506    }
5507    let tmp = arr[index1]
5508    arr[index1] = arr[swap_idx]
5509    arr[swap_idx] = tmp
5510}
5511
5512// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
5513// Elements equal to pivot go to the right
5514function quickSortSplit(arr: FixedArray<char>, startIndex: int, endIndex: int, mustPrecede: (lhs: char, rhs: char) => boolean): int {
5515    const pivot = arr[startIndex]
5516    let i = startIndex + 1
5517    let j = endIndex - 1
5518    // No bounds check because pivot is median of three elements
5519    while (mustPrecede(arr[i], pivot)) {
5520        i++
5521    }
5522    if (i == startIndex + 1) {
5523        while (i < j && !mustPrecede(arr[j], pivot)) {
5524            j--
5525        }
5526    } else {
5527        while (!mustPrecede(arr[j], pivot)) {
5528            j--
5529        }
5530    }
5531    while (i < j) {
5532        // Here !mustPrecede(arr[i], pivot) and mustPrecede(arr[j], pivot) holds
5533        let tmp = arr[i]
5534        arr[i] = arr[j]
5535        arr[j] = tmp
5536        while (mustPrecede(arr[++i], pivot)) {}
5537        while (!mustPrecede(arr[--j], pivot)) {}
5538    }
5539    let pivotIndex = i - 1
5540    arr[startIndex] = arr[pivotIndex]
5541    arr[pivotIndex] = pivot
5542
5543    return pivotIndex
5544}
5545
5546// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
5547// Elements equal to pivot go to the left
5548function quickSortSplitLeft(arr: FixedArray<char>, startIndex: int, endIndex: int, mustPrecede: (lhs: char, rhs: char) => boolean): int {
5549    const pivot = arr[startIndex]
5550    let i = startIndex + 1
5551    let j = endIndex - 1
5552    // No bounds check because pivot is median of three elements
5553    while (mustPrecede(pivot, arr[j])) {
5554        j--
5555    }
5556    if (j + 1 == endIndex) {
5557        while (i < j && !mustPrecede(pivot, arr[i])) {
5558            i++
5559        }
5560    } else {
5561        while (!mustPrecede(pivot, arr[i])) {
5562            i++
5563        }
5564    }
5565    while (i < j) {
5566        // Here mustPrecede(pivot, arr[i]) and !mustPrecede(pivot, arr[j]) holds
5567        let tmp = arr[i]
5568        arr[i] = arr[j]
5569        arr[j] = tmp
5570        while (!mustPrecede(pivot, arr[++i])) {}
5571        while (mustPrecede(pivot, arr[--j])) {}
5572    }
5573    arr[startIndex] = arr[j]
5574    arr[j] = pivot
5575
5576    return j
5577}
5578
5579function quickSortImpl3(arr: FixedArray<char>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: char, rhs: char) => boolean): void {
5580    while (endIndex - startIndex > 3) {
5581        if (--maxDepth == 0) {
5582            heapSort(arr, startIndex, endIndex, mustPrecede)
5583            return
5584        }
5585
5586        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
5587        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
5588        // make a call for the smaller part of array and continue processing the larger part in the loop
5589        if (p - startIndex < endIndex - p) {
5590            quickSortImpl3(arr, startIndex, p, maxDepth, mustPrecede)
5591            startIndex = p + 1
5592        } else {
5593            quickSortImpl3(arr, p + 1, endIndex, maxDepth, mustPrecede)
5594            endIndex = p
5595        }
5596    }
5597    insertionSort(arr, startIndex, endIndex, mustPrecede)
5598}
5599function quickSortImpl40(arr: FixedArray<char>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: char, rhs: char) => boolean): void {
5600    while (endIndex - startIndex > 40) {
5601        if (--maxDepth == 0) {
5602            heapSort(arr, startIndex, endIndex, mustPrecede)
5603            return
5604        }
5605
5606        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
5607        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
5608        // make a call for the smaller part of array and continue processing the larger part in the loop
5609        if (p - startIndex < endIndex - p) {
5610            quickSortImpl40(arr, startIndex, p, maxDepth, mustPrecede)
5611            startIndex = p + 1
5612        } else {
5613            quickSortImpl40(arr, p + 1, endIndex, maxDepth, mustPrecede)
5614            endIndex = p
5615        }
5616    }
5617    insertionSort(arr, startIndex, endIndex, mustPrecede)
5618}
5619
5620function quickSort(arr: FixedArray<char>, startIndex: int, endIndex: int, mustPrecede: (lhs: char, rhs: char) => boolean): void {
5621    let size = endIndex - startIndex
5622    if (size <= 1) {
5623        return
5624    }
5625    // find log of length to fall back into determenistic O(n logn) sort
5626    let bits = 32
5627    for (let i = 2; i < 31; i++) {
5628        if ((size >> i) == 0) {
5629            bits = i
5630            break
5631        }
5632    }
5633    quickSortImpl40(arr, startIndex, endIndex, bits * 3, mustPrecede)
5634}
5635
5636/**
5637 * sorts arr in-place
5638 *
5639 * @param arr an array to sort
5640 *
5641 * @param startIndex an index to start sorting with, inclusive
5642 *
5643 * @param endIndex an index to end sorting, exclusive
5644 *
5645 * @example: sort array arr
5646 * ```
5647 * sort(arr, 0, arr.length)
5648 * ```
5649 */
5650export function sort_subarray(arr: FixedArray<char>, startIndex: int, endIndex: int, mustPrecede: (lhs: char, rhs: char) => boolean): void {
5651    if (!checkRange(arr.length, startIndex, endIndex)) {
5652        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
5653    }
5654
5655    quickSort(arr, startIndex, endIndex, mustPrecede);
5656}
5657
5658/**
5659 * sorts arr in-place
5660 *
5661 * @param arr an array to sort
5662 */
5663export function sort_subarray(arr: FixedArray<char>, mustPrecede: (lhs: char, rhs: char) => boolean): void {
5664    sort_subarray(arr, 0, arr.length, mustPrecede);
5665}
5666
5667export function sort_subarray(arr: FixedArray<char>, startIndex: int, mustPrecede: (lhs: char, rhs: char) => boolean): void {
5668    sort_subarray(arr, startIndex, arr.length, mustPrecede)
5669}
5670
5671// ======== tests section ========
5672
5673/** note: used for tests, {@link test_sortAllOn} */
5674class test_SortDatachar {
5675    name: string
5676    arr: FixedArray<char>
5677
5678    constructor(name: string, arr: FixedArray<char>) {
5679        this.name = name
5680        this.arr = arr
5681    }
5682}
5683
5684/** note: used for tests, {@link test_sortAllOn} */
5685function test_sortCopy(arr: FixedArray<char>): FixedArray<char> {
5686    let c : FixedArray<char> = new char[arr.length]
5687    for (let i = 0; i < arr.length; i++) {
5688        c[i] = arr[i]
5689    }
5690    return c
5691}
5692
5693/** note: used for tests, {@link test_sortAllOn} */
5694function test_sortAllOnCmpFwd_char(l: char, r: char): boolean {
5695    return l < r
5696}
5697
5698/** note: used for tests, {@link test_sortAllOn} */
5699function test_sortAllOnCmpInv_char(l: char, r: char): boolean {
5700    return r < l
5701}
5702
5703/** note: used for tests, {@link test_sortAllOn} */
5704function test_printArr(arr: FixedArray<char>, startIndex: int, endIndex: int) {
5705    for (let i = startIndex; i < endIndex; i++) {
5706        console.print(arr[i] + ' ')
5707    }
5708    console.println('')
5709}
5710
5711/**
5712 * Function used to test sorting in standard library tests.
5713 * There is only one exported function: `sort`, but for testing
5714 * we need access to all sub sorts that are used. Hence this part of "tests"
5715 * is located here. All related entities are prefixed whith `test_` to stand out
5716 */
5717export function test_sortAllOn(arr: FixedArray<char>) {
5718    // block for comparator ``
5719    if (true) {
5720        const sorts = new Array<test_SortDatachar>()
5721        const bubbled = test_sortCopy(arr)
5722        bubbleSort(bubbled, 0, bubbled.length)
5723        sorts.push(new test_SortDatachar("bubble", bubbled))
5724
5725        const insertion = test_sortCopy(arr)
5726        insertionSort(insertion, 0, insertion.length)
5727        sorts.push(new test_SortDatachar("insertion", insertion))
5728
5729        const heaped = test_sortCopy(arr)
5730        heapSort(heaped, 0, heaped.length)
5731        sorts.push(new test_SortDatachar("heap", heaped))
5732
5733        const quicked = test_sortCopy(arr)
5734        quickSortImpl3(quicked, 0, quicked.length, quicked.length)
5735        sorts.push(new test_SortDatachar("quick", quicked))
5736
5737        const quickedInsertion = test_sortCopy(arr)
5738        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length)
5739        sorts.push(new test_SortDatachar("quick with insertion", quickedInsertion))
5740
5741        const just = test_sortCopy(arr)
5742        sort(just)
5743        sorts.push(new test_SortDatachar("sort()", just))
5744
5745        let sort0 = sorts.at(0)!
5746        for (let s = 1; s < sorts.length; s++) {
5747            let sortS = sorts.at(s)!
5748            for (let i = 0; i < arr.length; i++) {
5749                if (sort0.arr[i] != sortS.arr[i]) {
5750                    console.println(sort0.name + ': ')
5751                    test_printArr(sort0.arr, 0, arr.length)
5752                    console.println(sortS.name + ': ')
5753                    test_printArr(sortS.arr, 0, arr.length)
5754                    throw new Error("sorts are not equal: " + sort0.name + ' ' + sortS.name + ' for char')
5755                }
5756            }
5757        }
5758    }
5759    // block for comparator `est_sortAllOnCmpFwd_char`
5760    if (true) {
5761        const sorts = new Array<test_SortDatachar>()
5762        const bubbled = test_sortCopy(arr)
5763        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpFwd_char)
5764        sorts.push(new test_SortDatachar("bubble", bubbled))
5765
5766        const insertion = test_sortCopy(arr)
5767        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpFwd_char)
5768        sorts.push(new test_SortDatachar("insertion", insertion))
5769
5770        const heaped = test_sortCopy(arr)
5771        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpFwd_char)
5772        sorts.push(new test_SortDatachar("heap", heaped))
5773
5774        const quicked = test_sortCopy(arr)
5775        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpFwd_char)
5776        sorts.push(new test_SortDatachar("quick", quicked))
5777
5778        const quickedInsertion = test_sortCopy(arr)
5779        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpFwd_char)
5780        sorts.push(new test_SortDatachar("quick with insertion", quickedInsertion))
5781
5782        const just = test_sortCopy(arr)
5783        sort_subarray(just, test_sortAllOnCmpFwd_char)
5784        sorts.push(new test_SortDatachar("sort()", just))
5785
5786        let sort0 = sorts.at(0)!
5787        for (let s = 1; s < sorts.length; s++) {
5788            let sortS = sorts.at(s)!
5789            for (let i = 0; i < arr.length; i++) {
5790                if (sort0.arr[i] != sortS.arr[i]) {
5791                    console.println(sort0.name + ': ')
5792                    test_printArr(sort0.arr, 0, arr.length)
5793                    console.println(sortS.name + ': ')
5794                    test_printArr(sortS.arr, 0, arr.length)
5795                    throw new Error("sorts est_sortAllOnCmpFwd_char are not equal: " + sort0.name + ' ' + sortS.name + ' for char')
5796                }
5797            }
5798        }
5799    }
5800    // block for comparator `est_sortAllOnCmpInv_char`
5801    if (true) {
5802        const sorts = new Array<test_SortDatachar>()
5803        const bubbled = test_sortCopy(arr)
5804        bubbleSort(bubbled, 0, bubbled.length, test_sortAllOnCmpInv_char)
5805        sorts.push(new test_SortDatachar("bubble", bubbled))
5806
5807        const insertion = test_sortCopy(arr)
5808        insertionSort(insertion, 0, insertion.length, test_sortAllOnCmpInv_char)
5809        sorts.push(new test_SortDatachar("insertion", insertion))
5810
5811        const heaped = test_sortCopy(arr)
5812        heapSort(heaped, 0, heaped.length, test_sortAllOnCmpInv_char)
5813        sorts.push(new test_SortDatachar("heap", heaped))
5814
5815        const quicked = test_sortCopy(arr)
5816        quickSortImpl3(quicked, 0, quicked.length, quicked.length, test_sortAllOnCmpInv_char)
5817        sorts.push(new test_SortDatachar("quick", quicked))
5818
5819        const quickedInsertion = test_sortCopy(arr)
5820        quickSortImpl40(quickedInsertion, 0, quickedInsertion.length, quickedInsertion.length, test_sortAllOnCmpInv_char)
5821        sorts.push(new test_SortDatachar("quick with insertion", quickedInsertion))
5822
5823        const just = test_sortCopy(arr)
5824        sort_subarray(just, test_sortAllOnCmpInv_char)
5825        sorts.push(new test_SortDatachar("sort()", just))
5826
5827        let sort0 = sorts.at(0)!
5828        for (let s = 1; s < sorts.length; s++) {
5829            let sortS = sorts.at(s)!
5830            for (let i = 0; i < arr.length; i++) {
5831                if (sort0.arr[i] != sortS.arr[i]) {
5832                    console.println(sort0.name + ': ')
5833                    test_printArr(sort0.arr, 0, arr.length)
5834                    console.println(sortS.name + ': ')
5835                    test_printArr(sortS.arr, 0, arr.length)
5836                    throw new Error("sorts est_sortAllOnCmpInv_char are not equal: " + sort0.name + ' ' + sortS.name + ' for char')
5837                }
5838            }
5839        }
5840    }
5841}
5842// ======== end of tests section ========
5843
5844function bubbleSort(arr: FixedArray<NullishType>, startIndex: int, endIndex: int, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean): void {
5845    let was = true
5846    while (was) {
5847        was = false
5848        for (let i = startIndex; i < endIndex - 1; i++) {
5849            if (mustPrecede(arr[i + 1], arr[i])) {
5850                const tmp = arr[i + 1]
5851                arr[i + 1] = arr[i]
5852                arr[i] = tmp
5853                was = true
5854            }
5855        }
5856    }
5857}
5858
5859function insertionSort(arr: FixedArray<NullishType>, startIndex: int, endIndex: int, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean, initIndex: int = startIndex): void {
5860    if (startIndex != initIndex) {
5861        // arr[startIndex - 1] exists and is less than or equal to all elements in range
5862        for (let i = startIndex + 1; i < endIndex; i++) {
5863            const tmp = arr[i]
5864            let pos = i
5865            while (mustPrecede(tmp, arr[pos - 1])) {
5866                arr[pos] = arr[pos - 1]
5867                pos--
5868            }
5869            arr[pos] = tmp
5870        }
5871        return
5872    }
5873    for (let i = startIndex + 1; i < endIndex; i++) {
5874        const tmp = arr[i]
5875        if (mustPrecede(tmp, arr[startIndex])) {
5876            for (let j = i; j > startIndex; j--) {
5877                arr[j] = arr[j - 1]
5878            }
5879            arr[startIndex] = tmp
5880        } else {
5881            let pos = i
5882            while (mustPrecede(tmp, arr[pos - 1])) {
5883                arr[pos] = arr[pos - 1]
5884                pos--
5885            }
5886            arr[pos] = tmp
5887        }
5888    }
5889}
5890function heapSortUp(arr: FixedArray<NullishType>, idxFromStart: int, startIndex: int, heapRoot: int, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean): void {
5891    const tmp = arr[startIndex + idxFromStart]
5892    while (startIndex + idxFromStart > heapRoot) {
5893        const p = (idxFromStart - 1) / 2
5894        if (!mustPrecede(arr[startIndex + p], tmp)) {
5895            break
5896        }
5897        arr[startIndex + idxFromStart] = arr[startIndex + p]
5898        idxFromStart = p
5899    }
5900    arr[startIndex + idxFromStart] = tmp
5901}
5902
5903// Build max heap with root in startIndex given its children are roots of valid heaps
5904function heapSortDown(arr: FixedArray<NullishType>, idxFromStart: int, startIndex: int, endIndex: int, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean): void {
5905    let heapRoot = startIndex + idxFromStart
5906    let arrIndex = heapRoot
5907    let childIndex = startIndex + idxFromStart * 2 + 1
5908    const tmp = arr[arrIndex]
5909    // Walk heap to bottom and pull max child up on each level
5910    while (childIndex + 1 < endIndex) {
5911        if (mustPrecede(arr[childIndex], arr[childIndex + 1])) {
5912            childIndex++
5913        }
5914        arr[arrIndex] = arr[childIndex]
5915        arrIndex = childIndex
5916        childIndex = childIndex * 2 - startIndex + 1
5917    }
5918    if (childIndex < endIndex) {
5919        arr[arrIndex] = arr[childIndex]
5920        arrIndex = childIndex
5921    }
5922    arr[arrIndex] = tmp
5923    // Now heap is valid in all positions but arrIndex
5924    heapSortUp(arr, arrIndex - startIndex, startIndex, heapRoot, mustPrecede)
5925}
5926
5927export function heapSort(arr: FixedArray<NullishType>, startIndex: int, endIndex: int, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean): void {
5928    let len = endIndex - startIndex
5929    for (let i = len / 2 - 1; i >= 0; i--) {
5930        heapSortDown(arr, i, startIndex, endIndex, mustPrecede)
5931    }
5932
5933    for (let i = endIndex - 1; i > startIndex; i--) {
5934        // move max element to the end of range
5935        const tmp = arr[i]
5936        arr[i] = arr[startIndex]
5937        arr[startIndex] = tmp
5938        heapSortDown(arr, 0, startIndex, i, mustPrecede)
5939    }
5940}
5941
5942// Put median of three array elements to arr[index1]
5943function median3(arr: FixedArray<NullishType>, index1: int, index2: int, index3: int, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean): void {
5944    let swap_idx = index2
5945    if (mustPrecede(arr[index1], arr[index2])) {
5946        if (mustPrecede(arr[index3], arr[index1])) {
5947            return
5948        }
5949        if (mustPrecede(arr[index3], arr[index2])) {
5950            swap_idx = index3
5951        }
5952    } else {
5953        if (!mustPrecede(arr[index3], arr[index1])) {
5954            return
5955        }
5956        if (mustPrecede(arr[index2], arr[index3])) {
5957            swap_idx = index3
5958        }
5959    }
5960    let tmp = arr[index1]
5961    arr[index1] = arr[swap_idx]
5962    arr[swap_idx] = tmp
5963}
5964
5965// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
5966// Elements equal to pivot go to the right
5967function quickSortSplit(arr: FixedArray<NullishType>, startIndex: int, endIndex: int, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean): int {
5968    const pivot = arr[startIndex]
5969    let i = startIndex + 1
5970    let j = endIndex - 1
5971    // No bounds check because pivot is median of three elements
5972    while (mustPrecede(arr[i], pivot)) {
5973        i++
5974    }
5975    if (i == startIndex + 1) {
5976        while (i < j && !mustPrecede(arr[j], pivot)) {
5977            j--
5978        }
5979    } else {
5980        while (!mustPrecede(arr[j], pivot)) {
5981            j--
5982        }
5983    }
5984    while (i < j) {
5985        // Here !mustPrecede(arr[i], pivot) and mustPrecede(arr[j], pivot) holds
5986        let tmp = arr[i]
5987        arr[i] = arr[j]
5988        arr[j] = tmp
5989        while (mustPrecede(arr[++i], pivot)) {}
5990        while (!mustPrecede(arr[--j], pivot)) {}
5991    }
5992    let pivotIndex = i - 1
5993    arr[startIndex] = arr[pivotIndex]
5994    arr[pivotIndex] = pivot
5995
5996    return pivotIndex
5997}
5998
5999// Split range [startIndex, endIndex) by pivot arr[startIndex] and return pivot position
6000// Elements equal to pivot go to the left
6001function quickSortSplitLeft(arr: FixedArray<NullishType>, startIndex: int, endIndex: int, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean): int {
6002    const pivot = arr[startIndex]
6003    let i = startIndex + 1
6004    let j = endIndex - 1
6005    // No bounds check because pivot is median of three elements
6006    while (mustPrecede(pivot, arr[j])) {
6007        j--
6008    }
6009    if (j + 1 == endIndex) {
6010        while (i < j && !mustPrecede(pivot, arr[i])) {
6011            i++
6012        }
6013    } else {
6014        while (!mustPrecede(pivot, arr[i])) {
6015            i++
6016        }
6017    }
6018    while (i < j) {
6019        // Here mustPrecede(pivot, arr[i]) and !mustPrecede(pivot, arr[j]) holds
6020        let tmp = arr[i]
6021        arr[i] = arr[j]
6022        arr[j] = tmp
6023        while (!mustPrecede(pivot, arr[++i])) {}
6024        while (mustPrecede(pivot, arr[--j])) {}
6025    }
6026    arr[startIndex] = arr[j]
6027    arr[j] = pivot
6028
6029    return j
6030}
6031
6032function quickSortImpl3(arr: FixedArray<NullishType>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean): void {
6033    while (endIndex - startIndex > 3) {
6034        if (--maxDepth == 0) {
6035            heapSort(arr, startIndex, endIndex, mustPrecede)
6036            return
6037        }
6038
6039        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
6040        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
6041        // make a call for the smaller part of array and continue processing the larger part in the loop
6042        if (p - startIndex < endIndex - p) {
6043            quickSortImpl3(arr, startIndex, p, maxDepth, mustPrecede)
6044            startIndex = p + 1
6045        } else {
6046            quickSortImpl3(arr, p + 1, endIndex, maxDepth, mustPrecede)
6047            endIndex = p
6048        }
6049    }
6050    insertionSort(arr, startIndex, endIndex, mustPrecede)
6051}
6052function quickSortImpl40(arr: FixedArray<NullishType>, startIndex: int, endIndex: int, maxDepth: int, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean): void {
6053    while (endIndex - startIndex > 40) {
6054        if (--maxDepth == 0) {
6055            heapSort(arr, startIndex, endIndex, mustPrecede)
6056            return
6057        }
6058
6059        median3(arr, startIndex, endIndex - 1, (startIndex + endIndex) / 2, mustPrecede)
6060        let p = quickSortSplit(arr, startIndex, endIndex, mustPrecede)
6061        // make a call for the smaller part of array and continue processing the larger part in the loop
6062        if (p - startIndex < endIndex - p) {
6063            quickSortImpl40(arr, startIndex, p, maxDepth, mustPrecede)
6064            startIndex = p + 1
6065        } else {
6066            quickSortImpl40(arr, p + 1, endIndex, maxDepth, mustPrecede)
6067            endIndex = p
6068        }
6069    }
6070    insertionSort(arr, startIndex, endIndex, mustPrecede)
6071}
6072
6073function quickSort(arr: FixedArray<NullishType>, startIndex: int, endIndex: int, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean): void {
6074    let size = endIndex - startIndex
6075    if (size <= 1) {
6076        return
6077    }
6078    // find log of length to fall back into determenistic O(n logn) sort
6079    let bits = 32
6080    for (let i = 2; i < 31; i++) {
6081        if ((size >> i) == 0) {
6082            bits = i
6083            break
6084        }
6085    }
6086    quickSortImpl40(arr, startIndex, endIndex, bits * 3, mustPrecede)
6087}
6088
6089/**
6090 * sorts arr in-place
6091 *
6092 * @param arr an array to sort
6093 *
6094 * @param startIndex an index to start sorting with, inclusive
6095 *
6096 * @param endIndex an index to end sorting, exclusive
6097 *
6098 * @example: sort array arr
6099 * ```
6100 * sort(arr, 0, arr.length)
6101 * ```
6102 */
6103export function sort_subarray(arr: FixedArray<NullishType>, startIndex: int, endIndex: int, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean): void {
6104    if (!checkRange(arr.length, startIndex, endIndex)) {
6105        throw new ArrayIndexOutOfBoundsError("sort: bounds verification failed")
6106    }
6107
6108    quickSort(arr, startIndex, endIndex, mustPrecede);
6109}
6110
6111/**
6112 * sorts arr in-place
6113 *
6114 * @param arr an array to sort
6115 */
6116export function sort_subarray(arr: FixedArray<NullishType>, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean): void {
6117    sort_subarray(arr, 0, arr.length, mustPrecede);
6118}
6119
6120export function sort_subarray(arr: FixedArray<NullishType>, startIndex: int, mustPrecede: (lhs: NullishType, rhs: NullishType) => boolean): void {
6121    sort_subarray(arr, startIndex, arr.length, mustPrecede)
6122}
6123
6124