# Sorting and Sets

> StringZilla for C++

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.

Matching the all-`noexcept` surface, the C++ sort takes any container plus a string-extractor callable and writes the permutation into a caller-owned span, returning a `status_t`:

```cpp
status_t sz::argsort(container, extractor, span<sz_size_t> order, top_count = 0, reverse = false,
                     capabilities = sz::default_capabilities(), stream = nullptr, allocator = {});
status_t sz::argsort_utf8_uncased(container, extractor, span<sz_size_t> order, top_count = 0, reverse = false,
                                  capabilities = sz::default_capabilities(), stream = nullptr, allocator = {});
expected<size_t> sz::intersect(first, extractor, second, extractor, seed,
                               span<sz_size_t> first_positions, span<sz_size_t> second_positions,
                               capabilities = sz::default_capabilities(), stream = nullptr, allocator = {});
```

The outputs are sized `std::span<sz_size_t>` views over caller-owned storage, and the scratch memory comes from `allocator`.
Size `order` to `container.size()` — the whole span receives a permutation, and a non-zero `top_count` only requests that the first `top_count` entries be fully sorted.
Size each position span to `min(first.size(), second.size())`; a shorter span returns `unexpected_dimensions_k`.
`intersect` returns the number of matched pairs as an `expected<size_t>`, so there is no separate count out-parameter.

```cpp
#include <stringzilla/stringzilla.hpp>
#include <vector>
#include <cassert>
namespace sz = ashvardanian::stringzilla;

int main() {
    std::vector<sz::string_t> names = {"banana", "apple", "cherry"};

    sz_size_t order[3];
    sz::status_t status = sz::argsort(
        names, [](sz::string_t const &s) -> sz::string_view_t { return s; }, {order, 3});
    assert(status == sz::status_t::success_k);
    assert(order[0] == 1 && order[1] == 0 && order[2] == 2); // apple, banana, cherry

    auto [sorted, sort_status] = sz::argsort(names);
    assert(sz::succeeded(sort_status) && sorted[0] == 1);
    return 0;
}
```

Without output spans, `sz::argsort`, `sz::argsort_utf8_uncased` and `sz::intersect` allocate their results from the allocator passed last and return them in an `expected`, never throwing.

### String Collections and Containers

In C++, `sz::string_t` is the owning, SSO container and `sz::string_span_t` is its mutable view; both rebuild the `std::string` surface, and nothing in them throws.
Mutating members (`append`, `push_back`, `insert`, `erase`, `replace`, `resize`, `reserve`, `shrink_to_fit`, `clear`, `swap`, `assign`) are present, alongside `data`/`c_str`/`size`/`capacity` and the relational operators.
Every allocating verb returns a `sz::status_t`, or an `sz::expected` beside an iterator, and `insert_at`, `erase_slice` and `replace_slice` take signed offsets counted from the end, like Python's slices.
Copies and conversions come from `copy` and `make`, which return an `sz::expected`, and positions past the end clamp to it.

```cpp
#include <stringzilla/stringzilla.hpp>
namespace sz = ashvardanian::stringzilla;

int main() {
    sz::expected<sz::string_t> made = sz::string_t::make("hello");
    if (!made) return 1;
    sz::string_t greeting = std::move(made.value);
    if (sz::failed(greeting.append(", world"))) return 1;
    assert(greeting == "hello, world");

    if (sz::failed(greeting.push_back('!'))) return 1; // allocating verbs return a status
    if (sz::failed(greeting.reserve(1024))) return 1;
    if (sz::failed(greeting.replace_all("l", "L"))) return 1;

    assert(greeting.starts_with("heLLo"));
    return 0;
}
```

Collections pair the same way: `sz::strings_view_t` borrows an Arrow `String` or `LargeString` array from `(data, offsets, count)`, a span of `sz::string_view_t`, or an owned column, and `sz::strings_t` owns one.
Its conversions, `gather`, `escape`, `unescape`, `lookup` and `normalize`, return a fresh `sz::strings` inside an `sz::expected` with the status, and leave nothing allocated on failure.
`find`, `hashes` and `to_rows` write into spans the caller owns.
Every one takes a capability mask, `sz::default_capabilities()` unless given, and a stream, like the C dispatch points.

```cpp
#include <stringzilla/stringzilla.hpp>
namespace sz = ashvardanian::stringzilla;

int main() {
    char const payload[] = "alphabetagamma";
    sz_u32_t const offsets[] = {0, 5, 9, 14};
    sz::strings_view_t const column(payload, offsets, 3); // an Arrow `String` array, no copies

    auto const [escaped, escape_status] = column.escape(sz_escape_json_k);
    if (sz::failed(escape_status)) return 1;

    std::size_t malformed_index = 0;
    auto const [unescaped, unescape_status] = escaped.view().unescape(sz_escape_json_k, malformed_index);
    assert(sz::succeeded(unescape_status) && unescaped[1] == "beta");

    std::size_t positions[3];
    if (sz::failed(column.find("a", positions))) return 1; // the first match in every string
    return 0;
}
```

