Kwker

Sorting

Sort an array in place or into a copy, in either order, row by row, or on several cores.

Sort in place

sort puts the elements of an array in ascending order. It changes the array itself and returns nothing (JavaScript returns the same array). No extra copy of your data is made.

import numpy as np
import kwker

temperatures = np.array([21.5, 18.0, 25.25, 19.75, 23.0])
kwker.sort(temperatures)
print(temperatures)
[18.   19.75 21.5  23.   25.25]

Get a sorted copy

To keep the original, sort a copy. In Python, kwker.sorted(a) returns a new sorted array and leaves a unchanged; it always returns a flat (1-D) array.

import numpy as np
import kwker

ids = np.array([42, 7, 1000, 7, 3], dtype=np.uint32)
print(kwker.sorted(ids))
print(ids)
[   3    7    7   42 1000]
[  42    7 1000    7    3]

Largest first

Every call takes a descending order to reverse the result: descending=True in Python, sort_descending or Order::DESCENDING in Rust, KWKER_DESCENDING in C, Order::descending in C++ and { descending: true } in JavaScript.

import numpy as np
import kwker

scores = np.array([72, 95, 88, 61, 95])
kwker.sort(scores, descending=True)
print(scores)
[95 95 88 72 61]

Sort each row

Sorting each row of a matrix on its own is one call: axis=1 in Python (axis=0 sorts every column; without axis, all elements are sorted as one flat list), and sort_rows in Rust, C and C++ for a row-major matrix stored as one array. In JavaScript, sort each row's view.

import numpy as np
import kwker

m = np.array([[3, 1, 2],
              [9, 7, 8]])
kwker.sort(m, axis=1)
print(m)
[[1 2 3]
 [7 8 9]]

Big arrays: use more cores

For millions of elements, sort on several cores: threads= in Python, sort_mt in Rust, C and C++, SortMT in Go and C#, sortMt in Java. 0 threads means the default for your machine. The result is the same as with one thread. (The WebAssembly build runs on one thread.)

import numpy as np
import kwker

a = np.random.default_rng(1).random(5_000_000)
b = a.copy()
kwker.sort(a)
kwker.sort(b, threads=4)
print(np.array_equal(a, b))
True

Every number type

Kwker sorts signed and unsigned integers of 8, 16, 32 and 64 bits, and 32- and 64-bit floats, each in its own format. Half-precision and 8-bit float formats (float16, bfloat16, FP8) and 128-bit integers are available in the native packages.

import numpy as np
import kwker

print(kwker.sorted(np.array([5, -128, 127, 0], dtype=np.int8)))
print(kwker.sorted(np.array([65535, 1, 300], dtype=np.uint16)))
print(kwker.sorted(np.array([2**63 - 1, -(2**63), 0], dtype=np.int64)))
print(kwker.sorted(np.array([2.5, -1.25, 0.0], dtype=np.float32)))
[-128    0    5  127]
[    1   300 65535]
[-9223372036854775808                    0  9223372036854775807]
[-1.25  0.    2.5 ]

Is the sort stable?

A sort is stable when equal elements keep their original order. For plain numbers this makes no difference: equal numbers are identical, so you cannot tell them apart.

Tip

When the order of equal keys matters, for example when you reorder other data by a key, use argsort. It is always stable.

Missing values (NaN)

Kwker puts every NaN at the end, in both directions. Ask for NaNs first to put them at the start instead: nans_first=True in Python, NanPlacement::First in Rust, KWKER_NANS_FIRST in C, Order::nans_first in C++ and { nansFirst: true } in JavaScript.

import numpy as np
import kwker

readings = np.array([3.2, np.nan, 1.5, np.nan, 2.8])
print(kwker.sorted(readings))
print(kwker.sorted(readings, descending=True))
print(kwker.sorted(readings, nans_first=True))
[1.5 2.8 3.2 nan nan]
[3.2 2.8 1.5 nan nan]
[nan nan 1.5 2.8 3.2]

Two more rules make float results the same everywhere: