C++ · Sorting and Sets

Sorting and Sets

#include <stringzilla/stringzilla.hpp> 6.0.0-rc.1 · 91a27bdd

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:

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.

#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.

#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.

#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;
}

API

Functions

fill_randomOverwrites the string slice with random bytes.
stringzilla.hpp:4786 ↗
status_t fill_random(
    string_span_t string,
    std::uint64_t nonce,
    sz_capability_t capabilities = default_capabilities(),
    sz_stream_t stream = nullptr) noexcept;
status_t fill_random(string_span_t string) noexcept;

Overwrites the string slice with random bytes using std::rand for the nonce.

Parameters

stringoutThe string to overwrite.
noncein"Number used once" seeding the random number generator, never repeat it!
capabilitiesinCapabilities to pick from.
streaminNull on the CPU, or the stream of the device capabilities describes.

See also

argsortComputes the permutation of an array, that would lead to sorted order.
stringzilla.hpp:5038 ↗
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 C interface, overwrites the output span.

Parameters

containerinThe array of string-like elements to sort.
extractorinThe function object that extracts the string from the object.
orderoutThe caller-owned output span of indices, populated with the permutation. Must hold at least container.size() entries; the whole span receives a permutation.
top_countinIf non-zero, only the first top_count entries are guaranteed fully sorted (partial sort).
reverseinIf true, sorts in descending order.
capabilitiesinCapabilities to pick from.
streaminNull on the CPU, or the stream of the device capabilities describes.
allocatorinServes the scratch memory of the sort.

See also

Statuses

argsort_utf8_uncasedUncased (Unicode case-folded) counterpart of argsort over an output span.
stringzilla.hpp:5062 ↗
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

intersectLocates the positions of identically valued elements in 2 deduplicated string arrays.
stringzilla.hpp:5101 ↗
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;

Parameters

first_containerinThe first array of string-like elements.
first_extractorinExtracts the string from each object of the first array.
second_containerinThe second array of string-like elements.
second_extractorinExtracts the string from each object of the second array.
seedinRandomizes the internal hash table to resist adversarial inputs.
first_positionsoutThe caller-owned output span of indices from the first array.
second_positionsoutThe caller-owned output span of indices from the second array. Each span must fit at least min(first.size(), second.size()) entries.
capabilitiesinCapabilities to pick from.
streaminNull on the CPU, or the stream of the device capabilities describes.
allocatorinServes the scratch memory of the hash table.

Returns

The number of matched pairs, valid in the first entries of each output span, with unexpected_dimensions_k if a span is too short, or the status of the kernel.

See also