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