These operate on a read-only sz_strings_view_t in any layout and never reorder the underlying data — they fill an order permutation array instead.
Sorting is stable, so equal elements keep their input order, supports descending order via a reverse flag, and supports partial / top-K sorting via top_count — pass 0 to sort everything.
Every allocation these kernels need is routed through the sz_allocator_t you supply — pass NULL for the default malloc-based one — so, unlike std::sort or thrust::sort, no scratch memory is ever allocated behind your back.
The C API fills a caller-owned order array and reports success through a sz_status_t:
sz_sort_order_best orders byte-lexicographically; sz_sort_uncased_order_best orders under Unicode case-folding, folding small chunks on the fly, and malformed UTF-8 sorts by raw byte value so the order stays total and deterministic.
The allocator argument may be NULL to use the default allocator.
sz_intersect_best computes the intersection of two deduplicated binary string collections using a hash table, writing matched positions from each side and leaving unmatched slots as STRINGZILLA_SIZE_MAX:
The seed randomizes the hash table to resist adversarial inputs; the position arrays must each fit at least min(first->count, second->count) entries.
The companion sz_join_semantics_t enum documents the SQL-style JOIN modes the family is designed around, namely sz_join_inner_strict_k, sz_join_inner_k, sz_join_left_outer_k, sz_join_right_outer_k, sz_join_full_outer_k, and sz_join_cross_k.
char const *first[] = {"banana", "apple", "cherry"};char const *second[] = {"cherry", "orange", "pineapple", "banana"};sz_strings_view_t a, b;sz_strings_from_null_terminated_strings(first, 3, &a);sz_strings_from_null_terminated_strings(second, 4, &b);sz_size_t intersection_count;sz_size_t a_positions[3], b_positions[3]; // sized to the smaller collectionsz_status_t status = sz_intersect_best(&a, &b, NULL, /*seed*/ 0, &intersection_count, a_positions, b_positions, capabilities, NULL);assert(intersection_count == 2); // "banana" and "cherry"
Collections are read through sz_strings_view_t in one of three layouts: an Apache Arrow String tape with 32-bit offsets, a LargeString tape with 64-bit offsets, or an accessor that writes the views of up to 64 strings per call.
Accessors describe arrays of views, NUL-terminated pointers, struct members, record pointers and NUL-padded rows without copying a byte, while tapes are what Arrow and the GPU kernels read directly.
Every conversion takes a view, an optional array of indices that reorders, samples or repeats it, and a count, and writes a writable sz_strings_span_t tape:
Every conversion is atomic: it succeeds with the whole result in the tape, or fails and leaves the tape, its blocks and their bytes exactly as they were, whether an allocation was refused or a string was malformed.
A zeroed span receives fresh blocks from the allocator, blocks that already have the result’s sizes are written in place, and any other span’s old blocks go back only once the result is written.
The allocator’s reallocate is an optional fast path; without it, a block that must change size moves through allocate, memcpy and free.
Escaping covers JSON, JSON in ASCII the way Python’s json.dumps writes it, RFC 4180 CSV, hexadecimal, HTML, URL percent-encoding, and terminal-safe logs, and unescaping reports the first malformed string.
Normalization brings every string into NFC, NFD, NFKC, NFKD or NFKC_Casefold, and NUL-padded rows serve NumPy S arrays and fixed-width record fields.
A single buffer is a tape of one string, so lowercasing in place looks like this:
sz_string_t is a tiny memory-owning string with Small-String-Optimization: short strings, up to 22 bytes plus the terminator on 64-bit, live inline with no heap allocation, while longer strings spill to the heap.
Its length can be read branchlessly, and many length changes avoid branches entirely.
Every mutating call takes an explicit sz_allocator_t *.
sz_string_expand opens an uninitialized gap of added_length at offset that you then populate, often via memcpy; sz_string_erase removes a range without ever allocating and cannot fail.
RFC 8259 string bodies: \", \\, short forms like \n, and \u00XX for other bytes below 0x20.
sz_escape_hex_k
1
Two lowercase hexadecimal digits per byte, decoding either case.
sz_escape_html_k
2
HTML and XML text and attribute values: &, <, >, " and '.
sz_escape_url_k
3
RFC 3986 percent-encoding of every byte outside A-Z a-z 0-9 - _ ~ and the dot.
sz_escape_console_k
4
Terminal-safe logs: short forms like \n, \\ and \", \xHH for other C0 control bytes, DEL and ill-formed UTF-8, and \uXXXX for C1 controls and the bidirectional overrides U+202A to U+202E and U+2066 to U+2069, which reorder what a reader sees.
sz_escape_csv_k
5
RFC 4180 fields, quoted only when holding a comma, a quote, CR or LF, with every inner quote doubled.
sz_escape_json_ascii_k
6
RFC 8259 string bodies in ASCII alone, as Python's json.dumps writes them by default: every codepoint outside printable ASCII without a short form, DEL included, becomes \uXXXX, a surrogate pair beyond the Basic Multilingual Plane, and every ill-formed byte becomes the escape of U+FFFD.
sz_kernel_strings_escape_tSignature of the sz_strings_escape_best kernels, kept beside the enum it takes.
Where the members sit, which must outlive strings.
count
in
Number of records.
strings
out
The accessor-backed collection to initialize.
sz_strings_from_record_pointersDescribes the string members of records reached through an array of pointers, like the nodes a query over a parsed tree returns.
Reads any layout, so it turns accessor-backed strings, slices and permutations into a tape that GPU kernels and Apache Arrow read directly. The payload and the offsets are two blocks, exactly as large as they must be, with 32-bit offsets while the payload fits in 4 GiB and 64-bit beyond.
Every collection verb is atomic: it succeeds and leaves tape holding the whole result, or it fails and leaves tape, its blocks and their bytes as they were. Blocks of exactly the sizes the result needs are written in place, so a borrowed span of the right sizes needs no allocator; any other tape receives fresh blocks from allocator, and its old ones go back only once the result is written. A whole tape gathered onto itself is left as it is.
Parameters
strings
in
The strings to gather, in any layout.
order
in
Indices into strings, possibly repeated, or null for the first count.
count
in
Number of strings to write.
allocator
in
Where the blocks of tape come from and return to.
tape
inout
A zeroed span, one an earlier call produced, or the one strings views.
capabilities
in
One device's capabilities, like sz_capabilities_enabled_cpu reports.
stream
in
Null on the CPU, or the GPU stream of that device to queue on.
Notes
A GPU call reads the payload size back before allocating, synchronizing stream once.
sz_strings_to_rows_bestWrites strings[order[i]], or the first count strings, into NUL-padded rows.
Allocates and reuses tape like sz_strings_to_tape_best. When strings views all of tape, its payload block grows first, through reallocate when the allocator has one, then its bytes move to the end and escape front to back, the writer never overtaking unread bytes.
Parameters
strings
in
The strings to escape, in any layout.
order
in
Indices into strings, possibly repeated, or null for the first count.
count
in
Number of strings to write.
kind
in
The format to escape for.
allocator
in
Where the blocks of tape come from and return to.
tape
inout
A zeroed span, one an earlier call produced, or the one strings views.
capabilities
in
One device's capabilities, like sz_capabilities_enabled_cpu reports.
stream
in
Null on the CPU, or the GPU stream of that device to queue on.
sz_strings_unescape_bestUnescapes strings[order[i]], or the first count strings, from kind into a tape.
Allocates and reuses tape like sz_strings_to_tape_best. Every string is checked before anything is written, so one malformed string leaves tape as it was.
Parameters
strings
in
The strings to unescape, in any layout.
order
in
Indices into strings, possibly repeated, or null for the first count.
count
in
Number of strings to write.
kind
in
The format to unescape from.
allocator
in
Where the blocks of tape come from and return to.
tape
inout
A zeroed span, one an earlier call produced, or the one strings views.
malformed_index
out
The first string holding a malformed sequence, on failure.
capabilities
in
One device's capabilities, like sz_capabilities_enabled_cpu reports.
stream
in
Null on the CPU, or the GPU stream of that device to queue on.
sz_fill_random_bestA Pseudorandom Number Generator (PRNG), inspired by the AES-CTR-128 algorithm, but using only one round of AES mixing as opposed to "NIST SP 800-90A".
CTR_DRBG (CounTeR mode Deterministic Random Bit Generator) appears secure and indistinguishable from a true random source when AES is used as the underlying block cipher and 112 bits are taken from this PRNG. When AES is used as the underlying block cipher and 128 bits are taken from each instantiation, the required security level is delivered with the caveat that a 128-bit cipher's output in counter mode can be distinguished from a true RNG.
In this case, it doesn't apply, as we only use one round of AES mixing. We also don't expose a separate "key", only a "nonce", to keep the API simple, but we mix it with 512 bits of Pi constants to increase randomness.
Immutable collection of strings to sort, in any layout.
top_count
in
Number of leading elements to fully order, or 0 to sort all of them.
reverse
in
Whether to sort in descending order.
allocator
in
Optional memory allocator for temporary storage.
order
out
Output permutation that sorts the elements.
capabilities
in
One device's capabilities, like sz_capabilities_enabled_cpu reports.
stream
in
Null on the CPU, or the GPU stream of that device to queue on.
Preconditions
The order array must fit at least as many integers as strings holds.
Postconditions
The order array will contain a valid permutation of all indices of strings.
If top_count is non-zero and smaller than the count, only the first top_count entries are sorted; the rest are an arbitrary permutation of the leftover indices.
Notes
Takes linear memory, quadratic worst-case and log-linear average time.
This algorithm is stable: equal elements keep their relative order, ascending by index.
Orders strings under Unicode case-folding, equivalent to folding every string and sorting the folded bytes lexicographically - but folding only small chunks on the fly, never materializing a fully pre-folded copy of the collection.
Malformed UTF-8 is handled losslessly: any byte that does not begin a well-formed codepoint is compared and sorted by its raw byte value as a single one-byte unit, and decoding resyncs at the next byte. The result stays a total, deterministic order; valid input sorts byte-identically.
Parameters
strings
in
Immutable collection of UTF-8 strings to sort, in any layout.
top_count
in
Number of leading elements to fully order, or 0 to sort all of them.
reverse
in
Whether to sort in descending order.
allocator
in
Optional memory allocator for temporary storage.
order
out
Output permutation that sorts the elements.
capabilities
in
One device's capabilities, like sz_capabilities_enabled_cpu reports.
stream
in
Null on the CPU, or the GPU stream of that device to queue on.
Preconditions
The order array must fit at least as many integers as strings holds.
Postconditions
The order array will contain a valid permutation of all indices of strings.
Notes
This algorithm is stable: case-folded-equal elements keep their input order.
Outputs the first_positions from the first_strings and second_positions from the second_strings, that contain matched strings. Missing matches are represented as STRINGZILLA_SIZE_MAX. Tolerates duplicate strings within either collection: each distinct shared value is emitted exactly once, a distinct-set intersection, so intersection_count never exceeds the smaller of the two counts and can't overflow the output arrays.
For example, intersecting two small collections of fruit names: