# Splitting and Partitioning

> StringZilla for C++

This is a C++-only convenience layer; the C ABI provides the underlying scans `sz_find_best` and `sz_find_byteset_best` that you compose by hand.

`partition` and `rpartition` return a three-way `sz::string_partition_result` struct with `before`, `match`, and `after` members around the first or last occurrence of a pattern.
The pattern can be a view, a single character, or a byte-set.

`split`, `rsplit`, and `splitlines` return lazy ranges of `string_view`s.
`split(view)` yields a `sz::find_splits_view`, `rsplit(view)` yields a `sz::rfind_splits_view`, and the byte-set overloads `split(byteset)` and `rsplit(byteset)` yield the matching character-splitter views.
`split(byteset)` and `rsplit(byteset)` default to whitespace, and `splitlines()` defaults to the newline set.
A trailing separator yields a final empty segment, and an empty separator never splits, yielding the whole text as the only segment.

The key property is that these ranges are _lazy_ — they borrow from the source buffer and walk it one `string_view` at a time, so the next boundary is computed only when you ask for it.
Splitting a large document into millions of tokens therefore costs __no per-token allocation__: each iteration step advances a cursor and hands back a borrowed view.
This is the decisive contrast with the idiomatic STL approach, where splitting builds a `std::vector<std::string>` and heap-allocates a fresh string for every element, copying its bytes out of the source.

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

int main() {
    sz::string_view_t csv = "alice,30,engineer";

    sz::string_partition_result<sz::string_view_t> first = csv.partition(',');
    sz::string_view_t name = first.before;
    assert(name == "alice");
    assert(first.match == ",");
    assert(first.after == "30,engineer");

    std::size_t fields = 0;
    for (sz::string_view_t field : csv.split(",")) // "alice", "30", "engineer"
        ++fields;                                // no string is allocated per field
    assert(fields == 3);

    sz::string_view_t doc = "line one\nline two\nline three";
    for (sz::string_view_t line : doc.splitlines()) (void)line;

    return 0;
}
```

Each range exposes `begin`/`end`, dereferences to a `sz::string_view_t`, and can be consumed in a single pass.
When you genuinely need the tokens to outlive the source you can still materialize them into a `std::vector<sz::string_view_t>`, but only then do you pay for storage — the default path stays allocation-free.

`#include <stringzilla/stringzilla.hpp>`

## find_splits_view

A range of string slices for different splits of the data.

```cpp
template <typename string_type_,
          typename matcher_type_>
class find_splits_view
```

In some sense, represents the inverse operation to `[find_matches_view](https://www.unum.cloud/docs/stringzilla/cpp/searching-and-counting.md#find_matches_view)`, as it reports not the search matches but the data between them. Meaning that for `N` search matches, there will be `N+1` elements in the range, the last one empty when the text ends with a separator. Unlike ::find_matches_view, this range can't be empty, and an empty separator yields the whole text as its only segment. It also can't report overlapping intervals.

Notes: Lifetime semantics: Stores forwarded objects, owning strings included, to keep them alive. Iterators receive lightweight views only, so iteration is safe from ownership bugs.

Notes: For-loop optimized: Iterators are lightweight views with minimal register pressure, ideal for high-performance applications where cache efficiency and register allocation matter.

Notes: Sentinel support: Supports sentinel-based iteration via `operator==(end_sentinel_t)` for efficient termination without constructing full end iterators.

Notes: Compatible with C++20 ranges, `std::string_view`, and of course, StringZilla.

Notes: Similar to a pair of `boost::algorithm::split_iterator`.

## rfind_splits_view

A range of string slices for different splits of the data in __reverse-order__.

```cpp
template <typename string_type_,
          typename matcher_type_>
class rfind_splits_view
```

In some sense, represents the inverse operation to `[find_matches_view](https://www.unum.cloud/docs/stringzilla/cpp/searching-and-counting.md#find_matches_view)`, as it reports not the search matches but the data between them. Meaning that for `N` search matches, there will be `N+1` elements in the range, the last one empty when the text starts with a separator. Unlike ::find_matches_view, this range can't be empty, and an empty separator yields the whole text as its only segment. It also can't report overlapping intervals.

Notes: Lifetime semantics: Stores forwarded objects, owning strings included, to keep them alive. Iterators receive lightweight views only, so iteration is safe from ownership bugs.

Notes: For-loop optimized: Iterators are lightweight views with minimal register pressure, ideal for high-performance applications where cache efficiency and register allocation matter.

Notes: Sentinel support: Supports sentinel-based iteration via `operator==(end_sentinel_t)` for efficient termination without constructing full end iterators.

Notes: Compatible with C++20 ranges, `std::string_view`, and of course, StringZilla.

Notes: Similar to a pair of `boost::algorithm::split_iterator`.

## split_parts_t

Which parts of the split a `[utf8_split_view](#utf8_split_view)` yields at each separator the kernel reports.

```cpp
enum split_parts_t
```

- `between_k`: The default: the segments __between__ separators, as in `split_newlines`, `split_whitespaces`, and `split_delimiters`.
- `separators_k`: The separator runs themselves: newlines, whitespace, or delimiter codepoints.
- `both_k`: Interleaved segment, separator, segment, and so on, so rejoining them yields the input.

## empty_segments_t

