Repository: ashvardanian/StringZilla (language: rust)

Issue:
Supporting more Swift versions
Current CI pipeline uploads only a package for Swift 5.9 and only MacOS+Linux. The package, however, should work just fine on other versions of Swift, iOS, visionOS, watchOS, and tvOS. 


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml
index 5db20c97..4ecaa0e9 100644
--- a/.github/workflows/prerelease.yml
+++ b/.github/workflows/prerelease.yml
@@ -107,9 +107,6 @@ jobs:
 
     steps:
       - uses: actions/checkout@v4
-        with:
-          ref: main-dev
-      - run: git submodule update --init --recursive
 
         # C/C++
         # Clang 16 isn't available from default repos on Ubuntu 22.04, so we have to install it manually
@@ -203,9 +200,6 @@ jobs:
 
     steps:
       - uses: actions/checkout@v4
-        with:
-          ref: main-dev
-      - run: git submodule update --init --recursive
 
         # C/C++
       - name: Build C/C++
@@ -263,10 +257,73 @@ jobs:
           toolchain: stable
           override: true
 
+  test_windows:
+    name: Windows
+    runs-on: windows-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Python ${{ env.PYTHON_VERSION }}
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ env.PYTHON_VERSION }}
+      - name: Build Python
+        run: |
+          python -m pip install --upgrade pip
+          pip install pytest pytest-repeat
+          python -m pip install .
+      - name: Test Python
+        run: pytest scripts/test.py -s -x
+
+  test_alpine:
+    name: Alpine Linux
+    runs-on: ubuntu-latest
+    container:
+      image: alpine:latest
+      options: --privileged # If needed for certain Docker operations
+    steps:
+      - uses: actions/checkout@v3
+
+        # C/C++
+      - name: Build C/C++
+        run: |
+          apk add --update make cmake g++ gcc
+          cmake -B build_artifacts \
+            -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+            -DCMAKE_EXPORT_COMPILE_COMMANDS=1 \
+            -DSTRINGZILLA_BUILD_BENCHMARK=1 \
+            -DSTRINGZILLA_BUILD_TEST=1
+          cmake --build build_artifacts --config RelWithDebInfo
+      - name: Test C++
+        run: ./build_artifacts/stringzilla_test_cpp20
+
+        # Python
+      - name: Build Python
+        run: |
+          apk add python3 py3-pip python3-dev
+          pip install --break-system-packages pytest pytest-repeat
+          pip install --break-system-packages .
+      - name: Test Python
+        run: pytest scripts/test.py -s -x
+
+        # Rust
+      - name: Test Rust
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          override: true
+
   build_wheels:
     name: Build Python ${{ matrix.python-version }} for ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
-    needs: [test_ubuntu_gcc, test_ubuntu_clang, test_macos]
+    needs:
+      [
+        test_ubuntu_gcc,
+        test_ubuntu_clang,
+        test_macos,
+        test_windows,
+        test_alpine,
+      ]
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b71ff2a6..a4d9fd26 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -14,6 +14,7 @@ permissions:
   contents: write
   pages: write
   id-token: write
+  packages: write
 
 jobs:
   versioning:
@@ -64,6 +65,9 @@ jobs:
         python-version: ["36", "37", "38", "39", "310", "311", "312"]
     steps:
       - uses: actions/checkout@v4
+        with:
+          ref: "main"
+
       - name: Set up Python
         uses: actions/setup-python@v5
         with:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 566988ef..63d0f17a 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -33,6 +33,7 @@
     "bitcast",
     "BLOSUM",
     "Brumme",
+    "carray",
     "Cawley",
     "cheminformatics",
     "cibuildwheel",
@@ -48,8 +49,11 @@
     "getslice",
     "Giancarlo",
     "Gonnet",
+    "Heikki",
+    "hexdigits",
     "Hirschberg's",
     "Horspool",
+    "Hyyro",
     "initproc",
     "intp",
     "isprintable",
@@ -82,9 +86,12 @@
     "NOTIMPLEMENTED",
     "npos",
     "numpy",
+    "octdigits",
     "octogram",
+    "printables",
     "pytest",
     "Pythonic",
+    "qsort",
     "quadgram",
     "Raita",
     "readlines",
@@ -111,6 +118,7 @@
     "Tanimoto",
     "thyrotropin",
     "Titin",
+    "tparam",
     "TPFLAGS",
     "unigram",
     "usecases",
diff --git a/Cargo.lock b/Cargo.lock
index e97fba7a..080c826a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,7 +19,7 @@ checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
 
 [[package]]
 name = "stringzilla"
-version = "3.0.0"
+version = "3.2.0"
 dependencies = [
  "cc",
 ]