`#include <stringzilla/stringzilla.hpp>`

## fill_random

Overwrites the [`string`](https://www.unum.cloud/docs/stringzilla/cpp/types.md#string) slice with random bytes.

```cpp
status_t fill_random(
    string_span_t string,
    std::uint64_t nonce,
    sz_capability_t capabilities = default_capabilities(),
    sz_stream_t stream = nullptr) noexcept;
```

```cpp
status_t fill_random(string_span_t string) noexcept;
```

Overwrites the [`string`](https://www.unum.cloud/docs/stringzilla/cpp/types.md#string) slice with random bytes using `std::rand` for the nonce.

- `string` (out): The string to overwrite.
- `nonce` (in): "Number used once" seeding the random number generator, __never__ repeat it!
- `capabilities` (in): Capabilities to pick from.
- `stream` (in): Null on the CPU, or the stream of the device `capabilities` describes.

See also: [sz_fill_random_best](https://www.unum.cloud/docs/stringzilla/c/sorting-and-sets.md#sz_fill_random_best)

## argsort

Computes the permutation of an array, that would lead to sorted order.

```cpp
template <typename container_type_, typename string_extractor_, typename allocator_type_ = std::allocator<char>>
status_t argsort(
    container_type_ const & container,
    string_extractor_ const & extractor,
    std::span<sz_size_t> order,
    std::size_t top_count = 0,
    bool reverse = false,
    sz_capability_t capabilities = default_capabilities(),
    sz_stream_t stream = nullptr,
    allocator_type_ allocator = {}) noexcept;
```

The elements of the array must be convertible to a `string_view_t` with the given extractor. Unlike the [`sz_sort_order_best`](https://www.unum.cloud/docs/stringzilla/c/sorting-and-sets.md#sz_sort_order_best) C interface, overwrites the output span.

- `container` (in): The array of string-like elements to sort.
- `extractor` (in): The function object that extracts the string from the object.
- `order` (out): The caller-owned output span of indices, populated with the permutation. Must hold at least `container.size()` entries; the whole span receives a permutation.
- `top_count` (in): If non-zero, only the first `top_count` entries are guaranteed fully sorted (partial sort).
- `reverse` (in): If true, sorts in descending order.
- `capabilities` (in): Capabilities to pick from.
- `stream` (in): Null on the CPU, or the stream of the device `capabilities` describes.
- `allocator` (in): Serves the scratch memory of the sort.

See also: [sz_sort_order_best](https://www.unum.cloud/docs/stringzilla/c/sorting-and-sets.md#sz_sort_order_best)

Statuses: - [`unexpected_dimensions_k`](https://www.unum.cloud/docs/stringzilla/cpp/types.md#unexpected_dimensions_k) `order` is too short.

## argsort_utf8_uncased

Uncased (Unicode case-folded) counterpart of [`argsort`](#argsort) over an output span.

```cpp
template <typename container_type_, typename string_extractor_, typename allocator_type_ = std::allocator<char>>
status_t argsort_utf8_uncased(
    container_type_ const & container,
    string_extractor_ const & extractor,
    std::span<sz_size_t> order,
    std::size_t top_count = 0,
    bool reverse = false,
    sz_capability_t capabilities = default_capabilities(),
    sz_stream_t stream = nullptr,
    allocator_type_ allocator = {}) noexcept;
```

See also: [sz_sort_uncased_order_best](https://www.unum.cloud/docs/stringzilla/c/sorting-and-sets.md#sz_sort_uncased_order_best)

## intersect

Locates the positions of identically valued elements in 2 deduplicated string arrays.

```cpp
template <typename first_container_, typename second_container_, typename first_extractor_, typename second_extractor_, typename allocator_type_ = std::allocator<char>>
expected<std::size_t> intersect(
    first_container_ const & first_container,
    first_extractor_ const & first_extractor,
    second_container_ const & second_container,
    second_extractor_ const & second_extractor,
    std::uint64_t seed,
    std::span<sz_size_t> first_positions,
    std::span<sz_size_t> second_positions,
    sz_capability_t capabilities = default_capabilities(),
    sz_stream_t stream = nullptr,
    allocator_type_ allocator = {}) noexcept;
```

- `first_container` (in): The first array of string-like elements.
- `first_extractor` (in): Extracts the string from each object of the first array.
- `second_container` (in): The second array of string-like elements.
- `second_extractor` (in): Extracts the string from each object of the second array.
- `seed` (in): Randomizes the internal hash table to resist adversarial inputs.
- `first_positions` (out): The caller-owned output span of indices from the first array.
- `second_positions` (out): The caller-owned output span of indices from the second array. Each span must fit at least `min(first.size(), second.size())` entries.
- `capabilities` (in): Capabilities to pick from.
- `stream` (in): Null on the CPU, or the stream of the device `capabilities` describes.
- `allocator` (in): Serves the scratch memory of the hash table.

See also: [sz_intersect_best](https://www.unum.cloud/docs/stringzilla/c/sorting-and-sets.md#sz_intersect_best)