Whether a [utf8_split_view](#utf8_split_view) keeps or drops empty (zero-length) segments.

```cpp
enum empty_segments_t
```

- `keep_k`
- `skip_k`

## string_partition_result

A result of split a string once, containing the string slice ::before, the ::match itself, and the slice ::after.

```cpp
template <typename string_>
struct string_partition_result
```

- `before` `string_`
- `match` `string_`
- `after` `string_`

## utf8_split_view

A range of string slices split on the delimiter codepoints that a __token__ kernel reports to it.

```cpp
template <utf8_tokenizer_best_t tokenizer_,
          utf8_tokenizer_serial_t tokenizer_serial_,
          typename string_type_,
          std::size_t steps_ = sz_iterators_default_steps_k,
          split_parts_t parts_ = split_parts_t::between_k,
          empty_segments_t empties_ = empty_segments_t::keep_k>
class utf8_split_view
```

The shared engine behind the between-segment views `utf8_split_newlines_view`, `utf8_split_whitespaces_view`, and `utf8_split_delimiters_view`, and the delimiter-run views `utf8_newlines_view`, `utf8_whitespaces_view`, and `utf8_delimiters_view`. The kernel reports the byte spans of the __delimiters__, and `parts_` selects what the view yields:

- `between_k:` the N+1 segments between delimiters, the friendly lines, tokens, or fields;
- `separators_k:` the N delimiter runs themselves, the newlines, whitespace, or delimiters;
- `both_k:` the 2N+1 items interleaved, so concatenating them reconstructs the input exactly.Each batch's separator endpoints are expanded once into a single boundary array, so every span is just `bounds_[i] .. bounds_[i+1]` and `parts_` reduces to a compile-time ____(first,stride) over it: the iterator's hot path is one branchless formula shared by all three modes. `empties_`, a compile-time switch reachable via `.skip_empty()`, drops empty segments, and [`both_k`](#both_k) is lossless only when empties are kept.

## utf8_split_view

```cpp
utf8_split_view() noexcept=default;
```

```cpp
utf8_split_view(string_type haystack) noexcept;
```

## skip_empty

The same split with empty segments dropped (compile-time, branchless).

```cpp
utf8_split_view<tokenizer_, tokenizer_serial_, string_type_, steps_, parts_, empty_segments_t::skip_k> skip_empty() const noexcept;
```

## with_separators

The same split yielding segments __and__ delimiters interleaved, losslessly, turning [`between_k`](#between_k) into [`both_k`](#both_k).

```cpp
utf8_split_view<tokenizer_, tokenizer_serial_, string_type_, steps_, split_parts_t::both_k, empties_> with_separators() const noexcept;
```

## to

Copies the items into a container.

```cpp
template <typename container_>
void to(container_ & container);
```

```cpp
template <typename container_>
container_ to(container_ && container = {});
```

Copies the items into a consumed container, returning it at the end.

## split

Splits a string around every __non-overlapping__ inclusion of the second string.

```cpp
template <typename haystack_type_, typename needle_type_>
find_splits_view<range_haystack_type<haystack_type_>, matcher_find<range_view_type<haystack_type_>, exclude_overlaps_t, range_needle_type<haystack_type_, needle_type_>>> split(
    haystack_type_ && h,
    needle_type_ && n) noexcept;
```

Notes: An __empty__ delimiter never splits, yielding the whole string as the only segment.

## rsplit

Splits a string around every __non-overlapping__ inclusion of the second string in __reverse__ order.

```cpp
template <typename haystack_type_, typename needle_type_>
rfind_splits_view<range_haystack_type<haystack_type_>, matcher_rfind<range_view_type<haystack_type_>, exclude_overlaps_t, range_needle_type<haystack_type_, needle_type_>>> rsplit(
    haystack_type_ && h,
    needle_type_ && n) noexcept;
```

Notes: An __empty__ delimiter never splits, yielding the whole string as the only segment.

See also: [split](#split)

## split_characters

Splits a string around every character from the second string.

```cpp
template <typename haystack_type_, typename needle_type_>
find_splits_view<range_haystack_type<haystack_type_>, matcher_find_first_of<range_view_type<haystack_type_>>> split_characters(
    haystack_type_ && h,
    needle_type_ const & n) noexcept;
```

## rsplit_characters

Splits a string around every character from the second string in __reverse__ order.

```cpp
template <typename haystack_type_, typename needle_type_>
rfind_splits_view<range_haystack_type<haystack_type_>, matcher_find_last_of<range_view_type<haystack_type_>>> rsplit_characters(
    haystack_type_ && h,
    needle_type_ const & n) noexcept;
```

## split_other_characters

Splits a string around every character except the ones from the second string.

```cpp
template <typename haystack_type_, typename needle_type_>
find_splits_view<range_haystack_type<haystack_type_>, matcher_find_first_of<range_view_type<haystack_type_>>> split_other_characters(
    haystack_type_ && h,
    needle_type_ const & n) noexcept;
```

## rsplit_other_characters

Splits a string around every character except the ones from the second string in __reverse__ order.

```cpp
template <typename haystack_type_, typename needle_type_>
rfind_splits_view<range_haystack_type<haystack_type_>, matcher_find_last_of<range_view_type<haystack_type_>>> rsplit_other_characters(
    haystack_type_ && h,
    needle_type_ const & n) noexcept;
```