diff --git a/Package.swift b/Package.swift
index 27d90276..c5c15fbb 100644
--- a/Package.swift
+++ b/Package.swift
@@ -3,6 +3,13 @@ import PackageDescription
 
 let package = Package(
     name: "StringZilla",
+    platforms: [
+        // Linux doesn't have to be explicitly listed
+        .iOS(.v13),      // For iOS, version 13 and later
+        .tvOS(.v13),     // For tvOS, version 13 and later
+        .macOS(.v10_15), // For macOS, version 10.15 (Catalina) and later
+        .watchOS(.v6)    // For watchOS, version 6 and later
+    ],
     products: [
         .library(
             name: "StringZilla",
diff --git a/README.md b/README.md
index 26173367..6dbf5172 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,11 @@
 # StringZilla 🦖
 
 [![StringZilla Python installs](https://static.pepy.tech/personalized-badge/stringzilla?period=total&units=abbreviation&left_color=black&right_color=blue&left_text=StringZilla%20Python%20installs)](https://github.com/ashvardanian/stringzilla)
-[![StringZilla Rust installs](https://img.shields.io/crates/d/stringzilla?logo=rust")](https://crates.io/crates/stringzilla)
+[![StringZilla Rust installs](https://img.shields.io/crates/d/stringzilla?logo=rust&label=Rust%20installs)](https://crates.io/crates/stringzilla)
+[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ashvardanian/StringZilla/release.yml?branch=main&label=Ubuntu)](https://github.com/ashvardanian/StringZilla/actions/workflows/release.yml)
+[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ashvardanian/StringZilla/release.yml?branch=main&label=Windows)](https://github.com/ashvardanian/StringZilla/actions/workflows/release.yml)
+[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ashvardanian/StringZilla/release.yml?branch=main&label=MacOS)](https://github.com/ashvardanian/StringZilla/actions/workflows/release.yml)
+[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ashvardanian/StringZilla/release.yml?branch=main&label=Alpine%20Linux)](https://github.com/ashvardanian/StringZilla/actions/workflows/release.yml)
 ![StringZilla code size](https://img.shields.io/github/languages/code-size/ashvardanian/stringzilla)
 
 StringZilla is the GodZilla of string libraries, using [SIMD][faq-simd] and [SWAR][faq-swar] to accelerate string operations on modern CPUs.
@@ -16,10 +20,13 @@ It __accelerates exact and fuzzy string matching, edit distance computations, so
 - 🐍 __[Python](#quick-start-python-🐍):__ Upgrade your `str` to faster `Str`
 - 🍎 __[Swift](#quick-start-swift-🍏):__ Use the `String+StringZilla` extension
 - 🦀 __[Rust](#quick-start-rust-🦀):__ Use the `StringZilla` traits crate
+- 🐚 __[Shell][faq-shell]__: Accelerate common CLI tools
 - 📚 Researcher? Jump to [Algorithms & Design Decisions](#algorithms--design-decisions-📚)
 - 🤝 Want to benchmark or contribute? Jump to [Contributing](CONTRIBUTING.md)
 - Code in other languages? Let [me](https://github.com/ashvardanian) know!
 
+[faq-shell]: https://github.com/ashvardanian/StringZilla/blob/main/cli/README.md
+
 __Who is this for?__
 
 - For data-engineers parsing large datasets, like the [CommonCrawl](https://commoncrawl.org/), [RedPajama](https://github.com/togethercomputer/RedPajama-Data), or [LAION](https://laion.ai/blog/laion-5b/).
@@ -362,8 +369,8 @@ count: int = sz.count("haystack", "needle", start=0, end=9223372036854775807, al
 
 ```py
 assert sz.edit_distance("apple", "aple") == 1 # skip one ASCII character
-assert sz.edit_distance("αβγδ", "αγδ") == 2 # skip two bytes forming one codepoint
-assert sz.edit_distance_unicode("αβγδ", "αγδ") == 1 # one unicode codepoint
+assert sz.edit_distance("αβγδ", "αγδ") == 2 # skip two bytes forming one rune
+assert sz.edit_distance_unicode("αβγδ", "αγδ") == 1 # one unicode rune
 ```
 
 Several Python libraries provide edit distance computation.
diff --git a/build.rs b/build.rs
index 368600eb..8f7a130d 100644
--- a/build.rs
+++ b/build.rs
@@ -1,12 +1,85 @@
+use std::env;
+
 fn main() {
-    cc::Build::new()
+    let mut build = cc::Build::new();
+    build
         .file("c/lib.c")
         .include("include")
         .warnings(false)
         .flag_if_supported("-std=c99")
-        .flag_if_supported("-fcolor-diagnostics")
-        .flag_if_supported("-fPIC")
-        .compile("stringzilla");
+        .flag_if_supported("-fPIC");
+
+    // Cargo will set different environment variables that we can use to properly configure the build.
+    // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
+    let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
+    let target_endian = env::var("CARGO_CFG_TARGET_ENDIAN").unwrap_or_default();
+
+    // To get the operating system we can use the TARGET environment variable.
+    // To check the list of available targets, run `rustc --print target-list`.
+    let target = env::var("TARGET").unwrap_or_default();
+
+    if target.contains("linux") {
+        build.flag_if_supported("-fdiagnostics-color=always");
+        build.flag_if_supported("-O3");
+        build.flag_if_supported("-pedantic");
+
+        // Set architecture-specific flags and macros
+        if target_arch == "x86_64" {
+            build.define("SZ_USE_X86_AVX512", "1");
+            build.define("SZ_USE_X86_AVX2", "1");
+        } else {
+            build.define("SZ_USE_X86_AVX512", "0");
+            build.define("SZ_USE_X86_AVX2", "0");
+        }
+
+        if target_arch == "aarch64" {
+            build.flag_if_supported("-march=armv8-a+simd");
+            build.define("SZ_USE_ARM_SVE", "1");
+            build.define("SZ_USE_ARM_NEON", "1");
+        } else {
+            build.define("SZ_USE_ARM_SVE", "0");
+            build.define("SZ_USE_ARM_NEON", "0");
+        }
+    } else if target.contains("darwin") {
+        build.flag_if_supported("-fcolor-diagnostics");
+        build.flag_if_supported("-O3");
+        build.flag_if_supported("-pedantic");
+
+        if target_arch == "x86_64" {
+            // Assuming no AVX-512 support for Darwin as per setup.py logic
+            build.define("SZ_USE_X86_AVX512", "0");
+            build.define("SZ_USE_X86_AVX2", "1");
+        } else {
+            build.define("SZ_USE_X86_AVX512", "0");
+            build.define("SZ_USE_X86_AVX2", "0");
+        }
+
+        if target_arch == "aarch64" {
+            build.define("SZ_USE_ARM_SVE", "0"); // Assuming no SVE support for Darwin
+            build.define("SZ_USE_ARM_NEON", "1");
+        } else {
+            build.define("SZ_USE_ARM_SVE", "0");
+            build.define("SZ_USE_ARM_NEON", "0");
+        }
+    } else if target.contains("windows") {
+        // Set architecture-specific flags and macros
+        if target_arch == "x86_64" {
+            build.define("SZ_USE_X86_AVX512", "1");
+            build.define("SZ_USE_X86_AVX2", "1");
+        } else {
+            build.define("SZ_USE_X86_AVX512", "0");
+            build.define("SZ_USE_X86_AVX2", "0");
+        }
+    }
+
+    // Set endian-specific macro
+    if target_endian == "big" {
+        build.define("SZ_DETECT_BIG_ENDIAN", "1");
+    } else {
+        build.define("SZ_DETECT_BIG_ENDIAN", "0");
+    }
+
+    build.compile("stringzilla");
 
     println!("cargo:rerun-if-changed=c/lib.c");
     println!("cargo:rerun-if-changed=rust/lib.rs");
diff --git a/c/lib.c b/c/lib.c
index 1b0ad871..e58e215f 100644
--- a/c/lib.c
+++ b/c/lib.c
@@ -117,7 +117,6 @@ typedef struct sz_implementations_t {
     sz_find_set_t find_from_set;
     sz_find_set_t rfind_from_set;
 
-    // TODO: Upcoming vectorization
     sz_edit_distance_t edit_distance;
     sz_alignment_score_t alignment_score;
     sz_hashes_t hashes;
diff --git a/cli/README.md b/cli/README.md
new file mode 100644
index 00000000..0a75d30d
--- /dev/null
+++ b/cli/README.md
@@ -0,0 +1,64 @@
+# SIMD-accelerated CLI utilities based on StringZilla
+
+This section of the project is pretty much a work-in-progress.
+The goal is to provide a set of command-line utilities that:
+
+- ✅ are accelerated by SIMD instructions, 
+- ✅ are extremely core StringZilla functionality,
+- ✅ work the same on Linux, MacOS, and Windows.
+
+To install, just pull the Python package from PyPi:
+
+```bash
+pip install stringzilla
+```
+
+Currently implemented:
+
+- `sz_wc`: 3x faster `wc` word count.
+- `sz_split`: 4x faster `splt` file splitting.
+
+What other interfaces should be added?
+Levenshtein distances?
+Fuzzy search?
+Are there common alternatives to mimic?
+
+## `wc`: Word Count
+
+The `wc` utility on Linux can be used to count the number of lines, words, and bytes in a file.
+Using SIMD-accelerated character and character-set search, StringZilla, even with slow SSDs, it can be noticeably faster.
+
+```bash
+$ time wc enwik9.txt
+  13147025 129348346 1000000000 enwik9.txt
+
+real    0m3.562s
+user    0m3.470s
+sys     0m0.092s
+
+$ time sz_wc enwik9.txt
+  13147025 139132610 1000000000 enwik9.txt # Note: different word count, WIP
+
+real    0m1.165s
+user    0m1.121s
+sys     0m0.044s
+```
+
+## `split`: Split File into Smaller Ones
+
+The `split` utility on Linux can be used to split a file into smaller ones.
+The current prototype only splits by line counts.
+
+```bash
+$ time split -l 100000 enwik9.txt ...
+
+real    0m6.424s
+user    0m0.179s
+sys     0m0.663s
+
+$ time sz_split -l 100000 enwik9.txt ...
+
+real    0m1.482s
+user    0m1.020s
+sys     0m0.460s
+```
diff --git a/cli/__init__.py b/cli/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/cli/split.py b/cli/split.py
new file mode 100755
index 00000000..f36b5d9c
--- /dev/null
+++ b/cli/split.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+
+import argparse
+import sys
+import stringzilla
+from stringzilla import File, Str
+
+
+def parse_arguments():
+    parser = argparse.ArgumentParser(
+        description="Output pieces of FILE to PREFIXaa, PREFIXab, ...; default size is 1000 lines, and default PREFIX is 'x'."
+    )
+    parser.add_argument(
+        "file", nargs="?", default="-", help='File to process, "-" for standard input'
+    )
+    parser.add_argument(
+        "prefix", nargs="?", default="X", help='Output file prefix, default is "x"'
+    )
+    parser.add_argument(
+        "-l",
+        "--lines",
+        type=int,
+        default=1000,
+        help="Number of lines per output file, default is 1000",
+    )
+    parser.add_argument(
+        "-t",
+        "--separator",
+        default="\n",
+        help="Use SEP instead of newline as the record separator; '\\0' (zero) specifies the NUL character",
+    )
+    parser.add_argument(
+        "-n",
+        "--number",
+        type=int,
+        default=None,
+        help="Generate N output files based on size of input",
+    )
+    parser.add_argument("--version", action="version", version=stringzilla.__version__)
+    return parser.parse_args()
+
+
+def split_file(file_path, lines_per_file, output_prefix, separator, number_of_files):
+    try:
+        if separator == "\\0":
+            separator = "\0"
+        if file_path == "-":
+            file_contents = Str(sys.stdin.read())
+        else:
+            file_mapped = File(file_path)
+            file_contents = Str(file_mapped)
+
+        if number_of_files is not None:
+            total_length = len(file_contents)
+            chunk_size = total_length // number_of_files
+            for file_part in range(number_of_files):
+                start = file_part * chunk_size
+                end = (
+                    start + chunk_size
+                    if file_part < number_of_files - 1
+                    else total_length
+                )
+                current_slice = file_contents[start:end]
+                output_path = f"{output_prefix}{file_part}"
+                current_slice.write_to(output_path)
+            return
+        current_position = 0
+        file_part = 0
+        newline_position = -1
+
+        while current_position < len(file_contents):
+            for _ in range(lines_per_file):
+                newline_position = file_contents.find(separator, newline_position + 1)
+                if newline_position == -1:
+                    break
+
+            if newline_position == -1 and current_position < len(file_contents):
+                newline_position = len(file_contents)
+
+            section_length = (
+                newline_position - current_position if newline_position != -1 else 0
+            )
+
+            if section_length > 0:
+                current_slice = file_contents[current_position : newline_position + 1]
+                output_path = f"{output_prefix}{file_part}"
+                current_slice.write_to(output_path)
+
+                file_part += 1
+                current_position = newline_position + 1
+
+    except FileNotFoundError:
+        print(f"No such file: {file_path}")
+    except Exception as e:
+        print(f"An error occurred: {e}")
+        print("Usage example: split.py [-l LINES] [file] [prefix]")
+
+
+def main():
+    args = parse_arguments()
+    split_file(args.file, args.lines, args.prefix, args.separator, args.number)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/cli/wc.py b/cli/wc.py
new file mode 100755
index 00000000..e73bd3a5
--- /dev/null
+++ b/cli/wc.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+
+import sys
+import argparse
+import stringzilla
+from stringzilla import File, Str
+
+
+def parse_arguments():
+    parser = argparse.ArgumentParser(
+        description="Print newline, word, and byte counts for each FILE, and a total line if more than one FILE is \
+        specified. A word is a non-zero-length sequence of acters delimited by white space."
+    )
+    parser.add_argument("files", nargs="*", default=["-"], help="Files to process")
+    parser.add_argument(
+        "-c", "--bytes", action="store_true", help="print the byte counts"
+    )
+    parser.add_argument(
+        "-m", "--chars", action="store_true", help="print the character counts"
+    )
+    parser.add_argument(
+        "-l", "--lines", action="store_true", help="print the newline counts"
+    )
+    parser.add_argument(
+        "-L",
+        "--max-line-length",
+        action="store_true",
+        help="print the maximum display width",
+    )
+    parser.add_argument(
+        "-w", "--words", action="store_true", help="print the word counts"
+    )
+    parser.add_argument("--version", action="version", version=stringzilla.__version__)
+    return parser.parse_args()
+
+
+def wc(file_path, args):
+    if file_path == "-":  # read from stdin
+        content = sys.stdin.read()
+        mapped_bytes = Str(content)
+    else:
+        try:
+            mapped_file = File(file_path)
+            mapped_bytes = Str(mapped_file)
+        except RuntimeError:  # File gives a RuntimeError if the file does not exist
+            return f"No such file: {file_path}", False
+
+    line_count = mapped_bytes.count("\n")
+    word_count = mapped_bytes.count(" ") + 1
+    char_count = mapped_bytes.__len__()
+    counts = {
+        "line_count": line_count,
+        "word_count": word_count,
+        "char_count": char_count,
+    }
+
+    if args.max_line_length:
+        max_line_length = max(len(line) for line in str(mapped_bytes).split("\n"))
+        counts["max_line_length"] = max_line_length
+
+    if args.bytes or args.chars:
+        byte_count = char_count  # assume 1 char = 1 byte
+        counts["byte_count"] = byte_count
+
+    return counts, True
+
+
+def format_output(counts, args):
+    selected_counts = []
+    if args.lines:
+        selected_counts.append(counts["line_count"])
+    if args.words:
+        selected_counts.append(counts["word_count"])
+    if args.chars:
+        selected_counts.append(counts["char_count"])
+    if args.bytes:
+        selected_counts.append(counts.get("byte_count", counts["char_count"]))
+    if args.max_line_length:
+        selected_counts.append(counts.get("max_line_length", 0))
+
+    if not any([args.lines, args.words, args.chars, args.bytes, args.max_line_length]):
+        selected_counts = [
+            counts["line_count"],
+            counts["word_count"],
+            counts["char_count"],
+        ]
+
+    return " ".join(str(count) for count in selected_counts)
+
+
+def main():
+    args = parse_arguments()
+    total_counts = {
+        "line_count": 0,
+        "word_count": 0,
+        "char_count": 0,
+        "max_line_length": 0,
+        "byte_count": 0,
+    }
+
+    for file_path in args.files:
+        counts, success = wc(file_path, args)
+        if success:
+            for key in total_counts.keys():
+                total_counts[key] += counts.get(key, 0)
+            output = format_output(counts, args) + f" {file_path}"
+            print(output)
+        else:
+            print(counts)
+
+    if len(args.files) > 1:
+        total_output = format_output(total_counts, args) + " total"
+        print(total_output)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/include/stringzilla/experimental.h b/include/stringzilla/experimental.h
index 93fc1ff3..b20de5bb 100644
--- a/include/stringzilla/experimental.h
+++ b/include/stringzilla/experimental.h
@@ -368,6 +368,127 @@ sz_u512_vec_t sz_inclusive_min(sz_i32_t previous, sz_error_cost_t gap, sz_u512_v
 
 #if SZ_USE_ARM_NEON
 
+SZ_PUBLIC sz_cptr_t sz_find_neon_too_smart(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
+
+    // This almost never fires, but it's better to be safe than sorry.
+    if (h_length < n_length || !n_length) return SZ_NULL_CHAR;
+    if (n_length == 1) return sz_find_byte_neon(h, h_length, n);
+
+    // Scan through the string.
+    // Assuming how tiny the Arm NEON registers are, we should avoid internal branches at all costs.
+    // That's why, for smaller needles, we use different loops.
+    if (n_length == 2) {
+        // This is a common case. Aside from ASCII bigrams, it's also the most common case for UTF-16,
+        // or any UTF8 content in Cyrillic, Greek, Armenian, Hebrew, Arabic, Coptic, Syriac, Thaana,
+        // N'Ko writing system of West-African nations, and, of course, Latin scripts.
+        // Dealing with 16-bit values, we can check 15 possible offsets in a single loop iteration.
+        // For that we are going to keep 2 registers populated with haystack data.
+        // First - bigrams at even offsets - 0, 2, 4, 6, 8, 10, 12, 14.
+        // Second - bigrams at odd offsets - 1, 3, 5, 7, 9, 11, 13. One less than the first one.
+        sz_u64_t matches;
+        sz_u128_vec_t h_even_vec, h_odd_vec, n_vec, interleave_mask_vec, matches_vec;
+        // Broadcast needle characters into SIMD registers.
+        n_vec.u16x8 = vdupq_n_u16(sz_u16_load(n).u16);
+        interleave_mask_vec.u16x8 = vdupq_n_u16(0x00FFu);
+        for (; h_length >= 16; h += 15, h_length -= 15) {
+            h_even_vec.u8x16 = vld1q_u8((sz_u8_t const *)h);
+            h_odd_vec.u8x16 = vextq_u8(h_even_vec.u8x16, /* can be any noise: */ h_even_vec.u8x16, 1);
+            // We can now compare both 16-bit arrays with the needle.
+            // The result of each comparison will also be 16 bits long.
+            // Then - we blend!
+            // For odd offsets we are gonna take the bottom 8 bits, and for even - the top ones!
+            matches_vec.u8x16 =
+                vbslq_u8(interleave_mask_vec.u8x16, vreinterpretq_u8_u16(vceqq_u16(h_even_vec.u16x8, n_vec.u16x8)),
+                         vreinterpretq_u8_u16(vceqq_u16(h_odd_vec.u16x8, n_vec.u16x8)));
+            matches = vreinterpretq_u8_u4(matches_vec.u8x16);
+            if (matches) return h + sz_u64_ctz(matches) / 4;
+        }
+    }
+    else if (n_length == 3) {
+        // Comparing 24-bit values is a bumer. Being lazy, I went with a simple design.
+        // Instead of keeping one register per haystack offset, I keep a register per needle character.
+        sz_u64_t matches;
+        sz_u128_vec_t h_vec, n_first_vec, n_second_vec, n_third_vec, matches_vec;
+        // Broadcast needle characters into SIMD registers.
+        n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[0]);
+        n_second_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[1]);
+        n_third_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[2]);
+        for (; h_length >= 16; h += 14, h_length -= 14) {
+            h_vec.u8x16 = vld1q_u8((sz_u8_t const *)h);
+            // Let's compare the first character.
+            matches_vec.u8x16 = vceqq_u8(h_vec.u8x16, n_first_vec.u8x16);
+            // Let's compare the second one, shift the equality indicators left by 8 bits, and blend.
+            matches_vec.u8x16 =
+                vandq_u8(matches_vec.u8x16, vextq_u8(vceqq_u8(h_vec.u8x16, n_second_vec.u8x16), vdupq_n_u8(0), 1));
+            // Let's compare the third one, shift the equality indicators left by 16 bits, and blend.
+            matches_vec.u8x16 =
+                vandq_u8(matches_vec.u8x16, vextq_u8(vceqq_u8(h_vec.u8x16, n_third_vec.u8x16), vdupq_n_u8(0), 2));
+            // Now reduce bytes to nibbles, and check for matches.
+            matches = vreinterpretq_u8_u4(matches_vec.u8x16);
+            if (matches) return h + sz_u64_ctz(matches) / 4;
+        }
+    }
+    else if (n_length == 4) {
+        // This is a common case not only for ASCII 4-grams, but also UTF-32 content,
+        // emojis, Chinese, and many other east-Asian languages.
+        // Dealing with 32-bit values, we can analyze 13 offsets at once.
+        sz_u128_vec_t h_first_vec, h_second_vec, h_third_vec, h_fourth_vec, interleave_2mask_vec, interleave_4mask_vec,
+            n_vec, matches_vec;
+        sz_u64_t matches;
+        // Broadcast needle characters into SIMD registers.
+        n_vec.u32x4 = vdupq_n_u32(sz_u32_load(n).u32);
+        interleave_2mask_vec.u16x8 = vdupq_n_u16(0x00FFu);
+        interleave_4mask_vec.u32x4 = vdupq_n_u32(0x0000FFFFu);
+        for (; h_length >= 16; h += 13, h_length -= 13) {
+            h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)h);
+            h_second_vec.u8x16 = vextq_u8(h_first_vec.u8x16, /* can be any noise: */ h_first_vec.u8x16, 1);
+            h_third_vec.u8x16 = vextq_u8(h_first_vec.u8x16, /* can be any noise: */ h_first_vec.u8x16, 2);
+            h_fourth_vec.u8x16 = vextq_u8(h_first_vec.u8x16, /* can be any noise: */ h_first_vec.u8x16, 3);
+            // We can now compare all four arrays of 32-bit values with the needle.
+            // The result of each comparison will also be 32 bits long.
+            // Then - we blend!
+            matches_vec.u8x16 = vbslq_u8(
+                interleave_4mask_vec.u8x16,
+                vbslq_u8(interleave_2mask_vec.u8x16, vreinterpretq_u8_u32(vceqq_u32(h_first_vec.u32x4, n_vec.u32x4)),
+                         vreinterpretq_u8_u32(vceqq_u32(h_second_vec.u32x4, n_vec.u32x4))),
+                vbslq_u8(interleave_2mask_vec.u8x16, vreinterpretq_u8_u32(vceqq_u32(h_third_vec.u32x4, n_vec.u32x4)),
+                         vreinterpretq_u8_u32(vceqq_u32(h_fourth_vec.u32x4, n_vec.u32x4))));
+            matches = vreinterpretq_u8_u4(matches_vec.u8x16);
+            if (matches) return h + sz_u64_ctz(matches) / 4;
+        }
+    }
+    else {
+        // Pick the parts of the needle that are worth comparing.
+        sz_size_t offset_first, offset_mid, offset_last;
+        _sz_locate_needle_anomalies(n, n_length, &offset_first, &offset_mid, &offset_last);
+        // Broadcast those characters into SIMD registers.
+        sz_u64_t matches;
+        sz_u128_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec, matches_vec;
+        n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_first]);
+        n_mid_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_mid]);
+        n_last_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_last]);
+        // Walk through the string.
+        for (; h_length >= n_length + 16; h += 16, h_length -= 16) {
+            h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_first));
+            h_mid_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_mid));
+            h_last_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_last));
+            matches_vec.u8x16 = vandq_u8(                           //
+                vandq_u8(                                           //
+                    vceqq_u8(h_first_vec.u8x16, n_first_vec.u8x16), //
+                    vceqq_u8(h_mid_vec.u8x16, n_mid_vec.u8x16)),
+                vceqq_u8(h_last_vec.u8x16, n_last_vec.u8x16));
+            matches = vreinterpretq_u8_u4(matches_vec.u8x16);
+            while (matches) {
+                int potential_offset = sz_u64_ctz(matches) / 4;
+                if (sz_equal(h + potential_offset, n, n_length)) return h + potential_offset;
+                matches &= matches - 1;
+            }
+        }
+    }
+
+    return sz_find_serial(h, h_length, n, n_length);
+}
+
 SZ_INTERNAL void interleave_uint32x4_to_uint64x2(uint32x4_t in_low, uint32x4_t in_high, uint64x2_t *out_first_second,
                                                  uint64x2_t *out_third_fourth) {
     // Interleave elements
@@ -588,7 +709,7 @@ SZ_PUBLIC void sz_hashes_neon_reusing_loads(sz_cptr_t start, sz_size_t length, s
 }
 
 SZ_PUBLIC void sz_hashes_neon_readahead(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step,
-                                       sz_hash_callback_t callback, void *callback_handle) {
+                                        sz_hash_callback_t callback, void *callback_handle) {
 
     if (length < window_length || !window_length) return;
     if (length < 2 * window_length) {
diff --git a/include/stringzilla/stringzilla.h b/include/stringzilla/stringzilla.h
index 2e74d05d..77a6e3d5 100644
--- a/include/stringzilla/stringzilla.h
+++ b/include/stringzilla/stringzilla.h
@@ -1260,7 +1260,7 @@ SZ_PUBLIC sz_cptr_t sz_rfind_charset_neon(sz_cptr_t text, sz_size_t length, sz_c
 
 // Sadly, when building Win32 images, we can't use the `_tzcnt_u64`, `_lzcnt_u64`,
 // `_BitScanForward64`, or `_BitScanReverse64` intrinsics. For now it's a simple `for`-loop.
-// In the future we can switch to a more efficient DeBrujin's algorithm.
+// In the future we can switch to a more efficient De Bruijn's algorithm.
 // https://www.chessprogramming.org/BitScan
 // https://www.chessprogramming.org/De_Bruijn_Sequence
 // https://gist.github.com/resilar/e722d4600dbec9752771ab4c9d47044f
@@ -1609,14 +1609,51 @@ SZ_INTERNAL void _sz_locate_needle_anomalies(sz_cptr_t start, sz_size_t length,
 
     // Loop through letters to find non-colliding variants.
     if (length > 3 && has_duplicates) {
-        // Pivot the middle point left, until we find a character different from the first one.
-        for (; start[*second] == start[*first] && *second; --(*second)) {}
         // Pivot the middle point right, until we find a character different from the first one.
         for (; start[*second] == start[*first] && *second + 1 < *third; ++(*second)) {}
         // Pivot the third (last) point left, until we find a different character.
         for (; (start[*third] == start[*second] || start[*third] == start[*first]) && *third > (*second + 1);
              --(*third)) {}
     }
+
+    // TODO: Investigate alternative strategies for long needles.
+    // On very long needles we have the luxury to choose!
+    // Often dealing with UTF8, we will likely benfit from shifting the first and second characters
+    // further to the right, to achieve not only uniqness within the needle, but also avoid common
+    // rune prefixes of 2-, 3-, and 4-byte codes.
+    if (length > 8) {
+        // Pivot the first and second points right, until we find a character, that:
+        // > is different from others.
+        // > doesn't start with 0b'110x'xxxx - only 5 bits of relevant info.
+        // > doesn't start with 0b'1110'xxxx - only 4 bits of relevant info.
+        // > doesn't start with 0b'1111'0xxx - only 3 bits of relevant info.
+        //
+        // So we are practically searching for byte values that start with 0b0xxx'xxxx or 0b'10xx'xxxx.
+        // Meaning they fall in the range [0, 127] and [128, 191], in other words any unsigned int up to 191.
+        sz_u8_t const *start_u8 = (sz_u8_t const *)start;
+        sz_size_t vibrant_first = *first, vibrant_second = *second, vibrant_third = *third;
+
+        // Let's begin with the seccond character, as the termination criterea there is more obvious
+        // and we may end up with more variants to check for the first candidate.
+        for (; (start_u8[vibrant_second] > 191 || start_u8[vibrant_second] == start_u8[vibrant_third]) &&
+               (vibrant_second + 1 < vibrant_third);
+             ++vibrant_second) {}
+
+        // Now check if we've indeed found a good candidate or should revert the `vibrant_second` to `second`.
+        if (start_u8[vibrant_second] < 191) { *second = vibrant_second; }
+        else { vibrant_second = *second; }
+
+        // Now check the first character.
+        for (; (start_u8[vibrant_first] > 191 || start_u8[vibrant_first] == start_u8[vibrant_second] ||
+                start_u8[vibrant_first] == start_u8[vibrant_third]) &&
+               (vibrant_first + 1 < vibrant_second);
+             ++vibrant_first) {}
+
+        // Now check if we've indeed found a good candidate or should revert the `vibrant_first` to `first`.
+        // We don't need to shift the third one when dealing with texts as the last byte of the text is
+        // also the last byte of a rune and contains the most information.
+        if (start_u8[vibrant_first] < 191) { *first = vibrant_first; }
+    }
 }
 
 #pragma GCC visibility pop
@@ -2308,7 +2345,7 @@ SZ_INTERNAL void _sz_extract_utf8_rune(sz_cptr_t utf8, sz_rune_t *code, sz_rune_
     sz_rune_t ch;
     sz_rune_length_t ch_length;
 
-    // TODO: This can be made entirely branchless uing 32-bit SWAR.
+    // TODO: This can be made entirely branchless using 32-bit SWAR.
     if (leading_byte < 0x80) {
         // Single-byte rune (0xxxxxxx)
         ch = leading_byte;
@@ -2346,7 +2383,7 @@ SZ_INTERNAL void _sz_extract_utf8_rune(sz_cptr_t utf8, sz_rune_t *code, sz_rune_
 
 /**
  *  @brief  Exports a UTF8 string into a UTF32 buffer.
- *          ! The result is undefined id the UTF8 string is corrputed.
+ *          ! The result is undefined id the UTF8 string is corrupted.
  *  @return The length in the number of codepoints.
  */
 SZ_INTERNAL sz_size_t _sz_export_utf8_to_utf32(sz_cptr_t utf8, sz_size_t utf8_length, sz_rune_t *utf32) {
@@ -2425,7 +2462,7 @@ SZ_INTERNAL sz_size_t _sz_edit_distance_wagner_fisher_serial( //
     _distance_t *current_distances = previous_distances + n;                                                          \
     /*  Initialize the first row of the Levenshtein matrix with `iota`-style arithmetic progression. */               \
     for (_distance_t idx_shorter = 0; idx_shorter != n; ++idx_shorter) previous_distances[idx_shorter] = idx_shorter; \
-    /* The main loop of the lagorithm with quadratic complexity. */                                                   \
+    /* The main loop of the algorithm with quadratic complexity. */                                                   \
     for (_distance_t idx_longer = 0; idx_longer != longer_length; ++idx_longer) {                                     \
         _char_t const longer_char = longer_chars[idx_longer];                                                         \
         /* Using pure pointer arithmetic is faster than iterating with an index. */                                   \
@@ -3399,7 +3436,7 @@ SZ_PUBLIC void sz_sort_recursion( //
     //        if (!(sequence->order[i] & mask)) sz_u64_swap(sequence->order + i, sequence->order + split), ++split;
     //
     // This, however, doesn't take into account the high relative cost of writes and swaps.
-    // To cercumvent that, we can first count the total number entries to be mapped into either part.
+    // To circumvent that, we can first count the total number entries to be mapped into either part.
     // And then walk through both parts, swapping the entries that are in the wrong part.
     // This would often lead to ~15% performance gain.
     sz_size_t count_with_bit_set = 0;
@@ -4839,32 +4876,74 @@ SZ_PUBLIC sz_cptr_t sz_find_neon(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, s
     if (h_length < n_length || !n_length) return SZ_NULL_CHAR;
     if (n_length == 1) return sz_find_byte_neon(h, h_length, n);
 
-    // Pick the parts of the needle that are worth comparing.
-    sz_size_t offset_first, offset_mid, offset_last;
-    _sz_locate_needle_anomalies(n, n_length, &offset_first, &offset_mid, &offset_last);
-
-    // Broadcast those characters into SIMD registers.
-    sz_u64_t matches;
-    sz_u128_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec, matches_vec;
-    n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_first]);
-    n_mid_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_mid]);
-    n_last_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_last]);
-
     // Scan through the string.
-    for (; h_length >= n_length + 16; h += 16, h_length -= 16) {
-        h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_first));
-        h_mid_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_mid));
-        h_last_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_last));
-        matches_vec.u8x16 = vandq_u8(                           //
-            vandq_u8(                                           //
-                vceqq_u8(h_first_vec.u8x16, n_first_vec.u8x16), //
-                vceqq_u8(h_mid_vec.u8x16, n_mid_vec.u8x16)),
-            vceqq_u8(h_last_vec.u8x16, n_last_vec.u8x16));
-        matches = vreinterpretq_u8_u4(matches_vec.u8x16);
-        while (matches) {
-            int potential_offset = sz_u64_ctz(matches) / 4;
-            if (sz_equal(h + potential_offset, n, n_length)) return h + potential_offset;
-            matches &= matches - 1;
+    // Assuming how tiny the Arm NEON registers are, we should avoid internal branches at all costs.
+    // That's why, for smaller needles, we use different loops.
+    if (n_length == 2) {
+        // Broadcast needle characters into SIMD registers.
+        sz_u64_t matches;
+        sz_u128_vec_t h_first_vec, h_last_vec, n_first_vec, n_last_vec, matches_vec;
+        // Dealing with 16-bit values, we can load 2 registers at a time and compare 31 possible offsets
+        // in a single loop iteration.
+        n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[0]);
+        n_last_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[1]);
+        for (; h_length >= 17; h += 16, h_length -= 16) {
+            h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + 0));
+            h_last_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + 1));
+            matches_vec.u8x16 =
+                vandq_u8(vceqq_u8(h_first_vec.u8x16, n_first_vec.u8x16), vceqq_u8(h_last_vec.u8x16, n_last_vec.u8x16));
+            matches = vreinterpretq_u8_u4(matches_vec.u8x16);
+            if (matches) return h + sz_u64_ctz(matches) / 4;
+        }
+    }
+    else if (n_length == 3) {
+        // Broadcast needle characters into SIMD registers.
+        sz_u64_t matches;
+        sz_u128_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec, matches_vec;
+        // Comparing 24-bit values is a bumer. Being lazy, I went with the same approach
+        // as when searching for string over 4 characters long. I only avoid the last comparison.
+        n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[0]);
+        n_mid_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[1]);
+        n_last_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[2]);
+        for (; h_length >= 18; h += 16, h_length -= 16) {
+            h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + 0));
+            h_mid_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + 1));
+            h_last_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + 2));
+            matches_vec.u8x16 = vandq_u8(                           //
+                vandq_u8(                                           //
+                    vceqq_u8(h_first_vec.u8x16, n_first_vec.u8x16), //
+                    vceqq_u8(h_mid_vec.u8x16, n_mid_vec.u8x16)),
+                vceqq_u8(h_last_vec.u8x16, n_last_vec.u8x16));
+            matches = vreinterpretq_u8_u4(matches_vec.u8x16);
+            if (matches) return h + sz_u64_ctz(matches) / 4;
+        }
+    }
+    else {
+        // Pick the parts of the needle that are worth comparing.
+        sz_size_t offset_first, offset_mid, offset_last;
+        _sz_locate_needle_anomalies(n, n_length, &offset_first, &offset_mid, &offset_last);
+        // Broadcast those characters into SIMD registers.
+        sz_u64_t matches;
+        sz_u128_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec, matches_vec;
+        n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_first]);
+        n_mid_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_mid]);
+        n_last_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_last]);
+        // Walk through the string.
+        for (; h_length >= n_length + 16; h += 16, h_length -= 16) {
+            h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_first));
+            h_mid_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_mid));
+            h_last_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_last));
+            matches_vec.u8x16 = vandq_u8(                           //
+                vandq_u8(                                           //
+                    vceqq_u8(h_first_vec.u8x16, n_first_vec.u8x16), //
+                    vceqq_u8(h_mid_vec.u8x16, n_mid_vec.u8x16)),
+                vceqq_u8(h_last_vec.u8x16, n_last_vec.u8x16));
+            matches = vreinterpretq_u8_u4(matches_vec.u8x16);
+            while (matches) {
+                int potential_offset = sz_u64_ctz(matches) / 4;
+                if (sz_equal(h + potential_offset, n, n_length)) return h + potential_offset;
+                matches &= matches - 1;
+            }
         }
     }
 
diff --git a/include/stringzilla/stringzilla.hpp b/include/stringzilla/stringzilla.hpp
index d459236c..0c8e1bd1 100644
--- a/include/stringzilla/stringzilla.hpp
+++ b/include/stringzilla/stringzilla.hpp
@@ -45,8 +45,10 @@
 #endif
 
 #if !SZ_AVOID_STL
+#include <array>
 #include <bitset>
 #include <string>
+#include <vector>
 #if SZ_DETECT_CPP_17 && __cpp_lib_string_view
 #include <string_view>
 #endif
@@ -54,6 +56,7 @@
 
 #include <cassert>   // `assert`
 #include <cstddef>   // `std::size_t`
+#include <cstdint>   // `std::int8_t`
 #include <iosfwd>    // `std::basic_ostream`
 #include <stdexcept> // `std::out_of_range`
 #include <utility>   // `std::swap`
@@ -1037,8 +1040,6 @@ class reversed_iterator_for {
 
 /**
  *  @brief  An "expression template" for lazy concatenation of strings using the `operator|`.
- *
- *  TODO: Ensure eqnership passing and move semantics are preserved.
  */
 template <typename first_type, typename second_type>
 struct concatenation {
@@ -1860,7 +1861,7 @@ class basic_string_slice {
  *
  *  Functions defined for `basic_string`, but not present in `basic_string_slice`:
  *      * `replace`, `insert`, `erase`, `append`, `push_back`, `pop_back`, `resize`, `shrink_to_fit`... from STL,
- *      * `try_` exception-free "try" operations that returning non-zero values on succces,
+ *      * `try_` exception-free "try" operations that returning non-zero values on success,
  *      * `replace_all` and `erase_all` similar to Boost,
  *      * `edit_distance` - Levenshtein distance computation reusing the allocator,
  *      * `randomize`, `random` - for fast random string generation.
@@ -1874,7 +1875,7 @@ class basic_string_slice {
  *  Default constructor is `constexpr`. Move constructor and move assignment operator are `noexcept`.
  *  Copy constructor and copy assignment operator are not! They may throw `std::bad_alloc` if the memory
  *  allocation fails. Similar to STL `std::out_of_range` if the position argument to some of the functions
- *  is out of bounds. Same as with STL, the bound checks are often assymetric, so pay attention to docs.
+ *  is out of bounds. Same as with STL, the bound checks are often asymmetric, so pay attention to docs.
  *  If exceptions are disabled, on failure, `std::terminate` is called.
  */
 template <typename char_type_, typename allocator_type_ = std::allocator<char_type_>>
@@ -2791,8 +2792,8 @@ class basic_string {
      *  @throw  `std::length_error` if the string is too long.
      *  @throw  `std::bad_alloc` if the allocation fails.
      */
-    iterator insert(const_iterator it, std::initializer_list<char_type> ilist) noexcept(false) {
-        return insert(it, ilist.begin(), ilist.end());
+    iterator insert(const_iterator it, std::initializer_list<char_type> list) noexcept(false) {
+        return insert(it, list.begin(), list.end());
     }
 
     /**
@@ -2951,8 +2952,8 @@ class basic_string {
      *  @see    `try_replace` for a cleaner exception-less alternative.
      */
     basic_string &replace(const_iterator first, const_iterator last,
-                          std::initializer_list<char_type> ilist) noexcept(false) {
-        return replace(first, last, ilist.begin(), ilist.end());
+                          std::initializer_list<char_type> list) noexcept(false) {
+        return replace(first, last, list.begin(), list.end());
     }
 
     /**
@@ -3031,8 +3032,8 @@ class basic_string {
      *  @throw  `std::bad_alloc` if the allocation fails.
      *  @see    `try_assign` for a cleaner exception-less alternative.
      */
-    basic_string &assign(std::initializer_list<char_type> ilist) noexcept(false) {
-        return assign(ilist.begin(), ilist.end());
+    basic_string &assign(std::initializer_list<char_type> list) noexcept(false) {
+        return assign(list.begin(), list.end());
     }
 
     /**
@@ -3357,7 +3358,7 @@ bool basic_string<char_type_, allocator_>::try_replace_all_(pattern_type pattern
 
     // 2. The pattern is longer than the replacement. We need to compact the strings.
     else if (matcher.needle_length() > replacement.length()) {
-        // Dealing with shorter replacements, we will avoid memory allocations, but we can also mimnimize the number
+        // Dealing with shorter replacements, we will avoid memory allocations, but we can also minimize the number
         // of `memmove`-s, by keeping one more iterator, pointing to the end of the last compacted area.
         // Having the split-ranges, however, we reuse their logic.
         using splits_type = range_splits<string_view, matcher_type>;
diff --git a/python/lib.c b/python/lib.c
index d01cce62..5acbb752 100644
--- a/python/lib.c
+++ b/python/lib.c
@@ -35,6 +35,7 @@ typedef SSIZE_T ssize_t;
 
 #include <Python.h> // Core CPython interfaces
 
+#include <stdio.h>  // `fopen`
 #include <string.h> // `memset`, `memcpy`
 
 #include <stringzilla/stringzilla.h>
@@ -827,6 +828,113 @@ static PyObject *Str_richcompare(PyObject *self, PyObject *other, int op) {
     }
 }
 
+/**
+ *  @brief  Saves a StringZilla string to disk.
+ */
+static PyObject *Str_write_to(PyObject *self, PyObject *args, PyObject *kwargs) {
+
+    int is_member = self != NULL && PyObject_TypeCheck(self, &StrType);
+    Py_ssize_t nargs = PyTuple_Size(args);
+    if (nargs != !is_member + 1) {
+        PyErr_SetString(PyExc_TypeError, "Invalid number of arguments");
+        return NULL;
+    }
+
+    PyObject *text_obj = is_member ? self : PyTuple_GET_ITEM(args, 0);
+    PyObject *path_obj = PyTuple_GET_ITEM(args, !is_member + 0);
+
+    // Parse keyword arguments
+    if (kwargs) {
+        PyErr_Format(PyExc_TypeError, "Got an unexpected keyword argument");
+        return NULL;
+    }
+
+    sz_string_view_t text;
+    sz_string_view_t path;
+
+    // Validate and convert `text` and `path`
+    if (!export_string_like(text_obj, &text.start, &text.length) ||
+        !export_string_like(path_obj, &path.start, &path.length)) {
+        PyErr_SetString(PyExc_TypeError, "Text and path must be string-like");
+        return NULL;
+    }
+
+    // There is a chance, the path isn't NULL-terminated, so copy it to a new buffer.
+    // Many OSes have fairly low limit for the maximum path length.
+    // On Windows its 260, but up to __around__ 32,767 characters are supported in extended API.
+    // But it's better to be safe than sorry and use malloc :)
+    //
+    // https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
+    // https://doc.owncloud.com/server/next/admin_manual/troubleshooting/path_filename_length.html
+    char *path_buffer = (char *)malloc(path.length + 1);
+    if (path_buffer == NULL) {
+        PyErr_SetString(PyExc_MemoryError, "Unable to allocate memory for the path");
+        return NULL;
+    }
+    memcpy(path_buffer, path.start, path.length);
+
+    FILE *file_pointer = fopen(path_buffer, "wb");
+    if (file_pointer == NULL) {
+        PyErr_SetFromErrnoWithFilename(PyExc_OSError, path_buffer);
+        free(path_buffer);
+        return NULL;
+    }
+
+    setbuf(file_pointer, NULL); // Set the stream to unbuffered
+    int status = fwrite(text.start, 1, text.length, file_pointer);
+    if (status != text.length) {
+        PyErr_SetFromErrnoWithFilename(PyExc_OSError, path_buffer);
+        free(path_buffer);
+        fclose(file_pointer);
+        return NULL;
+    }
+
+    free(path_buffer);
+    fclose(file_pointer);
+    Py_RETURN_NONE;
+}
+
+/**
+ *  @brief  Given a native StringZilla string, suggests it's offset within another native StringZilla string.
+ *          Very practical when dealing with large files.
+ *  @return Unsigned integer on success.
+ */
+static PyObject *Str_offset_within(PyObject *self, PyObject *args, PyObject *kwargs) {
+
+    int is_member = self != NULL && PyObject_TypeCheck(self, &StrType);
+    Py_ssize_t nargs = PyTuple_Size(args);
+    if (nargs != !is_member + 1) {
+        PyErr_SetString(PyExc_TypeError, "Invalid number of arguments");
+        return NULL;
+    }
+
+    PyObject *slice_obj = is_member ? self : PyTuple_GET_ITEM(args, 0);
+    PyObject *text_obj = PyTuple_GET_ITEM(args, !is_member + 0);
+
+    // Parse keyword arguments
+    if (kwargs) {
+        PyErr_Format(PyExc_TypeError, "Got an unexpected keyword argument");
+        return NULL;
+    }
+
+    sz_string_view_t text;
+    sz_string_view_t slice;
+
+    // Validate and convert `text` and `slice`
+    if (!export_string_like(text_obj, &text.start, &text.length) ||
+        !export_string_like(slice_obj, &slice.start, &slice.length)) {
+        PyErr_SetString(PyExc_TypeError, "Text and slice must be string-like");
+        return NULL;
+    }
+
+    if (slice.start < text.start || slice.start + slice.length > text.start + text.length) {
+        PyErr_SetString(PyExc_ValueError, "The slice is not within the text bounds");
+        return NULL;
+    }
+
+    return PyLong_FromSize_t((size_t)(slice.start - text.start));
+}
+
 /**
  *  @brief  Implementation function for all search-like operations, parameterized by a function callback.
  *  @return 1 on success, 0 on failure.
@@ -1531,12 +1639,10 @@ static PyObject *Str_split(PyObject *self, PyObject *args, PyObject *kwargs) {
 
     // Validate and convert `separator`
     if (separator_obj) {
-        Py_ssize_t len;
-        if (!export_string_like(separator_obj, &separator.start, &len)) {
+        if (!export_string_like(separator_obj, &separator.start, &separator.length)) {
             PyErr_SetString(PyExc_TypeError, "The separator argument must be string-like");
             return NULL;
         }
-        separator.length = (size_t)len;
     }
     else {
         separator.start = " ";
@@ -1726,6 +1832,11 @@ static PyMethodDef Str_methods[] = {
     {"find_last_not_of", Str_find_last_not_of, SZ_METHOD_FLAGS,
      "Finds the last occurrence of a character not present in another string."},
 
+    // Dealing with larger-than-memory datasets
+    {"offset_within", Str_offset_within, SZ_METHOD_FLAGS,
+     "Return the raw byte offset of one binary string within another."},
+    {"write_to", Str_write_to, SZ_METHOD_FLAGS, "Return the raw byte offset of one binary string within another."},
+
     {NULL, NULL, 0, NULL}};
 
 static PyTypeObject StrType = {
diff --git a/setup.py b/setup.py
index 0a171974..8ab44991 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
 import os
 import sys
 import platform
-from setuptools import setup, Extension
+from setuptools import setup, find_packages, Extension
 from typing import List, Tuple
 import sysconfig
 import glob
@@ -159,10 +159,16 @@ def windows_settings() -> Tuple[List[str], List[str], List[Tuple[str]]]:
         "License :: OSI Approved :: Apache Software License",
         "Programming Language :: C++",
         "Programming Language :: Python :: 3 :: Only",
+        "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
+        "Programming Language :: Python :: 3.11",
+        "Programming Language :: Python :: 3.12",
         "Programming Language :: Python :: Implementation :: CPython",
-        "Operating System :: MacOS",
-        "Operating System :: Unix",
-        "Operating System :: Microsoft :: Windows",
+        "Programming Language :: Python :: Implementation :: PyPy",
+        "Operating System :: OS Independent",
         "Topic :: File Formats",
         "Topic :: Internet :: Log Analysis",
         "Topic :: Scientific/Engineering :: Information Analysis",
@@ -173,4 +179,11 @@ def windows_settings() -> Tuple[List[str], List[str], List[Tuple[str]]]:
     include_dirs=[],
     setup_requires=[],
     ext_modules=ext_modules,
+    packages=find_packages(),
+    entry_points={
+        "console_scripts": [
+            "sz_split=cli.split:main",
+            "sz_wc=cli.wc:main",
+        ],
+    },
 )


Test patch:
diff --git a/scripts/test.cpp b/scripts/test.cpp
index bd831d9a..f8d4af25 100644
--- a/scripts/test.cpp
+++ b/scripts/test.cpp
@@ -1,6 +1,19 @@
 #undef NDEBUG      // Enable all assertions
 #include <cassert> // assertions
 
+// Overload the following with caution.
+// Those parameters must never be explicitly set during releases,
+// but they come handy during development, if you want to validate
+// different ISA-specific implementations.
+// #define SZ_USE_X86_AVX2 0
+// #define SZ_USE_X86_AVX512 0
+// #define SZ_USE_ARM_NEON 0
+// #define SZ_USE_ARM_SVE 0
+#define SZ_DEBUG 1 // Enforce aggressive logging for this unit.
+
+// Put this at the top to make sure it pulls all the right dependencies
+#include <stringzilla/stringzilla.hpp>
+
 #if defined(__SANITIZE_ADDRESS__)
 #include <sanitizer/asan_interface.h> // ASAN
 #endif
@@ -14,19 +27,8 @@
 #include <sstream>   // `std::ostringstream`
 #include <vector>    // `std::vector`
 
-// Overload the following with caution.
-// Those parameters must never be explicitly set during releases,
-// but they come handy during development, if you want to validate
-// different ISA-specific implementations.
-// #define SZ_USE_X86_AVX2 0
-// #define SZ_USE_X86_AVX512 0
-// #define SZ_USE_ARM_NEON 0
-// #define SZ_USE_ARM_SVE 0
-#define SZ_DEBUG 1 // Enforce aggressive logging for this unit.
-
-#include <string>                      // Baseline
-#include <string_view>                 // Baseline
-#include <stringzilla/stringzilla.hpp> // Contender
+#include <string>      // Baseline
+#include <string_view> // Baseline
 
 #if !SZ_DETECT_CPP_11
 #error "This test requires C++11 or later."
@@ -236,6 +238,8 @@ static void test_api_readonly() {
 
     assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find("xy") == 23);  // first match
     assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find("XY") == 49);  // first match
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find("yz") == 24);  // first match
+    assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").find("YZ") == 50);  // first match
     assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").rfind("xy") == 23); // last match
     assert(str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-").rfind("XY") == 49); // last match