stringzilla


Namestringzilla JSON
Version 3.8.4 PyPI version JSON
download
home_pageNone
SummarySIMD-accelerated string search, sort, hashes, fingerprints, & edit distances
upload_time2024-04-27 17:52:43
maintainerNone
docs_urlNone
authorAsh Vardanian
requires_pythonNone
licenseApache-2.0
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # StringZilla ๐Ÿฆ–

![StringZilla banner](https://github.com/ashvardanian/ashvardanian/blob/master/repositories/StringZilla.png?raw=true)

The world wastes a minimum of $100M annually due to inefficient string operations.
A typical codebase processes strings character by character, resulting in too many branches and data-dependencies, neglecting 90% of modern CPU's potential.
LibC is different.
It attempts to leverage SIMD instructions to boost some operations, and is often used by higher-level languages, runtimes, and databases.
But it isn't perfect.
1๏ธโƒฃ First, even on common hardware, including over a billion 64-bit ARM CPUs, common functions like `strstr` and `memmem` only achieve 1/3 of the CPU's thoughput.
2๏ธโƒฃ Second, SIMD coverage is inconsistent: acceleration in forward scans does not guarantee speed in the reverse-order search.
3๏ธโƒฃ At last, most high-level languages can't always use LibC, as the strings are often not NULL-terminated or may contain the Unicode "Zero" character in the middle of the string.
That's why StringZilla was created.
To provide predictably high performance, portable to any modern platform, operating system, and programming language.

[![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&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.
It is up to __10x faster than the default and even other SIMD-accelerated string libraries__ in C, C++, Python, and other languages, while covering broad functionality.
It __accelerates exact and fuzzy string matching, edit distance computations, sorting, lazily-evaluated ranges to avoid memory allocations, and even random-string generators__.

[faq-simd]: https://en.wikipedia.org/wiki/Single_instruction,_multiple_data
[faq-swar]: https://en.wikipedia.org/wiki/SWAR

- ๐Ÿ‚ __[C](#Basic-Usage-with-C-99-and-Newer) :__ Upgrade LibC's `<string.h>` to `<stringzilla.h>`  in C 99
- ๐Ÿ‰ __[C++](#basic-usage-with-c-11-and-newer):__ Upgrade STL's `<string>` to `<stringzilla.hpp>` in C++ 11
- ๐Ÿ __[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 with `sz_` prefix
- ๐Ÿ“š Researcher? Jump to [Algorithms & Design Decisions](#algorithms--design-decisions-๐Ÿ“š)
- ๐Ÿ’ก Thinking to contribute? Look for ["good first issues"][first-issues]
- ๐Ÿค And check the [guide](https://github.com/ashvardanian/StringZilla/blob/main/CONTRIBUTING.md) to setup the environment
- Want more bindings or features? Let [me](https://github.com/ashvardanian) know!

[faq-shell]: https://github.com/ashvardanian/StringZilla/blob/main/cli/README.md
[first-issues]: https://github.com/ashvardanian/StringZilla/issues

__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/).
- For software engineers optimizing strings in their apps and services.
- For bioinformaticians and search engineers looking for edit-distances for [USearch](https://github.com/unum-cloud/usearch).
- For [DBMS][faq-dbms] devs, optimizing `LIKE`, `ORDER BY`, and `GROUP BY` operations.
- For hardware designers, needing a SWAR baseline for strings-processing functionality.
- For students studying SIMD/SWAR applications to non-data-parallel operations.

[faq-dbms]: https://en.wikipedia.org/wiki/Database

## Performance

<table style="width: 100%; text-align: center; table-layout: fixed;">
  <colgroup>
    <col style="width: 25%;">
    <col style="width: 25%;">
    <col style="width: 25%;">
    <col style="width: 25%;">
  </colgroup>
  <tr>
    <th align="center">C</th>
    <th align="center">C++</th>
    <th align="center">Python</th>
    <th align="center">StringZilla</th>
  </tr>
  <!-- Substrings, normal order -->
  <tr>
    <td colspan="4" align="center">find the first occurrence of a random word from text, โ‰… 5 bytes long</td>
  </tr>
  <tr>
    <td align="center">
      <code>strstr</code> <sup>1</sup><br/>
      <span style="color:#ABABAB;">x86:</span> <b>7.4</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>2.0</b> GB/s
    </td>
    <td align="center">
      <code>.find</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>2.9</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>1.6</b> GB/s
    </td>
    <td align="center">
      <code>.find</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>1.1</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>0.6</b> GB/s
    </td>
    <td align="center">
      <code>sz_find</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>10.6</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>7.1</b> GB/s
    </td>
  </tr>
  <!-- Substrings, reverse order -->
  <tr>
    <td colspan="4" align="center">find the last occurrence of a random word from text, โ‰… 5 bytes long</td>
  </tr>
  <tr>
    <td align="center">โšช</td>
    <td align="center">
      <code>.rfind</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>0.5</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>0.4</b> GB/s
    </td>
    <td align="center">
      <code>.rfind</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>0.9</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>0.5</b> GB/s
    </td>
    <td align="center">
      <code>sz_rfind</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>10.8</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>6.7</b> GB/s
    </td>
  </tr>
  <!-- Characters, normal order -->
  <tr>
    <td colspan="4" align="center">split lines separated by <code>\n</code> or <code>\r</code> <sup>2</sup></td>
  </tr>
  <tr>
    <td align="center">
      <code>strcspn</code> <sup>1</sup><br/>
      <span style="color:#ABABAB;">x86:</span> <b>5.42</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>2.19</b> GB/s
    </td>
    <td align="center">
      <code>.find_first_of</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>0.59</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>0.46</b> GB/s
    </td>
    <td align="center">
      <code>re.finditer</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>0.06</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>0.02</b> GB/s
    </td>
    <td align="center">
      <code>sz_find_charset</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>4.08</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>3.22</b> GB/s
    </td>
  </tr>
  <!-- Characters, reverse order -->
  <tr>
    <td colspan="4" align="center">find the last occurrence of any of 6 whitespaces <sup>2</sup></td>
  </tr>
  <tr>
    <td align="center">โšช</td>
    <td align="center">
      <code>.find_last_of</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>0.25</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>0.25</b> GB/s
    </td>
    <td align="center">โšช</td>
    <td align="center">
      <code>sz_rfind_charset</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>0.43</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>0.23</b> GB/s
    </td>
  </tr>
  <!-- Random Generation -->
  <tr>
    <td colspan="4" align="center">Random string from a given alphabet, 20 bytes long <sup>5</sup></td>
  </tr>
  <tr>
    <td align="center">
      <code>rand() % n</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>18.0</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>9.4</b> MB/s
    </td>
    <td align="center">
      <code>uniform_int_distribution</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>47.2</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>20.4</b> MB/s
    </td>
    <td align="center">
      <code>join(random.choices(...))</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>13.3</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>5.9</b> MB/s
    </td>
    <td align="center">
      <code>sz_generate</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>56.2</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>25.8</b> MB/s
    </td>
  </tr>
  <!-- Sorting -->
  <tr>
    <td colspan="4" align="center">Get sorted order, โ‰… 8 million English words <sup>6</sup></td>
  </tr>
  <tr>
    <td align="center">
      <code>qsort_r</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>3.55</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>5.77</b> s
    </td>
    <td align="center">
      <code>std::sort</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>2.79</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>4.02</b> s
    </td>
    <td align="center">
      <code>numpy.argsort</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>7.58</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>13.00</b> s
    </td>
    <td align="center">
      <code>sz_sort</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>1.91</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>2.37</b> s
    </td>
  </tr>
  <!-- Edit Distance -->
  <tr>
    <td colspan="4" align="center">Levenshtein edit distance, โ‰… 5 bytes long</td>
  </tr>
  <tr>
    <td align="center">โšช</td>
    <td align="center">โšช</td>
    <td align="center">
      via <code>jellyfish</code> <sup>3</sup><br/>
      <span style="color:#ABABAB;">x86:</span> <b>1,550</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>2,220</b> ns
    </td>
    <td align="center">
      <code>sz_edit_distance</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>99</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>180</b> ns
    </td>
  </tr>
  <!-- Alignment Score -->
  <tr>
    <td colspan="4" align="center">Needleman-Wunsch alignment scores, โ‰… 10 K aminoacids long</td>
  </tr>
  <tr>
    <td align="center">โšช</td>
    <td align="center">โšช</td>
    <td align="center">
      via <code>biopython</code> <sup>4</sup><br/>
      <span style="color:#ABABAB;">x86:</span> <b>257</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>367</b> ms
    </td>
    <td align="center">
      <code>sz_alignment_score</code><br/>
      <span style="color:#ABABAB;">x86:</span> <b>73</b> &centerdot;
      <span style="color:#ABABAB;">arm:</span> <b>177</b> ms
    </td>
  </tr>
</table>

StringZilla has a lot of functionality, most of which is covered by benchmarks across C, C++, Python and other languages.
You can find those in the `./scripts` directory, with usage notes listed in the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
Notably, if the CPU supports misaligned loads, even the 64-bit SWAR backends are faster than either standard library.

> Most benchmarks were conducted on a 1 GB English text corpus, with an average word length of 5 characters.
> The code was compiled with GCC 12, using `glibc` v2.35.
> The benchmarks performed on Arm-based Graviton3 AWS `c7g` instances and `r7iz` Intel Sapphire Rapids.
> Most modern Arm-based 64-bit CPUs will have similar relative speedups.
> Variance withing x86 CPUs will be larger.
> <sup>1</sup> Unlike other libraries, LibC requires strings to be NULL-terminated.
> <sup>2</sup> Six whitespaces in the ASCII set are: ` \t\n\v\f\r`. Python's and other standard libraries have specialized functions for those.
> <sup>3</sup> Most Python libraries for strings are also implemented in C.
> <sup>4</sup> Unlike the rest of BioPython, the alignment score computation is [implemented in C](https://github.com/biopython/biopython/blob/master/Bio/Align/_pairwisealigner.c).
> <sup>5</sup> All modulo operations were conducted with `uint8_t` to allow compilers more optimization opportunities.
> The C++ STL and StringZilla benchmarks used a 64-bit [Mersenne Twister][faq-mersenne-twister] as the generator.
> For C, C++, and StringZilla, an in-place update of the string was used.
> In Python every string had to be allocated as a new object, which makes it less fair.
> <sup>6</sup> Contrary to the popular opinion, Python's default `sorted` function works faster than the C and C++ standard libraries.
> That holds for large lists or tuples of strings, but fails as soon as you need more complex logic, like sorting dictionaries by a string key, or producing the "sorted order" permutation.
> The latter is very common in database engines and is most similar to `numpy.argsort`.
> Current StringZilla solution can be at least 4x faster without loss of generality.

[faq-mersenne-twister]: https://en.wikipedia.org/wiki/Mersenne_Twister

## Functionality

StringZilla is compatible with most modern CPUs, and provides a broad range of functionality.

- [x] works on both Little-Endian and Big-Endian architectures.
- [x] works on 32-bit and 64-bit hardware architectures.
- [x] compatible with ASCII and UTF-8 encoding.

Not all features are available across all bindings.
Consider contributing, if you need a feature that's not yet implemented.

|                                | Maturity | C 99  | C++ 11 | Python | Swift | Rust  |
| :----------------------------- | :------: | :---: | :----: | :----: | :---: | :---: |
| Substring Search               |    ๐ŸŒณ     |   โœ…   |   โœ…    |   โœ…    |   โœ…   |   โœ…   |
| Character Set Search           |    ๐ŸŒณ     |   โœ…   |   โœ…    |   โœ…    |   โœ…   |   โœ…   |
| Edit Distances                 |    ๐Ÿง     |   โœ…   |   โœ…    |   โœ…    |   โœ…   |   โšช   |
| Small String Class             |    ๐Ÿง     |   โœ…   |   โœ…    |   โŒ    |   โŒ   |   โšช   |
| Sorting & Sequence Operations  |    ๐Ÿšง     |   โœ…   |   โœ…    |   โœ…    |   โšช   |   โšช   |
| Lazy Ranges, Compressed Arrays |    ๐Ÿง     |   โšช   |   โœ…    |   โœ…    |   โšช   |   โšช   |
| Hashes & Fingerprints          |    ๐Ÿšง     |   โœ…   |   โœ…    |   โšช    |   โšช   |   โšช   |

> ๐ŸŒณ parts are used in production.
> ๐Ÿง parts are in beta.
> ๐Ÿšง parts are under active development, and are likely to break in subsequent releases.
> โœ… are implemented.
> โšช are considered.
> โŒ are not intended.

## Quick Start: Python ๐Ÿ

Python bindings are available on PyPI, and can be installed with `pip`.
You can immediately check the installed version and the used hardware capabilities with following commands:

```bash
pip install stringzilla
python -c "import stringzilla; print(stringzilla.__version__)"
python -c "import stringzilla; print(stringzilla.__capabilities__)"
```

### Basic Usage

If you've ever used the Python `str`, `bytes`, `bytearray`, `memoryview` class, you'll know what to expect.
StringZilla's `Str` class is a hybrid of those two, providing `str`-like interface to byte-arrays.

```python
from stringzilla import Str, File

text_from_str = Str('some-string') # no copies, just a view
text_from_file = Str(File('some-file.txt')) # memory-mapped file
```

The `File` class memory-maps a file from persistent memory without loading its copy into RAM.
The contents of that file would remain immutable, and the mapping can be shared by multiple Python processes simultaneously.
A standard dataset pre-processing use case would be to map a sizeable textual dataset like Common Crawl into memory, spawn child processes, and split the job between them.

### Basic Operations

- Length: `len(text) -> int`
- Indexing: `text[42] -> str`
- Slicing: `text[42:46] -> Str`
- Substring check: `'substring' in text -> bool`
- Hashing: `hash(text) -> int`
- String conversion: `str(text) -> str`

### Advanced Operations

```py
import sys

x: bool = text.contains('substring', start=0, end=sys.maxsize)
x: int = text.find('substring', start=0, end=sys.maxsize)
x: int = text.count('substring', start=0, end=sys.maxsize, allowoverlap=False)
x: str = text.decode(encoding='utf-8', errors='strict')
x: Strs = text.split(separator=' ', maxsplit=sys.maxsize, keepseparator=False)
x: Strs = text.rsplit(separator=' ', maxsplit=sys.maxsize, keepseparator=False)
x: Strs = text.splitlines(keeplinebreaks=False, maxsplit=sys.maxsize)
```

It's important to note, that the last function behavior is slightly different from Python's `str.splitlines`.
The [native version][faq-splitlines] matches `\n`, `\r`, `\v` or `\x0b`, `\f` or `\x0c`, `\x1c`, `\x1d`, `\x1e`, `\x85`, `\r\n`, `\u2028`, `\u2029`, including 3x two-bytes-long runes.
The StringZilla version matches only `\n`, `\v`, `\f`, `\r`, `\x1c`, `\x1d`, `\x1e`, `\x85`, avoiding two-byte-long runes.

[faq-splitlines]: https://docs.python.org/3/library/stdtypes.html#str.splitlines

### Character Set Operations

Python strings don't natively support character set operations.
This forces people to use regular expressions, which are slow and hard to read.
To avoid the need for `re.finditer`, StringZilla provides the following interfaces:

```py
x: int = text.find_first_of('chars', start=0, end=sys.maxsize)
x: int = text.find_last_of('chars', start=0, end=sys.maxsize)
x: int = text.find_first_not_of('chars', start=0, end=sys.maxsize)
x: int = text.find_last_not_of('chars', start=0, end=sys.maxsize)
x: Strs = text.split_charset(separator='chars', maxsplit=sys.maxsize, keepseparator=False)
x: Strs = text.rsplit_charset(separator='chars', maxsplit=sys.maxsize, keepseparator=False)
```

### Collection-Level Operations

Once split into a `Strs` object, you can sort, shuffle, and reorganize the slices, with minimum memory footprint.
If all the chunks are located in consecutive memory regions, the memory overhead can be as low as 4 bytes per chunk.

```python
lines: Strs = text.split(separator='\n') # 4 bytes per line overhead for under 4 GB of text
batch: Strs = lines.sample(seed=42) # 10x faster than `random.choices`
lines.shuffle(seed=42) # or shuffle all lines in place and shard with slices
# WIP: lines.sort() # explodes to 16 bytes per line overhead for any length text
# WIP: sorted_order: tuple = lines.argsort() # similar to `numpy.argsort`
```

Working on [RedPajama][redpajama], addressing 20 Billion annotated english documents, one will need only 160 GB of RAM instead of Terabytes.
Once loaded, the data will be memory-mapped, and can be reused between multiple Python processes without copies.
And of course, you can use slices to navigate the dataset and shard it between multiple workers.

```python
lines[::3] # every third line
lines[1::1] # every odd line
lines[:-100:-1] # last 100 lines in reverse order
```

[redpajama]: https://github.com/togethercomputer/RedPajama-Data

### Iterators and Memory Efficiency

Python's operations like `split()` and `readlines()` immediately materialize a `list` of copied parts.
This can be very memory-inefficient for large datasets.
StringZilla saves a lot of memory by viewing existing memory regions as substrings, but even more memory can be saved by using lazily evaluated iterators.

```py
x: SplitIterator[Str] = text.split_iter(separator=' ', keepseparator=False)
x: SplitIterator[Str] = text.rsplit_iter(separator=' ', keepseparator=False)
x: SplitIterator[Str] = text.split_charset_iter(separator='chars', keepseparator=False)
x: SplitIterator[Str] = text.rsplit_charset_iter(separator='chars', keepseparator=False)
```

StringZilla can easily be 10x more memory efficient than native Python classes for tokenization.
With lazy operations, it practically becomes free.

```py
import stringzilla as sz
%load_ext memory_profiler

text = open("enwik9.txt", "r").read() # 1 GB, mean word length 7.73 bytes
%memit text.split() # increment: 8670.12 MiB (152 ms)
%memit sz.split(text) # increment: 530.75 MiB (25 ms)
%memit sum(1 for _ in sz.split_iter(text)) # increment: 0.00 MiB
```

### Low-Level Python API

Aside from calling the methods on the `Str` and `Strs` classes, you can also call the global functions directly on `str` and `bytes` instances.
Assuming StringZilla CPython bindings are implemented [without any intermediate tools like SWIG or PyBind](https://ashvardanian.com/posts/pybind11-cpython-tutorial/), the call latency should be similar to native classes.

```py
import stringzilla as sz

contains: bool = sz.contains("haystack", "needle", start=0, end=sys.maxsize)
offset: int = sz.find("haystack", "needle", start=0, end=sys.maxsize)
count: int = sz.count("haystack", "needle", start=0, end=sys.maxsize, allowoverlap=False)
```

### Edit Distances

```py
assert sz.edit_distance("apple", "aple") == 1 # skip one ASCII character
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.
Most of them are implemented in C, but are not always as fast as StringZilla.
Taking a 1'000 long proteins around 10'000 characters long, computing just a 100 distances:

- [JellyFish](https://github.com/jamesturk/jellyfish): 62.3s
- [EditDistance](https://github.com/roy-ht/editdistance): 32.9s
- StringZilla: __0.8s__

Moreover, you can pass custom substitution matrices to compute the Needleman-Wunsch alignment scores.
That task is very common in bioinformatics and computational biology.
It's natively supported in BioPython, and its BLOSUM matrices can be converted to StringZilla's format.
Alternatively, you can construct an arbitrary 256 by 256 cost matrix using NumPy.
Depending on arguments, the result may be equal to the negative Levenshtein distance.

```py
import numpy as np
import stringzilla as sz

costs = np.zeros((256, 256), dtype=np.int8)
costs.fill(-1)
np.fill_diagonal(costs, 0)

assert sz.alignment_score("first", "second", substitution_matrix=costs, gap_score=-1) == -sz.edit_distance(a, b)
```

Using the same proteins as for Levenshtein distance benchmarks:

- [BioPython](https://github.com/biopython/biopython): 25.8s
- StringZilla: __7.8s__

<details>
  <summary><b>ยง Example converting from BioPython to StringZilla.</b></summary>

```py
import numpy as np
from Bio import Align
from Bio.Align import substitution_matrices

aligner = Align.PairwiseAligner()
aligner.substitution_matrix = substitution_matrices.load("BLOSUM62")
aligner.open_gap_score = 1
aligner.extend_gap_score = 1

# Convert the matrix to NumPy
subs_packed = np.array(aligner.substitution_matrix).astype(np.int8)
subs_reconstructed = np.zeros((256, 256), dtype=np.int8)

# Initialize all banned characters to a the largest possible penalty
subs_reconstructed.fill(127)
for packed_row, packed_row_aminoacid in enumerate(aligner.substitution_matrix.alphabet):
    for packed_column, packed_column_aminoacid in enumerate(aligner.substitution_matrix.alphabet):
        reconstructed_row = ord(packed_row_aminoacid)
        reconstructed_column = ord(packed_column_aminoacid)
        subs_reconstructed[reconstructed_row, reconstructed_column] = subs_packed[packed_row, packed_column]

# Let's pick two examples for of tri-peptides (made of 3 aminoacids)
glutathione = "ECG" # Need to rebuild human tissue?
thyrotropin_releasing_hormone = "QHP" # Or to regulate your metabolism?

assert sz.alignment_score(
    glutathione,
    thyrotropin_releasing_hormone, 
    substitution_matrix=subs_reconstructed, 
    gap_score=1) == aligner.score(glutathione, thyrotropin_releasing_hormone) # Equal to 6
```

</details>

### Serialization

#### Filesystem

Similar to how `File` can be used to read a large file, other interfaces can be used to dump strings to disk faster.
The `Str` class has `write_to` to write the string to a file, and `offset_within` to obtain integer offsets of substring view in larger string for navigation.

```py
web_archieve = Str("<html>...</html><html>...</html>")
_, end_tag, next_doc = web_archieve.partition("</html>") # or use `find`
next_doc_offset = next_doc.offset_within(web_archieve)
web_archieve.write_to("next_doc.html") # no GIL, no copies, just a view
```

#### PyArrow

A `Str` is easy to cast to [PyArrow](https://arrow.apache.org/docs/python/arrays.html#string-and-binary-types) buffers.

```py
from pyarrow import foreign_buffer
from stringzilla import Str

original = "hello"
view = Str(native)
arrow = foreign_buffer(view.address, view.nbytes, view)
```

That means you can convert `Str` to `pyarrow.Buffer` and `Strs` to `pyarrow.Array` without extra copies.

## Quick Start: C/C++ ๐Ÿ› ๏ธ

The C library is header-only, so you can just copy the `stringzilla.h` header into your project.
Same applies to C++, where you would copy the `stringzilla.hpp` header.
Alternatively, add it as a submodule, and include it in your build system.

```sh
git submodule add https://github.com/ashvardanian/stringzilla.git
```

Or using a pure CMake approach:

```cmake
FetchContent_Declare(stringzilla GIT_REPOSITORY https://github.com/ashvardanian/stringzilla.git)
FetchContent_MakeAvailable(stringzilla)
```

Last, but not the least, you can also install it as a library, and link against it.
This approach is worse for inlining, but brings dynamic runtime dispatch for the most advanced CPU features.

### Basic Usage with C 99 and Newer

There is a stable C 99 interface, where all function names are prefixed with `sz_`.
Most interfaces are well documented, and come with self-explanatory names and examples.
In some cases, hardware specific overloads are available, like `sz_find_avx512` or `sz_find_neon`.
Both are companions of the `sz_find`, first for x86 CPUs with AVX-512 support, and second for Arm NEON-capable CPUs.

```c
#include <stringzilla/stringzilla.h>

// Initialize your haystack and needle
sz_string_view_t haystack = {your_text, your_text_length};
sz_string_view_t needle = {your_subtext, your_subtext_length};

// Perform string-level operations
sz_size_t substring_position = sz_find(haystack.start, haystack.length, needle.start, needle.length);
sz_size_t substring_position = sz_find_avx512(haystack.start, haystack.length, needle.start, needle.length);
sz_size_t substring_position = sz_find_neon(haystack.start, haystack.length, needle.start, needle.length);

// Hash strings
sz_u64_t hash = sz_hash(haystack.start, haystack.length);

// Perform collection level operations
sz_sequence_t array = {your_order, your_count, your_get_start, your_get_length, your_handle};
sz_sort(&array, &your_config);
```

<details>
  <summary><b>ยง Mapping from LibC to StringZilla.</b></summary>

By design, StringZilla has a couple of notable differences from LibC:

1. all strings are expected to have a length, and are not necessarily null-terminated.
2. every operations has a reverse order counterpart.

That way `sz_find` and `sz_rfind` are similar to `strstr` and `strrstr` in LibC.
Similarly, `sz_find_byte` and `sz_rfind_byte` replace `memchr` and `memrchr`.
The `sz_find_charset` maps to `strspn` and `strcspn`, while `sz_rfind_charset` has no sibling in LibC.

<table>
    <tr>
        <th>LibC Functionality</th>
        <th>StringZilla Equivalents</th>
    </tr>
    <tr>
        <td><code>memchr(haystack, needle, haystack_length)</code>, <code>strchr</code></td>
        <td><code>sz_find_byte(haystack, haystack_length, needle)</code></td>
    </tr>
    <tr>
        <td><code>memrchr(haystack, needle, haystack_length)</code></td>
        <td><code>sz_rfind_byte(haystack, haystack_length, needle)</code></td>
    </tr>
    <tr>
        <td><code>memcmp</code>, <code>strcmp</code></td>
        <td><code>sz_order</code>, <code>sz_equal</code></td>
    </tr>
    <tr>
        <td><code>strlen(haystack)</code></td>
        <td><code>sz_find_byte(haystack, haystack_length, needle)</code></td>
    </tr>
    <tr>
        <td><code>strcspn(haystack, needles)</code></td>
        <td><code>sz_rfind_charset(haystack, haystack_length, needles_bitset)</code></td>
    </tr>
    <tr>
        <td><code>strspn(haystack, needles)</code></td>
        <td><code>sz_find_charset(haystack, haystack_length, needles_bitset)</code></td>
    </tr>
    <tr>
        <td><code>memmem(haystack, haystack_length, needle, needle_length)</code>, <code>strstr</code></td>
        <td><code>sz_find(haystack, haystack_length, needle, needle_length)</code></td>
    </tr>
    <tr>
        <td><code>memcpy(destination, source, destination_length)</code></td>
        <td><code>sz_copy(destination, source, destination_length)</code></td>
    </tr>
    <tr>
        <td><code>memmove(destination, source, destination_length)</code></td>
        <td><code>sz_move(destination, source, destination_length)</code></td>
    </tr>
    <tr>
        <td><code>memset(destination, value, destination_length)</code></td>
        <td><code>sz_fill(destination, destination_length, value)</code></td>
    </tr>
</table>

</details>

### Basic Usage with C++ 11 and Newer

There is a stable C++ 11 interface available in the `ashvardanian::stringzilla` namespace.
It comes with two STL-like classes: `string_view` and `string`.
The first is a non-owning view of a string, and the second is a mutable string with a [Small String Optimization][faq-sso].

```cpp
#include <stringzilla/stringzilla.hpp>

namespace sz = ashvardanian::stringzilla;

sz::string haystack = "some string";
sz::string_view needle = sz::string_view(haystack).substr(0, 4);

auto substring_position = haystack.find(needle); // Or `rfind`
auto hash = std::hash<sz::string_view>(haystack); // Compatible with STL's `std::hash`

haystack.end() - haystack.begin() == haystack.size(); // Or `rbegin`, `rend`
haystack.find_first_of(" \w\t") == 4; // Or `find_last_of`, `find_first_not_of`, `find_last_not_of`
haystack.starts_with(needle) == true; // Or `ends_with`
haystack.remove_prefix(needle.size()); // Why is this operation in-place?!
haystack.contains(needle) == true; // STL has this only from C++ 23 onwards
haystack.compare(needle) == 1; // Or `haystack <=> needle` in C++ 20 and beyond
```

StringZilla also provides string literals for automatic type resolution, [similar to STL][stl-literal]:

```cpp
using sz::literals::operator""_sz;
using std::literals::operator""sv;

auto a = "some string"; // char const *
auto b = "some string"sv; // std::string_view
auto b = "some string"_sz; // sz::string_view
```

[stl-literal]: https://en.cppreference.com/w/cpp/string/basic_string_view/operator%22%22sv

### Memory Ownership and Small String Optimization

Most operations in StringZilla don't assume any memory ownership.
But in addition to the read-only search-like operations StringZilla provides a minimalistic C and C++ implementations for a memory owning string "class".
Like other efficient string implementations, it uses the [Small String Optimization][faq-sso] (SSO) to avoid heap allocations for short strings.

[faq-sso]: https://cpp-optimizations.netlify.app/small_strings/

```c
typedef union sz_string_t {
    struct internal {
        sz_ptr_t start;
        sz_u8_t length;
        char chars[SZ_STRING_INTERNAL_SPACE]; /// Ends with a null-terminator.
    } internal;

    struct external {
        sz_ptr_t start;
        sz_size_t length;        
        sz_size_t space; /// The length of the heap-allocated buffer.
        sz_size_t padding;
    } external;

} sz_string_t;
```

As one can see, a short string can be kept on the stack, if it fits within `internal.chars` array.
Before 2015 GCC string implementation was just 8 bytes, and could only fit 7 characters.
Different STL implementations today have different thresholds for the Small String Optimization.
Similar to GCC, StringZilla is 32 bytes in size, and similar to Clang it can fit 22 characters on stack.
Our layout might be preferential, if you want to avoid branches.
If you use a different compiler, you may want to check it's SSO buffer size with a [simple Gist](https://gist.github.com/ashvardanian/c197f15732d9855c4e070797adf17b21).

|                       | `libstdc++` in  GCC 13 | `libc++` in Clang 17 | StringZilla |
| :-------------------- | ---------------------: | -------------------: | ----------: |
| `sizeof(std::string)` |                     32 |                   24 |          32 |
| Small String Capacity |                     15 |               __22__ |      __22__ |

This design has been since ported to many high-level programming languages.
Swift, for example, [can store 15 bytes](https://developer.apple.com/documentation/swift/substring/withutf8(_:)#discussion) in the `String` instance itself.
StringZilla implements SSO at the C level, providing the `sz_string_t` union and a simple API for primary operations.

```c
sz_memory_allocator_t allocator;
sz_string_t string;

// Init and make sure we are on stack
sz_string_init(&string);
sz_string_is_on_stack(&string); // == sz_true_k

// Optionally pre-allocate space on the heap for future insertions.
sz_string_grow(&string, 100, &allocator); // == sz_true_k

// Append, erase, insert into the string.
sz_string_expand(&string, 0, "_Hello_", 7, &allocator); // == sz_true_k
sz_string_expand(&string, SZ_SIZE_MAX, "world", 5, &allocator); // == sz_true_k
sz_string_erase(&string, 0, 1);

// Unpacking & introspection.
sz_ptr_t string_start;
sz_size_t string_length;
sz_size_t string_space;
sz_bool_t string_is_external;
sz_string_unpack(string, &string_start, &string_length, &string_space, &string_is_external);
sz_equal(string_start, "Hello_world", 11); // == sz_true_k

// Reclaim some memory.
sz_string_shrink_to_fit(&string, &allocator); // == sz_true_k
sz_string_free(&string, &allocator);
```

Unlike the conventional C strings, the `sz_string_t` is allowed to contain null characters.
To safely print those, pass the `string_length` to `printf` as well.

```c
printf("%.*s\n", (int)string_length, string_start);
```

### What's Wrong with the C++ Standard Library?

| C++ Code                             | Evaluation Result | Invoked Signature              |
| :----------------------------------- | :---------------- | :----------------------------- |
| `"Loose"s.replace(2, 2, "vath"s, 1)` | `"Loathe"` ๐Ÿคข      | `(pos1, count1, str2, pos2)`   |
| `"Loose"s.replace(2, 2, "vath", 1)`  | `"Love"` ๐Ÿฅฐ        | `(pos1, count1, str2, count2)` |

StringZilla is designed to be a drop-in replacement for the C++ Standard Templates Library.
That said, some of the design decisions of STL strings are highly controversial, error-prone, and expensive.
Most notably:

1. Argument order for `replace`, `insert`, `erase` and similar functions is impossible to guess.
2. Bounds-checking exceptions for `substr`-like functions are only thrown for one side of the range.
3. Returning string copies in `substr`-like functions results in absurd volume of allocations.
4. Incremental construction via `push_back`-like functions goes through too many branches.
5. Inconsistency between `string` and `string_view` methods, like the lack of `remove_prefix` and `remove_suffix`.

Check the following set of asserts validating the `std::string` specification.
It's not realistic to expect the average developer to remember the [14 overloads of `std::string::replace`][stl-replace].

[stl-replace]: https://en.cppreference.com/w/cpp/string/basic_string/replace

```cpp
using str = std::string;

assert(str("hello world").substr(6) == "world");
assert(str("hello world").substr(6, 100) == "world"); // 106 is beyond the length of the string, but its OK
assert_throws(str("hello world").substr(100), std::out_of_range);   // 100 is beyond the length of the string
assert_throws(str("hello world").substr(20, 5), std::out_of_range); // 20 is beyond the length of the string
assert_throws(str("hello world").substr(-1, 5), std::out_of_range); // -1 casts to unsigned without any warnings...
assert(str("hello world").substr(0, -1) == "hello world");          // -1 casts to unsigned without any warnings...

assert(str("hello").replace(1, 2, "123") == "h123lo");
assert(str("hello").replace(1, 2, str("123"), 1) == "h23lo");
assert(str("hello").replace(1, 2, "123", 1) == "h1lo");
assert(str("hello").replace(1, 2, "123", 1, 1) == "h2lo");
assert(str("hello").replace(1, 2, str("123"), 1, 1) == "h2lo");
assert(str("hello").replace(1, 2, 3, 'a') == "haaalo");
assert(str("hello").replace(1, 2, {'a', 'b'}) == "hablo");
```

To avoid those issues, StringZilla provides an alternative consistent interface.
It supports signed arguments, and doesn't have more than 3 arguments per function or
The standard API and our alternative can be conditionally disabled with `SZ_SAFETY_OVER_COMPATIBILITY=1`.
When it's enabled, the _~~subjectively~~_ risky overloads from the Standard will be disabled.

```cpp
using str = sz::string;

str("a:b").front(1) == "a"; // no checks, unlike `substr`
str("a:b").back(-1) == "b"; // accepting negative indices
str("a:b").sub(1, -1) == ":"; // similar to Python's `"a:b"[1:-1]`
str("a:b").sub(-2, -1) == ":"; // similar to Python's `"a:b"[-2:-1]`
str("a:b").sub(-2, 1) == ""; // similar to Python's `"a:b"[-2:1]`
"a:b"_sz[{-2, -1}] == ":"; // works on views and overloads `operator[]`
```

Assuming StringZilla is a header-only library you can use the full API in some translation units and gradually transition to safer restricted API in others.
Bonus - all the bound checking is branchless, so it has a constant cost and won't hurt your branch predictor.

### Beyond the C++ Standard Library - Learning from Python

Python is arguably the most popular programming language for data science.
In part, that's due to the simplicity of its standard interfaces.
StringZilla brings some of that functionality to C++.

- Content checks: `isalnum`, `isalpha`, `isascii`, `isdigit`, `islower`, `isspace`, `isupper`.
- Trimming character sets: `lstrip`, `rstrip`, `strip`.
- Trimming string matches: `remove_prefix`, `remove_suffix`.
- Ranges of search results: `splitlines`, `split`, `rsplit`.
- Number of non-overlapping substring matches: `count`.
- Partitioning: `partition`, `rpartition`.

For example, when parsing documents, it is often useful to split it into substrings.
Most often, after that, you would compute the length of the skipped part, the offset and the length of the remaining part.
This results in a lot of pointer arithmetic and is error-prone.
StringZilla provides a convenient `partition` function, which returns a tuple of three string views, making the code cleaner.

```cpp
auto parts = haystack.partition(':'); // Matching a character
auto [before, match, after] = haystack.partition(':'); // Structure unpacking
auto [before, match, after] = haystack.partition(char_set(":;")); // Character-set argument
auto [before, match, after] = haystack.partition(" : "); // String argument
auto [before, match, after] = haystack.rpartition(sz::whitespaces); // Split around the last whitespace
```

Combining those with the `split` function, one can easily parse a CSV file or HTTP headers.

```cpp
for (auto line : haystack.split("\r\n")) {
    auto [key, _, value] = line.partition(':');
    headers[key.strip()] = value.strip();
}
```

Some other extensions are not present in the Python standard library either.
Let's go through the C++ functionality category by category.

- [Splits and Ranges](#splits-and-ranges).
- [Concatenating Strings without Allocations](#concatenating-strings-without-allocations).
- [Random Generation](#random-generation).
- [Edit Distances and Fuzzy Search](#levenshtein-edit-distance-and-alignment-scores).

Some of the StringZilla interfaces are not available even Python's native `str` class.
Here is a sneak peek of the most useful ones.

```cpp
text.hash(); // -> 64 bit unsigned integer 
text.ssize(); // -> 64 bit signed length to avoid `static_cast<std::ssize_t>(text.size())`
text.contains_only(" \w\t"); // == text.find_first_not_of(char_set(" \w\t")) == npos;
text.contains(sz::whitespaces); // == text.find(char_set(sz::whitespaces)) != npos;

// Simpler slicing than `substr`
text.front(10); // -> sz::string_view
text.back(10); // -> sz::string_view

// Safe variants, which clamp the range into the string bounds
using sz::string::cap;
text.front(10, cap) == text.front(std::min(10, text.size()));
text.back(10, cap) == text.back(std::min(10, text.size()));

// Character set filtering
text.lstrip(sz::whitespaces).rstrip(sz::newlines); // like Python
text.front(sz::whitespaces); // all leading whitespaces
text.back(sz::digits); // all numerical symbols forming the suffix

// Incremental construction
using sz::string::unchecked;
text.push_back('x'); // no surprises here
text.push_back('x', unchecked); // no bounds checking, Rust style
text.try_push_back('x'); // returns `false` if the string is full and the allocation failed

sz::concatenate(text, "@", domain, ".", tld); // No allocations
```

### Splits and Ranges

One of the most common use cases is to split a string into a collection of substrings.
Which would often result in [StackOverflow lookups][so-split] and snippets like the one below.

[so-split]: https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c

```cpp
std::vector<std::string> lines = split(haystack, "\r\n"); // string delimiter
std::vector<std::string> words = split(lines, ' '); // character delimiter
```

Those allocate memory for each string and the temporary vectors.
Each allocation can be orders of magnitude more expensive, than even serial `for`-loop over characters.
To avoid those, StringZilla provides lazily-evaluated ranges, compatible with the [Range-v3][range-v3] library.

[range-v3]: https://github.com/ericniebler/range-v3

```cpp
for (auto line : haystack.split("\r\n"))
    for (auto word : line.split(char_set(" \w\t.,;:!?")))
        std::cout << word << std::endl;
```

Each of those is available in reverse order as well.
It also allows interleaving matches, if you want both inclusions of `xx` in `xxx`.
Debugging pointer offsets is not a pleasant exercise, so keep the following functions in mind.

- `haystack.[r]find_all(needle, interleaving)`
- `haystack.[r]find_all(char_set(""))`
- `haystack.[r]split(needle)`
- `haystack.[r]split(char_set(""))`

For $N$ matches the split functions will report $N+1$ matches, potentially including empty strings.
Ranges have a few convenience methods as well:

```cpp
range.size(); // -> std::size_t
range.empty(); // -> bool
range.template to<std::set<std::sting>>(); 
range.template to<std::vector<std::sting_view>>(); 
```

### Concatenating Strings without Allocations

Another common string operation is concatenation.
The STL provides `std::string::operator+` and `std::string::append`, but those are not very efficient, if multiple invocations are performed.

```cpp
std::string name, domain, tld;
auto email = name + "@" + domain + "." + tld; // 4 allocations
```

The efficient approach would be to pre-allocate the memory and copy the strings into it.

```cpp
std::string email;
email.reserve(name.size() + domain.size() + tld.size() + 2);
email.append(name), email.append("@"), email.append(domain), email.append("."), email.append(tld);
```

That's mouthful and error-prone.
StringZilla provides a more convenient `concatenate` function, which takes a variadic number of arguments.
It also overrides the `operator|` to concatenate strings lazily, without any allocations.

```cpp
auto email = sz::concatenate(name, "@", domain, ".", tld);   // 0 allocations
auto email = name | "@" | domain | "." | tld;                // 0 allocations
sz::string email = name | "@" | domain | "." | tld;          // 1 allocations
```

### Random Generation

Software developers often need to generate random strings for testing purposes.
The STL provides `std::generate` and `std::random_device`, that can be used with StringZilla.

```cpp
sz::string random_string(std::size_t length, char const *alphabet, std::size_t cardinality) {
    sz::string result(length, '\0');
    static std::random_device seed_source; // Too expensive to construct every time
    std::mt19937 generator(seed_source());
    std::uniform_int_distribution<std::size_t> distribution(0, cardinality);
    std::generate(result.begin(), result.end(), [&]() { return alphabet[distribution(generator)]; });
    return result;
}
```

Mouthful and slow.
StringZilla provides a C native method - `sz_generate` and a convenient C++ wrapper - `sz::generate`.
Similar to Python it also defines the commonly used character sets.

```cpp
auto protein = sz::string::random(300, "ARNDCQEGHILKMFPSTWYV"); // static method
auto dna = sz::basic_string<custom_allocator>::random(3_000_000_000, "ACGT");

dna.randomize("ACGT"); // `noexcept` pre-allocated version
dna.randomize(&std::rand, "ACGT"); // pass any generator, like `std::mt19937`

char uuid[36];
sz::randomize(sz::string_span(uuid, 36), "0123456789abcdef-"); // Overwrite any buffer
```

### Levenshtein Edit Distance and Alignment Scores

Levenshtein and Hamming edit distance are provided for both byte-strings and UTF-8 strings.
The latter will output the distance in Unicode code points, not bytes.
Needleman-Wunsch alignment scores are only defined for byte-strings.

```cpp
// Count number of substitutions in same length strings
sz::hamming_distance(first, second[, upper_bound]) -> std::size_t;
sz::hamming_distance_utf8(first, second[, upper_bound]) -> std::size_t;

// Count number of insertions, deletions and substitutions
sz::edit_distance(first, second[, upper_bound[, allocator]]) -> std::size_t;
sz::edit_distance_utf8(first, second[, upper_bound[, allocator]]) -> std::size_t;

// Substitution-parametrized Needleman-Wunsch global alignment score
std::int8_t costs[256][256]; // Substitution costs matrix
sz::alignment_score(first, second, costs[, gap_score[, allocator]) -> std::ptrdiff_t;
```

### Sorting in C and C++

LibC provides `qsort` and STL provides `std::sort`.
Both have their quarks.
The LibC standard has no way to pass a context to the comparison function, that's only possible with platform-specific extensions.
Those have [different arguments order](https://stackoverflow.com/a/39561369) on every OS.

```c
// Linux: https://linux.die.net/man/3/qsort_r
void qsort_r(void *elements, size_t count, size_t element_width, 
    int (*compare)(void const *left, void const *right, void *context),
    void *context);
// MacOS and FreeBSD: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/qsort_r.3.html
void qsort_r(void *elements, size_t count, size_t element_width, 
    void *context,
    int (*compare)(void *context, void const *left, void const *right));
// Windows conflicts with ISO `qsort_s`: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/qsort-s?view=msvc-170
void qsort_s(id *elements, size_t count, size_t element_width, 
    int (*compare)(void *context, void const *left, void const *right),
    void *context);
```

C++ generic algorithm is not perfect either.
There is no guarantee in the standard that `std::sort` won't allocate any memory.
If you are running on embedded, in real-time or on 100+ CPU cores per node, you may want to avoid that.
StringZilla doesn't solve the general case, but hopes to improve the performance for strings.
Use `sz_sort`, or the high-level `sz::sorted_order`, which can be used sort any collection of elements convertible to `sz::string_view`.

```cpp
std::vector<std::string> data({"c", "b", "a"});
std::vector<std::size_t> order = sz::sorted_order(data); //< Simple shortcut

// Or, taking care of memory allocation:
sz::sorted_order(data.begin(), data.end(), order.data(), [](auto const &x) -> sz::string_view { return x; });
```

### Standard C++ Containers with String Keys

The C++ Standard Templates Library provides several associative containers, often used with string keys.

```cpp
std::map<std::string, int, std::less<std::string>> sorted_words;
std::unordered_map<std::string, int, std::hash<std::string>, std::equal_to<std::string>> words;
```

The performance of those containers is often limited by the performance of the string keys, especially on reads.
StringZilla can be used to accelerate containers with `std::string` keys, by overriding the default comparator and hash functions.

```cpp
std::map<std::string, int, sz::string_view_less> sorted_words;
std::unordered_map<std::string, int, sz::string_view_hash, sz::string_view_equal_to> words;
```

Alternatively, a better approach would be to use the `sz::string` class as a key.
The right hash function and comparator would be automatically selected and the performance gains would be more noticeable if the keys are short.

```cpp
std::map<sz::string, int> sorted_words;
std::unordered_map<sz::string, int> words;
```

### Compilation Settings and Debugging

__`SZ_DEBUG`__:

> For maximal performance, the C library does not perform any bounds checking in Release builds.
> In C++, bounds checking happens only in places where the STL `std::string` would do it.
> If you want to enable more aggressive bounds-checking, define `SZ_DEBUG` before including the header.
> If not explicitly set, it will be inferred from the build type.

__`SZ_USE_X86_AVX512`, `SZ_USE_X86_AVX2`, `SZ_USE_ARM_NEON`__:

> One can explicitly disable certain families of SIMD instructions for compatibility purposes.
> Default values are inferred at compile time.

__`SZ_DYNAMIC_DISPATCH`__:

> By default, StringZilla is a header-only library.
> But if you are running on different generations of devices, it makes sense to pre-compile the library for all supported generations at once, and dispatch at runtime.
> This flag does just that and is used to produce the `stringzilla.so` shared library, as well as the Python bindings.

__`SZ_USE_MISALIGNED_LOADS`__:

> By default, StringZilla avoids misaligned loads.
> If supported, it replaces many byte-level operations with word-level ones.
> Going from `char`-like types to `uint64_t`-like ones can significantly accelerate the serial (SWAR) backend.
> So consider enabling it if you are building for some embedded device.

__`SZ_AVOID_LIBC`__ and __`SZ_OVERRIDE_LIBC`__:

> When using the C header-only library one can disable the use of LibC.
> This may affect the type resolution system on obscure hardware platforms. 
> Moreover, one may let `stringzilla` override the common symbols like the `memcpy` and `memset` with its own implementations.
> In that case you can use the [`LD_PRELOAD` trick][ld-preload-trick] to prioritize it's symbols over the ones from the LibC and accelerate existing string-heavy applications without recompiling them.

[ld-preload-trick]: https://ashvardanian.com/posts/ld-preload-libsee

__`SZ_AVOID_STL`__ and __`SZ_SAFETY_OVER_COMPATIBILITY`__:

> When using the C++ interface one can disable implicit conversions from `std::string` to `sz::string` and back.
> If not needed, the `<string>` and `<string_view>` headers will be excluded, reducing compilation time.
> Moreover, if STL compatibility is a low priority, one can make the API safer by disabling the overloads, which are subjectively error prone.

__`STRINGZILLA_BUILD_SHARED`, `STRINGZILLA_BUILD_TEST`, `STRINGZILLA_BUILD_BENCHMARK`, `STRINGZILLA_TARGET_ARCH`__ for CMake users:

> When compiling the tests and benchmarks, you can explicitly set the target hardware architecture.
> It's synonymous to GCC's `-march` flag and is used to enable/disable the appropriate instruction sets.
> You can also disable the shared library build, if you don't need it.

## Quick Start: Rust ๐Ÿฆ€

StringZilla is available as a Rust crate, with documentation available on [docs.rs/stringzilla](https://docs.rs/stringzilla).
To use the latest crate release in your project, add the following to your `Cargo.toml`:

```toml
[dependencies]
stringzilla = ">=3"
```

Or if you want to use the latest pre-release version from the repository:

```toml
[dependencies]
stringzilla = { git = "https://github.com/ashvardanian/stringzilla", branch = "main-dev" }
```

Once installed, all of the functionality is available through the `stringzilla` namespace.
Many interfaces will look familiar to the users of the `memchr` crate.

```rust
use stringzilla::sz;

// Identical to `memchr::memmem::find` and `memchr::memmem::rfind` functions
sz::find("Hello, world!", "world") // 7
sz::rfind("Hello, world!", "world") // 7

// Generalizations of `memchr::memrchr[123]`
sz::find_char_from("Hello, world!", "world") // 2
sz::rfind_char_from("Hello, world!", "world") // 11
```

Unlike `memchr`, the throughput of `stringzilla` is [high in both normal and reverse-order searches][memchr-benchmarks].
It also provides no constraints on the size of the character set, while `memchr` allows only 1, 2, or 3 characters.
In addition to global functions, `stringzilla` provides a `StringZilla` extension trait:

```rust
use stringzilla::StringZilla;

let my_string: String = String::from("Hello, world!");
let my_str = my_string.as_str();
let my_cow_str = Cow::from(&my_string);

// Use the generic function with a String
assert_eq!(my_string.sz_find("world"), Some(7));
assert_eq!(my_string.sz_rfind("world"), Some(7));
assert_eq!(my_string.sz_find_char_from("world"), Some(2));
assert_eq!(my_string.sz_rfind_char_from("world"), Some(11));
assert_eq!(my_string.sz_find_char_not_from("world"), Some(0));
assert_eq!(my_string.sz_rfind_char_not_from("world"), Some(12));

// Same works for &str and Cow<'_, str>
assert_eq!(my_str.sz_find("world"), Some(7));
assert_eq!(my_cow_str.as_ref().sz_find("world"), Some(7));
```

The library also exposes Levenshtein and Hamming edit-distances for byte-arrays and UTF-8 strings, as well as Needleman-Wunch alignment scores.

```rust
use stringzilla::sz;

// Handling arbitrary byte arrays:
sz::edit_distance("Hello, world!", "Hello, world?"); // 1
sz::hamming_distance("Hello, world!", "Hello, world?"); // 1
sz::alignment_score("Hello, world!", "Hello, world?", sz::unary_substitution_costs(), -1); // -1

// Handling UTF-8 strings:
sz::hamming_distance_utf8("ฮฑฮฒฮณฮด", "ฮฑฮณฮณฮด") // 1
sz::edit_distance_utf8("faรงade", "facade") // 1
```

[memchr-benchmarks]: https://github.com/ashvardanian/memchr_vs_stringzilla

## Quick Start: Swift ๐Ÿ

StringZilla can be added as a dependency in the Swift Package Manager.
In your `Package.swift` file, add the following:

```swift
dependencies: [
    .package(url: "https://github.com/ashvardanian/stringzilla")
]
```

The package currently covers only the most basic functionality, but is planned to be extended to cover the full C++ API.

```swift
var s = "Hello, world! Welcome to StringZilla. ๐Ÿ‘‹"
s[s.findFirst(substring: "world")!...] // "world! Welcome to StringZilla. ๐Ÿ‘‹")    
s[s.findLast(substring: "o")!...] // "o StringZilla. ๐Ÿ‘‹")
s[s.findFirst(characterFrom: "aeiou")!...] // "ello, world! Welcome to StringZilla. ๐Ÿ‘‹")
s[s.findLast(characterFrom: "aeiou")!...] // "a. ๐Ÿ‘‹")
s[s.findFirst(characterNotFrom: "aeiou")!...] // "Hello, world! Welcome to StringZilla. ๐Ÿ‘‹"
s.editDistance(from: "Hello, world!")! // 29
```

## Algorithms & Design Decisions ๐Ÿ“š

StringZilla aims to optimize some of the slowest string operations.
Some popular operations, however, like equality comparisons and relative order checking, almost always complete on some of the very first bytes in either string.
In such operations vectorization is almost useless, unless huge and very similar strings are considered.
StringZilla implements those operations as well, but won't result in substantial speedups.

### Exact Substring Search

Substring search algorithms are generally divided into: comparison-based, automaton-based, and bit-parallel.
Different families are effective for different alphabet sizes and needle lengths.
The more operations are needed per-character - the more effective SIMD would be.
The longer the needle - the more effective the skip-tables are.
StringZilla uses different exact substring search algorithms for different needle lengths and backends:

- When no SIMD is available - SWAR (SIMD Within A Register) algorithms are used on 64-bit words.
- Boyer-Moore-Horspool (BMH) algorithm with Raita heuristic variation for longer needles.
- SIMD algorithms are randomized to look at different parts of the needle.

On very short needles, especially 1-4 characters long, brute force with SIMD is the fastest solution.
On mid-length needles, bit-parallel algorithms are effective, as the character masks fit into 32-bit or 64-bit words.
Either way, if the needle is under 64-bytes long, on haystack traversal we will still fetch every CPU cache line.
So the only way to improve performance is to reduce the number of comparisons.
The snippet below shows how StringZilla accomplishes that for needles of length two.

https://github.com/ashvardanian/StringZilla/blob/266c01710dddf71fc44800f36c2f992ca9735f87/include/stringzilla/stringzilla.h#L1585-L1637

Going beyond that, to long needles, Boyer-Moore (BM) and its variants are often the best choice.
It has two tables: the good-suffix shift and the bad-character shift.
Common choice is to use the simplified BMH algorithm, which only uses the bad-character shift table, reducing the pre-processing time.
We do the same for mid-length needles up to 256 bytes long.
That way the stack-allocated shift table remains small.

https://github.com/ashvardanian/StringZilla/blob/46e957cd4f9ecd4945318dd3c48783dd11323f37/include/stringzilla/stringzilla.h#L1774-L1825

In the C++ Standards Library, the `std::string::find` function uses the BMH algorithm with Raita's heuristic.
Before comparing the entire string, it matches the first, last, and the middle character.
Very practical, but can be slow for repetitive characters.
Both SWAR and SIMD backends of StringZilla have a cheap pre-processing step, where we locate unique characters.
This makes the library a lot more practical when dealing with non-English corpora.

https://github.com/ashvardanian/StringZilla/blob/46e957cd4f9ecd4945318dd3c48783dd11323f37/include/stringzilla/stringzilla.h#L1398-L1431

All those, still, have $O(hn)$ worst case complexity.
To guarantee $O(h)$ worst case time complexity, the Apostolico-Giancarlo (AG) algorithm adds an additional skip-table.
Preprocessing phase is $O(n+sigma)$ in time and space.
On traversal, performs from $(h/n)$ to $(3h/2)$ comparisons.
It however, isn't practical on modern CPUs.
A simpler idea, the Galil-rule might be a more relevant optimizations, if many matches must be found.

Other algorithms previously considered and deprecated:

- Apostolico-Giancarlo algorithm for longer needles. _Control-flow is too complex for efficient vectorization._
- Shift-Or-based Bitap algorithm for short needles. _Slower than SWAR._
- Horspool-style bad-character check in SIMD backends. _Effective only for very long needles, and very uneven character distributions between the needle and the haystack. Faster "character-in-set" check needed to generalize._

> ยง Reading materials.
> [Exact String Matching Algorithms in Java](https://www-igm.univ-mlv.fr/~lecroq/string).
> [SIMD-friendly algorithms for substring searching](http://0x80.pl/articles/simd-strfind.html).

### Levenshtein Edit Distance

Levenshtein distance is the best known edit-distance for strings, that checks, how many insertions, deletions, and substitutions are needed to transform one string to another.
It's extensively used in approximate string-matching, spell-checking, and bioinformatics.

The computational cost of the Levenshtein distance is $O(n * m)$, where $n$ and $m$ are the lengths of the string arguments.
To compute that, the naive approach requires $O(n * m)$ space to store the "Levenshtein matrix", the bottom-right corner of which will contain the Levenshtein distance.
The algorithm producing the matrix has been simultaneously studied/discovered by the Soviet mathematicians Vladimir Levenshtein in 1965, Taras Vintsyuk in 1968, and American computer scientists - Robert Wagner, David Sankoff, Michael J. Fischer in the following years.
Several optimizations are known:

1. __Space Optimization__: The matrix can be computed in $O(min(n,m))$ space, by only storing the last two rows of the matrix.
2. __Divide and Conquer__: Hirschberg's algorithm can be applied to decompose the computation into subtasks.
3. __Automata__: Levenshtein automata can be effective, if one of the strings doesn't change, and is a subject to many comparisons.
4. __Shift-Or__: Bit-parallel algorithms transpose the matrix into a bit-matrix, and perform bitwise operations on it.

The last approach is quite powerful and performant, and is used by the great [RapidFuzz][rapidfuzz] library.
It's less known, than the others, derived from the Baeza-Yates-Gonnet algorithm, extended to bounded edit-distance search by Manber and Wu in 1990s, and further extended by Gene Myers in 1999 and Heikki Hyyro between 2002 and 2004.

StringZilla introduces a different approach, extensively used in Unum's internal combinatorial optimization libraries.
The approach doesn't change the number of trivial operations, but performs them in a different order, removing the data dependency, that occurs when computing the insertion costs.
This results in much better vectorization for intra-core parallelism and potentially multi-core evaluation of a single request.

Next design goals:

- [ ] Generalize fast traversals to rectangular matrices.
- [ ] Port x86 AVX-512 solution to Arm NEON.

> ยง Reading materials.
> [Faster Levenshtein Distances with a SIMD-friendly Traversal Order](https://ashvardanian.com/posts/levenshtein-diagonal).

[rapidfuzz]: https://github.com/rapidfuzz/RapidFuzz

### Needleman-Wunsch Alignment Score for Bioinformatics

The field of bioinformatics studies various representations of biological structures.
The "primary" representations are generally strings over sparse alphabets:

- [DNA][faq-dna] sequences, where the alphabet is {A, C, G, T}, ranging from ~100 characters for short reads to 3 billion for the human genome.
- [RNA][faq-rna] sequences, where the alphabet is {A, C, G, U}, ranging from ~50 characters for tRNA to thousands for mRNA.
- [Proteins][faq-protein], where the alphabet is made of 22 amino acids, ranging from 2 characters for [dipeptide][faq-dipeptide] to 35,000 for [Titin][faq-titin], the longest protein.

The shorter the representation, the more often researchers may want to use custom substitution matrices.
Meaning that the cost of a substitution between two characters may not be the same for all pairs.

StringZilla adapts the fairly efficient two-row Wagner-Fisher algorithm as a baseline serial implementation of the Needleman-Wunsch score.
It supports arbitrary alphabets up to 256 characters, and can be used with either [BLOSUM][faq-blosum], [PAM][faq-pam], or other substitution matrices.
It also uses SIMD for hardware acceleration of the substitution lookups.
This however, does not __yet__ break the data-dependency for insertion costs, where 80% of the time is wasted.
With that solved, the SIMD implementation will become 5x faster than the serial one.

[faq-dna]: https://en.wikipedia.org/wiki/DNA
[faq-rna]: https://en.wikipedia.org/wiki/RNA
[faq-protein]: https://en.wikipedia.org/wiki/Protein
[faq-blosum]: https://en.wikipedia.org/wiki/BLOSUM
[faq-pam]: https://en.wikipedia.org/wiki/Point_accepted_mutation
[faq-dipeptide]: https://en.wikipedia.org/wiki/Dipeptide
[faq-titin]: https://en.wikipedia.org/wiki/Titin

### Random Generation

Generating random strings from different alphabets is a very common operation.
StringZilla accepts an arbitrary [Pseudorandom Number Generator][faq-prng] to produce noise, and an array of characters to sample from.
Sampling is optimized to avoid integer division, a costly operation on modern CPUs.
For that a 768-byte long lookup table is used to perform 2 lookups, 1 multiplication, 2 shifts, and 2 accumulations.

https://github.com/ashvardanian/StringZilla/blob/266c01710dddf71fc44800f36c2f992ca9735f87/include/stringzilla/stringzilla.h#L2490-L2533

[faq-prng]: https://en.wikipedia.org/wiki/Pseudorandom_number_generator

### Sorting

For lexicographic sorting of strings, StringZilla uses a "hybrid-hybrid" approach with $O(n * log(n))$ and.

1. Radix sort for first bytes exported into a continuous buffer for locality.
2. IntroSort on partially ordered chunks to balance efficiency and worst-case performance.
   1. IntroSort begins with a QuickSort.
   2. If the recursion depth exceeds a certain threshold, it switches to a HeapSort.

Next design goals:

- [ ] Generalize to arrays with over 4 billion entries.
- [ ] Algorithmic improvements may yield another 3x performance gain.
- [ ] SIMD-acceleration for the Radix slice.

### Hashing

> [!WARNING]
> Hash functions are not cryptographically safe and are currently under active development.
> They may change in future __minor__ releases.

Choosing the right hashing algorithm for your application can be crucial from both performance and security standpoint.
In StringZilla a 64-bit rolling hash function is reused for both string hashes and substring hashes, Rabin-style fingerprints.
Rolling hashes take the same amount of time to compute hashes with different window sizes, and are fast to update.
Those are not however perfect hashes, and collisions are frequent.
StringZilla attempts to use SIMD, but the performance is not __yet__ satisfactory.
On Intel Sapphire Rapids, the following numbers can be expected for N-way parallel variants.

- 4-way AVX2 throughput with 64-bit integer multiplication (no native support): 0.28 GB/s.
- 4-way AVX2 throughput with 32-bit integer multiplication: 0.54 GB/s.
- 4-way AVX-512DQ throughput with 64-bit integer multiplication: 0.46 GB/s.
- 4-way AVX-512 throughput with 32-bit integer multiplication: 0.58 GB/s.
- 8-way AVX-512 throughput with 32-bit integer multiplication: 0.11 GB/s.

Next design goals:

- [ ] Try gear-hash and other rolling approaches.

#### Why not CRC32?

Cyclic Redundancy Check 32 is one of the most commonly used hash functions in Computer Science.
It has in-hardware support on both x86 and Arm, for both 8-bit, 16-bit, 32-bit, and 64-bit words.
The `0x1EDC6F41` polynomial is used in iSCSI, Btrfs, ext4, and the `0x04C11DB7` in SATA, Ethernet, Zlib, PNG.
In case of Arm more than one polynomial is supported.
It is, however, somewhat limiting for Big Data usecases, which often have to deal with more than 4 Billion strings, making collisions unavoidable.
Moreover, the existing SIMD approaches are tricky, combining general purpose computations with specialized instructions, to utilize more silicon in every cycle.

> ยง Reading materials.
> [Comprehensive derivation of approaches](https://github.com/komrad36/CRC)
> [Faster computation for 4 KB buffers on x86](https://www.corsix.org/content/fast-crc32c-4k)
> [Comparing different lookup tables](https://create.stephan-brumme.com/crc32)
> Great open-source implementations.
> [By Peter Cawley](https://github.com/corsix/fast-crc32)
> [By Stephan Brumme](https://github.com/stbrumme/crc32)

#### Other Modern Alternatives

[MurmurHash](https://github.com/aappleby/smhasher/blob/master/README.md) from 2008 by Austin Appleby is one of the best known non-cryptographic hashes.
It has a very short implementation and is capable of producing 32-bit and 128-bit hashes.
The [CityHash](https://opensource.googleblog.com/2011/04/introducing-cityhash) from 2011 by Google and the [xxHash](https://github.com/Cyan4973/xxHash) improve on that, better leveraging the super-scalar nature of modern CPUs and producing 64-bit and 128-bit hashes.

Neither of those functions are cryptographic, unlike MD5, SHA, and BLAKE algorithms.
Most of cryptographic hashes are based on the Merkle-Damgรฅrd construction, and aren't resistant to the length-extension attacks.
Current state of the Art, might be the [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) algorithm.
It's resistant to a broad range of attacks, can process 2 bytes per CPU cycle, and comes with a very optimized official implementation for C and Rust.
It has the same 128-bit security level as the BLAKE2, and achieves its performance gains by reducing the number of mixing rounds, and processing data in 1 KiB chunks, which is great for longer strings, but may result in poor performance on short ones.

All mentioned libraries have undergone extensive testing and are considered production-ready.
They can definitely accelerate your application, but so may the downstream mixer.
For instance, when a hash-table is constructed, the hashes are further shrunk to address table buckets.
If the mixer looses entropy, the performance gains from the hash function may be lost.
An example would be power-of-two modulo, which is a common mixer, but is known to be weak.
One alternative would be the [fastrange](https://github.com/lemire/fastrange) by Daniel Lemire.
Another one is the [Fibonacci hash trick](https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/) using the Golden Ratio, also used in StringZilla.

### Unicode, UTF-8, and Wide Characters

Most StringZilla operations are byte-level, so they work well with ASCII and UTF8 content out of the box.
In some cases, like edit-distance computation, the result of byte-level evaluation and character-level evaluation may differ.
So StringZilla provides following functions to work with Unicode:

- `sz_edit_distance_utf8` - computes the Levenshtein distance between two UTF-8 strings.
- `sz_hamming_distance_utf8` - computes the Hamming distance between two UTF-8 strings.

Java, JavaScript, Python 2, C#, and Objective-C, however, use wide characters (`wchar`) - two byte long codes, instead of the more reasonable fixed-length UTF32 or variable-length UTF8.
This leads [to all kinds of offset-counting issues][wide-char-offsets] when facing four-byte long Unicode characters.
So consider transcoding with [simdutf](https://github.com/simdutf/simdutf), if you are coming from such environments.

[wide-char-offsets]: https://josephg.com/blog/string-length-lies/

## Contributing ๐Ÿ‘พ

Please check out the [contributing guide](https://github.com/ashvardanian/StringZilla/blob/main/CONTRIBUTING.md) for more details on how to setup the development environment and contribute to this project.
If you like this project, you may also enjoy [USearch][usearch], [UCall][ucall], [UForm][uform], and [SimSIMD][simsimd]. ๐Ÿค—

[usearch]: https://github.com/unum-cloud/usearch
[ucall]: https://github.com/unum-cloud/ucall
[uform]: https://github.com/unum-cloud/uform
[simsimd]: https://github.com/ashvardanian/simsimd

If you like strings and value efficiency, you may also enjoy the following projects:

- [simdutf](https://github.com/simdutf/simdutf) - transcoding UTF8, UTF16, and UTF32 LE and BE.
- [hyperscan](https://github.com/intel/hyperscan) - regular expressions with SIMD acceleration.
- [pyahocorasick](https://github.com/WojciechMula/pyahocorasick) - Aho-Corasick algorithm in Python.
- [rapidfuzz](https://github.com/rapidfuzz/RapidFuzz) - fast string matching in C++ and Python.

## License ๐Ÿ“œ

Feel free to use the project under Apache 2.0 or the Three-clause BSD license at your preference.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "stringzilla",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": null,
    "author": "Ash Vardanian",
    "author_email": null,
    "download_url": null,
    "platform": null,
    "description": "# StringZilla \ud83e\udd96\n\n![StringZilla banner](https://github.com/ashvardanian/ashvardanian/blob/master/repositories/StringZilla.png?raw=true)\n\nThe world wastes a minimum of $100M annually due to inefficient string operations.\nA typical codebase processes strings character by character, resulting in too many branches and data-dependencies, neglecting 90% of modern CPU's potential.\nLibC is different.\nIt attempts to leverage SIMD instructions to boost some operations, and is often used by higher-level languages, runtimes, and databases.\nBut it isn't perfect.\n1\ufe0f\u20e3 First, even on common hardware, including over a billion 64-bit ARM CPUs, common functions like `strstr` and `memmem` only achieve 1/3 of the CPU's thoughput.\n2\ufe0f\u20e3 Second, SIMD coverage is inconsistent: acceleration in forward scans does not guarantee speed in the reverse-order search.\n3\ufe0f\u20e3 At last, most high-level languages can't always use LibC, as the strings are often not NULL-terminated or may contain the Unicode \"Zero\" character in the middle of the string.\nThat's why StringZilla was created.\nTo provide predictably high performance, portable to any modern platform, operating system, and programming language.\n\n[![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)\n[![StringZilla Rust installs](https://img.shields.io/crates/d/stringzilla?logo=rust&label=Rust%20installs)](https://crates.io/crates/stringzilla)\n[![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)\n[![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)\n[![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)\n[![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)\n![StringZilla code size](https://img.shields.io/github/languages/code-size/ashvardanian/stringzilla)\n\nStringZilla is the GodZilla of string libraries, using [SIMD][faq-simd] and [SWAR][faq-swar] to accelerate string operations on modern CPUs.\nIt is up to __10x faster than the default and even other SIMD-accelerated string libraries__ in C, C++, Python, and other languages, while covering broad functionality.\nIt __accelerates exact and fuzzy string matching, edit distance computations, sorting, lazily-evaluated ranges to avoid memory allocations, and even random-string generators__.\n\n[faq-simd]: https://en.wikipedia.org/wiki/Single_instruction,_multiple_data\n[faq-swar]: https://en.wikipedia.org/wiki/SWAR\n\n- \ud83d\udc02 __[C](#Basic-Usage-with-C-99-and-Newer) :__ Upgrade LibC's `<string.h>` to `<stringzilla.h>`  in C 99\n- \ud83d\udc09 __[C++](#basic-usage-with-c-11-and-newer):__ Upgrade STL's `<string>` to `<stringzilla.hpp>` in C++ 11\n- \ud83d\udc0d __[Python](#quick-start-python-\ud83d\udc0d):__ Upgrade your `str` to faster `Str`\n- \ud83c\udf4e __[Swift](#quick-start-swift-\ud83c\udf4f):__ Use the `String+StringZilla` extension\n- \ud83e\udd80 __[Rust](#quick-start-rust-\ud83e\udd80):__ Use the `StringZilla` traits crate\n- \ud83d\udc1a __[Shell][faq-shell]__: Accelerate common CLI tools with `sz_` prefix\n- \ud83d\udcda Researcher? Jump to [Algorithms & Design Decisions](#algorithms--design-decisions-\ud83d\udcda)\n- \ud83d\udca1 Thinking to contribute? Look for [\"good first issues\"][first-issues]\n- \ud83e\udd1d And check the [guide](https://github.com/ashvardanian/StringZilla/blob/main/CONTRIBUTING.md) to setup the environment\n- Want more bindings or features? Let [me](https://github.com/ashvardanian) know!\n\n[faq-shell]: https://github.com/ashvardanian/StringZilla/blob/main/cli/README.md\n[first-issues]: https://github.com/ashvardanian/StringZilla/issues\n\n__Who is this for?__\n\n- 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/).\n- For software engineers optimizing strings in their apps and services.\n- For bioinformaticians and search engineers looking for edit-distances for [USearch](https://github.com/unum-cloud/usearch).\n- For [DBMS][faq-dbms] devs, optimizing `LIKE`, `ORDER BY`, and `GROUP BY` operations.\n- For hardware designers, needing a SWAR baseline for strings-processing functionality.\n- For students studying SIMD/SWAR applications to non-data-parallel operations.\n\n[faq-dbms]: https://en.wikipedia.org/wiki/Database\n\n## Performance\n\n<table style=\"width: 100%; text-align: center; table-layout: fixed;\">\n  <colgroup>\n    <col style=\"width: 25%;\">\n    <col style=\"width: 25%;\">\n    <col style=\"width: 25%;\">\n    <col style=\"width: 25%;\">\n  </colgroup>\n  <tr>\n    <th align=\"center\">C</th>\n    <th align=\"center\">C++</th>\n    <th align=\"center\">Python</th>\n    <th align=\"center\">StringZilla</th>\n  </tr>\n  <!-- Substrings, normal order -->\n  <tr>\n    <td colspan=\"4\" align=\"center\">find the first occurrence of a random word from text, \u2245 5 bytes long</td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <code>strstr</code> <sup>1</sup><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>7.4</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>2.0</b> GB/s\n    </td>\n    <td align=\"center\">\n      <code>.find</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>2.9</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>1.6</b> GB/s\n    </td>\n    <td align=\"center\">\n      <code>.find</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>1.1</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>0.6</b> GB/s\n    </td>\n    <td align=\"center\">\n      <code>sz_find</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>10.6</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>7.1</b> GB/s\n    </td>\n  </tr>\n  <!-- Substrings, reverse order -->\n  <tr>\n    <td colspan=\"4\" align=\"center\">find the last occurrence of a random word from text, \u2245 5 bytes long</td>\n  </tr>\n  <tr>\n    <td align=\"center\">\u26aa</td>\n    <td align=\"center\">\n      <code>.rfind</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>0.5</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>0.4</b> GB/s\n    </td>\n    <td align=\"center\">\n      <code>.rfind</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>0.9</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>0.5</b> GB/s\n    </td>\n    <td align=\"center\">\n      <code>sz_rfind</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>10.8</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>6.7</b> GB/s\n    </td>\n  </tr>\n  <!-- Characters, normal order -->\n  <tr>\n    <td colspan=\"4\" align=\"center\">split lines separated by <code>\\n</code> or <code>\\r</code> <sup>2</sup></td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <code>strcspn</code> <sup>1</sup><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>5.42</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>2.19</b> GB/s\n    </td>\n    <td align=\"center\">\n      <code>.find_first_of</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>0.59</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>0.46</b> GB/s\n    </td>\n    <td align=\"center\">\n      <code>re.finditer</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>0.06</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>0.02</b> GB/s\n    </td>\n    <td align=\"center\">\n      <code>sz_find_charset</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>4.08</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>3.22</b> GB/s\n    </td>\n  </tr>\n  <!-- Characters, reverse order -->\n  <tr>\n    <td colspan=\"4\" align=\"center\">find the last occurrence of any of 6 whitespaces <sup>2</sup></td>\n  </tr>\n  <tr>\n    <td align=\"center\">\u26aa</td>\n    <td align=\"center\">\n      <code>.find_last_of</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>0.25</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>0.25</b> GB/s\n    </td>\n    <td align=\"center\">\u26aa</td>\n    <td align=\"center\">\n      <code>sz_rfind_charset</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>0.43</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>0.23</b> GB/s\n    </td>\n  </tr>\n  <!-- Random Generation -->\n  <tr>\n    <td colspan=\"4\" align=\"center\">Random string from a given alphabet, 20 bytes long <sup>5</sup></td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <code>rand() % n</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>18.0</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>9.4</b> MB/s\n    </td>\n    <td align=\"center\">\n      <code>uniform_int_distribution</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>47.2</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>20.4</b> MB/s\n    </td>\n    <td align=\"center\">\n      <code>join(random.choices(...))</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>13.3</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>5.9</b> MB/s\n    </td>\n    <td align=\"center\">\n      <code>sz_generate</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>56.2</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>25.8</b> MB/s\n    </td>\n  </tr>\n  <!-- Sorting -->\n  <tr>\n    <td colspan=\"4\" align=\"center\">Get sorted order, \u2245 8 million English words <sup>6</sup></td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <code>qsort_r</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>3.55</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>5.77</b> s\n    </td>\n    <td align=\"center\">\n      <code>std::sort</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>2.79</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>4.02</b> s\n    </td>\n    <td align=\"center\">\n      <code>numpy.argsort</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>7.58</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>13.00</b> s\n    </td>\n    <td align=\"center\">\n      <code>sz_sort</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>1.91</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>2.37</b> s\n    </td>\n  </tr>\n  <!-- Edit Distance -->\n  <tr>\n    <td colspan=\"4\" align=\"center\">Levenshtein edit distance, \u2245 5 bytes long</td>\n  </tr>\n  <tr>\n    <td align=\"center\">\u26aa</td>\n    <td align=\"center\">\u26aa</td>\n    <td align=\"center\">\n      via <code>jellyfish</code> <sup>3</sup><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>1,550</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>2,220</b> ns\n    </td>\n    <td align=\"center\">\n      <code>sz_edit_distance</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>99</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>180</b> ns\n    </td>\n  </tr>\n  <!-- Alignment Score -->\n  <tr>\n    <td colspan=\"4\" align=\"center\">Needleman-Wunsch alignment scores, \u2245 10 K aminoacids long</td>\n  </tr>\n  <tr>\n    <td align=\"center\">\u26aa</td>\n    <td align=\"center\">\u26aa</td>\n    <td align=\"center\">\n      via <code>biopython</code> <sup>4</sup><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>257</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>367</b> ms\n    </td>\n    <td align=\"center\">\n      <code>sz_alignment_score</code><br/>\n      <span style=\"color:#ABABAB;\">x86:</span> <b>73</b> &centerdot;\n      <span style=\"color:#ABABAB;\">arm:</span> <b>177</b> ms\n    </td>\n  </tr>\n</table>\n\nStringZilla has a lot of functionality, most of which is covered by benchmarks across C, C++, Python and other languages.\nYou can find those in the `./scripts` directory, with usage notes listed in the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.\nNotably, if the CPU supports misaligned loads, even the 64-bit SWAR backends are faster than either standard library.\n\n> Most benchmarks were conducted on a 1 GB English text corpus, with an average word length of 5 characters.\n> The code was compiled with GCC 12, using `glibc` v2.35.\n> The benchmarks performed on Arm-based Graviton3 AWS `c7g` instances and `r7iz` Intel Sapphire Rapids.\n> Most modern Arm-based 64-bit CPUs will have similar relative speedups.\n> Variance withing x86 CPUs will be larger.\n> <sup>1</sup> Unlike other libraries, LibC requires strings to be NULL-terminated.\n> <sup>2</sup> Six whitespaces in the ASCII set are: ` \\t\\n\\v\\f\\r`. Python's and other standard libraries have specialized functions for those.\n> <sup>3</sup> Most Python libraries for strings are also implemented in C.\n> <sup>4</sup> Unlike the rest of BioPython, the alignment score computation is [implemented in C](https://github.com/biopython/biopython/blob/master/Bio/Align/_pairwisealigner.c).\n> <sup>5</sup> All modulo operations were conducted with `uint8_t` to allow compilers more optimization opportunities.\n> The C++ STL and StringZilla benchmarks used a 64-bit [Mersenne Twister][faq-mersenne-twister] as the generator.\n> For C, C++, and StringZilla, an in-place update of the string was used.\n> In Python every string had to be allocated as a new object, which makes it less fair.\n> <sup>6</sup> Contrary to the popular opinion, Python's default `sorted` function works faster than the C and C++ standard libraries.\n> That holds for large lists or tuples of strings, but fails as soon as you need more complex logic, like sorting dictionaries by a string key, or producing the \"sorted order\" permutation.\n> The latter is very common in database engines and is most similar to `numpy.argsort`.\n> Current StringZilla solution can be at least 4x faster without loss of generality.\n\n[faq-mersenne-twister]: https://en.wikipedia.org/wiki/Mersenne_Twister\n\n## Functionality\n\nStringZilla is compatible with most modern CPUs, and provides a broad range of functionality.\n\n- [x] works on both Little-Endian and Big-Endian architectures.\n- [x] works on 32-bit and 64-bit hardware architectures.\n- [x] compatible with ASCII and UTF-8 encoding.\n\nNot all features are available across all bindings.\nConsider contributing, if you need a feature that's not yet implemented.\n\n|                                | Maturity | C 99  | C++ 11 | Python | Swift | Rust  |\n| :----------------------------- | :------: | :---: | :----: | :----: | :---: | :---: |\n| Substring Search               |    \ud83c\udf33     |   \u2705   |   \u2705    |   \u2705    |   \u2705   |   \u2705   |\n| Character Set Search           |    \ud83c\udf33     |   \u2705   |   \u2705    |   \u2705    |   \u2705   |   \u2705   |\n| Edit Distances                 |    \ud83e\uddd0     |   \u2705   |   \u2705    |   \u2705    |   \u2705   |   \u26aa   |\n| Small String Class             |    \ud83e\uddd0     |   \u2705   |   \u2705    |   \u274c    |   \u274c   |   \u26aa   |\n| Sorting & Sequence Operations  |    \ud83d\udea7     |   \u2705   |   \u2705    |   \u2705    |   \u26aa   |   \u26aa   |\n| Lazy Ranges, Compressed Arrays |    \ud83e\uddd0     |   \u26aa   |   \u2705    |   \u2705    |   \u26aa   |   \u26aa   |\n| Hashes & Fingerprints          |    \ud83d\udea7     |   \u2705   |   \u2705    |   \u26aa    |   \u26aa   |   \u26aa   |\n\n> \ud83c\udf33 parts are used in production.\n> \ud83e\uddd0 parts are in beta.\n> \ud83d\udea7 parts are under active development, and are likely to break in subsequent releases.\n> \u2705 are implemented.\n> \u26aa are considered.\n> \u274c are not intended.\n\n## Quick Start: Python \ud83d\udc0d\n\nPython bindings are available on PyPI, and can be installed with `pip`.\nYou can immediately check the installed version and the used hardware capabilities with following commands:\n\n```bash\npip install stringzilla\npython -c \"import stringzilla; print(stringzilla.__version__)\"\npython -c \"import stringzilla; print(stringzilla.__capabilities__)\"\n```\n\n### Basic Usage\n\nIf you've ever used the Python `str`, `bytes`, `bytearray`, `memoryview` class, you'll know what to expect.\nStringZilla's `Str` class is a hybrid of those two, providing `str`-like interface to byte-arrays.\n\n```python\nfrom stringzilla import Str, File\n\ntext_from_str = Str('some-string') # no copies, just a view\ntext_from_file = Str(File('some-file.txt')) # memory-mapped file\n```\n\nThe `File` class memory-maps a file from persistent memory without loading its copy into RAM.\nThe contents of that file would remain immutable, and the mapping can be shared by multiple Python processes simultaneously.\nA standard dataset pre-processing use case would be to map a sizeable textual dataset like Common Crawl into memory, spawn child processes, and split the job between them.\n\n### Basic Operations\n\n- Length: `len(text) -> int`\n- Indexing: `text[42] -> str`\n- Slicing: `text[42:46] -> Str`\n- Substring check: `'substring' in text -> bool`\n- Hashing: `hash(text) -> int`\n- String conversion: `str(text) -> str`\n\n### Advanced Operations\n\n```py\nimport sys\n\nx: bool = text.contains('substring', start=0, end=sys.maxsize)\nx: int = text.find('substring', start=0, end=sys.maxsize)\nx: int = text.count('substring', start=0, end=sys.maxsize, allowoverlap=False)\nx: str = text.decode(encoding='utf-8', errors='strict')\nx: Strs = text.split(separator=' ', maxsplit=sys.maxsize, keepseparator=False)\nx: Strs = text.rsplit(separator=' ', maxsplit=sys.maxsize, keepseparator=False)\nx: Strs = text.splitlines(keeplinebreaks=False, maxsplit=sys.maxsize)\n```\n\nIt's important to note, that the last function behavior is slightly different from Python's `str.splitlines`.\nThe [native version][faq-splitlines] matches `\\n`, `\\r`, `\\v` or `\\x0b`, `\\f` or `\\x0c`, `\\x1c`, `\\x1d`, `\\x1e`, `\\x85`, `\\r\\n`, `\\u2028`, `\\u2029`, including 3x two-bytes-long runes.\nThe StringZilla version matches only `\\n`, `\\v`, `\\f`, `\\r`, `\\x1c`, `\\x1d`, `\\x1e`, `\\x85`, avoiding two-byte-long runes.\n\n[faq-splitlines]: https://docs.python.org/3/library/stdtypes.html#str.splitlines\n\n### Character Set Operations\n\nPython strings don't natively support character set operations.\nThis forces people to use regular expressions, which are slow and hard to read.\nTo avoid the need for `re.finditer`, StringZilla provides the following interfaces:\n\n```py\nx: int = text.find_first_of('chars', start=0, end=sys.maxsize)\nx: int = text.find_last_of('chars', start=0, end=sys.maxsize)\nx: int = text.find_first_not_of('chars', start=0, end=sys.maxsize)\nx: int = text.find_last_not_of('chars', start=0, end=sys.maxsize)\nx: Strs = text.split_charset(separator='chars', maxsplit=sys.maxsize, keepseparator=False)\nx: Strs = text.rsplit_charset(separator='chars', maxsplit=sys.maxsize, keepseparator=False)\n```\n\n### Collection-Level Operations\n\nOnce split into a `Strs` object, you can sort, shuffle, and reorganize the slices, with minimum memory footprint.\nIf all the chunks are located in consecutive memory regions, the memory overhead can be as low as 4 bytes per chunk.\n\n```python\nlines: Strs = text.split(separator='\\n') # 4 bytes per line overhead for under 4 GB of text\nbatch: Strs = lines.sample(seed=42) # 10x faster than `random.choices`\nlines.shuffle(seed=42) # or shuffle all lines in place and shard with slices\n# WIP: lines.sort() # explodes to 16 bytes per line overhead for any length text\n# WIP: sorted_order: tuple = lines.argsort() # similar to `numpy.argsort`\n```\n\nWorking on [RedPajama][redpajama], addressing 20 Billion annotated english documents, one will need only 160 GB of RAM instead of Terabytes.\nOnce loaded, the data will be memory-mapped, and can be reused between multiple Python processes without copies.\nAnd of course, you can use slices to navigate the dataset and shard it between multiple workers.\n\n```python\nlines[::3] # every third line\nlines[1::1] # every odd line\nlines[:-100:-1] # last 100 lines in reverse order\n```\n\n[redpajama]: https://github.com/togethercomputer/RedPajama-Data\n\n### Iterators and Memory Efficiency\n\nPython's operations like `split()` and `readlines()` immediately materialize a `list` of copied parts.\nThis can be very memory-inefficient for large datasets.\nStringZilla saves a lot of memory by viewing existing memory regions as substrings, but even more memory can be saved by using lazily evaluated iterators.\n\n```py\nx: SplitIterator[Str] = text.split_iter(separator=' ', keepseparator=False)\nx: SplitIterator[Str] = text.rsplit_iter(separator=' ', keepseparator=False)\nx: SplitIterator[Str] = text.split_charset_iter(separator='chars', keepseparator=False)\nx: SplitIterator[Str] = text.rsplit_charset_iter(separator='chars', keepseparator=False)\n```\n\nStringZilla can easily be 10x more memory efficient than native Python classes for tokenization.\nWith lazy operations, it practically becomes free.\n\n```py\nimport stringzilla as sz\n%load_ext memory_profiler\n\ntext = open(\"enwik9.txt\", \"r\").read() # 1 GB, mean word length 7.73 bytes\n%memit text.split() # increment: 8670.12 MiB (152 ms)\n%memit sz.split(text) # increment: 530.75 MiB (25 ms)\n%memit sum(1 for _ in sz.split_iter(text)) # increment: 0.00 MiB\n```\n\n### Low-Level Python API\n\nAside from calling the methods on the `Str` and `Strs` classes, you can also call the global functions directly on `str` and `bytes` instances.\nAssuming StringZilla CPython bindings are implemented [without any intermediate tools like SWIG or PyBind](https://ashvardanian.com/posts/pybind11-cpython-tutorial/), the call latency should be similar to native classes.\n\n```py\nimport stringzilla as sz\n\ncontains: bool = sz.contains(\"haystack\", \"needle\", start=0, end=sys.maxsize)\noffset: int = sz.find(\"haystack\", \"needle\", start=0, end=sys.maxsize)\ncount: int = sz.count(\"haystack\", \"needle\", start=0, end=sys.maxsize, allowoverlap=False)\n```\n\n### Edit Distances\n\n```py\nassert sz.edit_distance(\"apple\", \"aple\") == 1 # skip one ASCII character\nassert sz.edit_distance(\"\u03b1\u03b2\u03b3\u03b4\", \"\u03b1\u03b3\u03b4\") == 2 # skip two bytes forming one rune\nassert sz.edit_distance_unicode(\"\u03b1\u03b2\u03b3\u03b4\", \"\u03b1\u03b3\u03b4\") == 1 # one unicode rune\n```\n\nSeveral Python libraries provide edit distance computation.\nMost of them are implemented in C, but are not always as fast as StringZilla.\nTaking a 1'000 long proteins around 10'000 characters long, computing just a 100 distances:\n\n- [JellyFish](https://github.com/jamesturk/jellyfish): 62.3s\n- [EditDistance](https://github.com/roy-ht/editdistance): 32.9s\n- StringZilla: __0.8s__\n\nMoreover, you can pass custom substitution matrices to compute the Needleman-Wunsch alignment scores.\nThat task is very common in bioinformatics and computational biology.\nIt's natively supported in BioPython, and its BLOSUM matrices can be converted to StringZilla's format.\nAlternatively, you can construct an arbitrary 256 by 256 cost matrix using NumPy.\nDepending on arguments, the result may be equal to the negative Levenshtein distance.\n\n```py\nimport numpy as np\nimport stringzilla as sz\n\ncosts = np.zeros((256, 256), dtype=np.int8)\ncosts.fill(-1)\nnp.fill_diagonal(costs, 0)\n\nassert sz.alignment_score(\"first\", \"second\", substitution_matrix=costs, gap_score=-1) == -sz.edit_distance(a, b)\n```\n\nUsing the same proteins as for Levenshtein distance benchmarks:\n\n- [BioPython](https://github.com/biopython/biopython): 25.8s\n- StringZilla: __7.8s__\n\n<details>\n  <summary><b>\u00a7 Example converting from BioPython to StringZilla.</b></summary>\n\n```py\nimport numpy as np\nfrom Bio import Align\nfrom Bio.Align import substitution_matrices\n\naligner = Align.PairwiseAligner()\naligner.substitution_matrix = substitution_matrices.load(\"BLOSUM62\")\naligner.open_gap_score = 1\naligner.extend_gap_score = 1\n\n# Convert the matrix to NumPy\nsubs_packed = np.array(aligner.substitution_matrix).astype(np.int8)\nsubs_reconstructed = np.zeros((256, 256), dtype=np.int8)\n\n# Initialize all banned characters to a the largest possible penalty\nsubs_reconstructed.fill(127)\nfor packed_row, packed_row_aminoacid in enumerate(aligner.substitution_matrix.alphabet):\n    for packed_column, packed_column_aminoacid in enumerate(aligner.substitution_matrix.alphabet):\n        reconstructed_row = ord(packed_row_aminoacid)\n        reconstructed_column = ord(packed_column_aminoacid)\n        subs_reconstructed[reconstructed_row, reconstructed_column] = subs_packed[packed_row, packed_column]\n\n# Let's pick two examples for of tri-peptides (made of 3 aminoacids)\nglutathione = \"ECG\" # Need to rebuild human tissue?\nthyrotropin_releasing_hormone = \"QHP\" # Or to regulate your metabolism?\n\nassert sz.alignment_score(\n    glutathione,\n    thyrotropin_releasing_hormone, \n    substitution_matrix=subs_reconstructed, \n    gap_score=1) == aligner.score(glutathione, thyrotropin_releasing_hormone) # Equal to 6\n```\n\n</details>\n\n### Serialization\n\n#### Filesystem\n\nSimilar to how `File` can be used to read a large file, other interfaces can be used to dump strings to disk faster.\nThe `Str` class has `write_to` to write the string to a file, and `offset_within` to obtain integer offsets of substring view in larger string for navigation.\n\n```py\nweb_archieve = Str(\"<html>...</html><html>...</html>\")\n_, end_tag, next_doc = web_archieve.partition(\"</html>\") # or use `find`\nnext_doc_offset = next_doc.offset_within(web_archieve)\nweb_archieve.write_to(\"next_doc.html\") # no GIL, no copies, just a view\n```\n\n#### PyArrow\n\nA `Str` is easy to cast to [PyArrow](https://arrow.apache.org/docs/python/arrays.html#string-and-binary-types) buffers.\n\n```py\nfrom pyarrow import foreign_buffer\nfrom stringzilla import Str\n\noriginal = \"hello\"\nview = Str(native)\narrow = foreign_buffer(view.address, view.nbytes, view)\n```\n\nThat means you can convert `Str` to `pyarrow.Buffer` and `Strs` to `pyarrow.Array` without extra copies.\n\n## Quick Start: C/C++ \ud83d\udee0\ufe0f\n\nThe C library is header-only, so you can just copy the `stringzilla.h` header into your project.\nSame applies to C++, where you would copy the `stringzilla.hpp` header.\nAlternatively, add it as a submodule, and include it in your build system.\n\n```sh\ngit submodule add https://github.com/ashvardanian/stringzilla.git\n```\n\nOr using a pure CMake approach:\n\n```cmake\nFetchContent_Declare(stringzilla GIT_REPOSITORY https://github.com/ashvardanian/stringzilla.git)\nFetchContent_MakeAvailable(stringzilla)\n```\n\nLast, but not the least, you can also install it as a library, and link against it.\nThis approach is worse for inlining, but brings dynamic runtime dispatch for the most advanced CPU features.\n\n### Basic Usage with C 99 and Newer\n\nThere is a stable C 99 interface, where all function names are prefixed with `sz_`.\nMost interfaces are well documented, and come with self-explanatory names and examples.\nIn some cases, hardware specific overloads are available, like `sz_find_avx512` or `sz_find_neon`.\nBoth are companions of the `sz_find`, first for x86 CPUs with AVX-512 support, and second for Arm NEON-capable CPUs.\n\n```c\n#include <stringzilla/stringzilla.h>\n\n// Initialize your haystack and needle\nsz_string_view_t haystack = {your_text, your_text_length};\nsz_string_view_t needle = {your_subtext, your_subtext_length};\n\n// Perform string-level operations\nsz_size_t substring_position = sz_find(haystack.start, haystack.length, needle.start, needle.length);\nsz_size_t substring_position = sz_find_avx512(haystack.start, haystack.length, needle.start, needle.length);\nsz_size_t substring_position = sz_find_neon(haystack.start, haystack.length, needle.start, needle.length);\n\n// Hash strings\nsz_u64_t hash = sz_hash(haystack.start, haystack.length);\n\n// Perform collection level operations\nsz_sequence_t array = {your_order, your_count, your_get_start, your_get_length, your_handle};\nsz_sort(&array, &your_config);\n```\n\n<details>\n  <summary><b>\u00a7 Mapping from LibC to StringZilla.</b></summary>\n\nBy design, StringZilla has a couple of notable differences from LibC:\n\n1. all strings are expected to have a length, and are not necessarily null-terminated.\n2. every operations has a reverse order counterpart.\n\nThat way `sz_find` and `sz_rfind` are similar to `strstr` and `strrstr` in LibC.\nSimilarly, `sz_find_byte` and `sz_rfind_byte` replace `memchr` and `memrchr`.\nThe `sz_find_charset` maps to `strspn` and `strcspn`, while `sz_rfind_charset` has no sibling in LibC.\n\n<table>\n    <tr>\n        <th>LibC Functionality</th>\n        <th>StringZilla Equivalents</th>\n    </tr>\n    <tr>\n        <td><code>memchr(haystack, needle, haystack_length)</code>, <code>strchr</code></td>\n        <td><code>sz_find_byte(haystack, haystack_length, needle)</code></td>\n    </tr>\n    <tr>\n        <td><code>memrchr(haystack, needle, haystack_length)</code></td>\n        <td><code>sz_rfind_byte(haystack, haystack_length, needle)</code></td>\n    </tr>\n    <tr>\n        <td><code>memcmp</code>, <code>strcmp</code></td>\n        <td><code>sz_order</code>, <code>sz_equal</code></td>\n    </tr>\n    <tr>\n        <td><code>strlen(haystack)</code></td>\n        <td><code>sz_find_byte(haystack, haystack_length, needle)</code></td>\n    </tr>\n    <tr>\n        <td><code>strcspn(haystack, needles)</code></td>\n        <td><code>sz_rfind_charset(haystack, haystack_length, needles_bitset)</code></td>\n    </tr>\n    <tr>\n        <td><code>strspn(haystack, needles)</code></td>\n        <td><code>sz_find_charset(haystack, haystack_length, needles_bitset)</code></td>\n    </tr>\n    <tr>\n        <td><code>memmem(haystack, haystack_length, needle, needle_length)</code>, <code>strstr</code></td>\n        <td><code>sz_find(haystack, haystack_length, needle, needle_length)</code></td>\n    </tr>\n    <tr>\n        <td><code>memcpy(destination, source, destination_length)</code></td>\n        <td><code>sz_copy(destination, source, destination_length)</code></td>\n    </tr>\n    <tr>\n        <td><code>memmove(destination, source, destination_length)</code></td>\n        <td><code>sz_move(destination, source, destination_length)</code></td>\n    </tr>\n    <tr>\n        <td><code>memset(destination, value, destination_length)</code></td>\n        <td><code>sz_fill(destination, destination_length, value)</code></td>\n    </tr>\n</table>\n\n</details>\n\n### Basic Usage with C++ 11 and Newer\n\nThere is a stable C++ 11 interface available in the `ashvardanian::stringzilla` namespace.\nIt comes with two STL-like classes: `string_view` and `string`.\nThe first is a non-owning view of a string, and the second is a mutable string with a [Small String Optimization][faq-sso].\n\n```cpp\n#include <stringzilla/stringzilla.hpp>\n\nnamespace sz = ashvardanian::stringzilla;\n\nsz::string haystack = \"some string\";\nsz::string_view needle = sz::string_view(haystack).substr(0, 4);\n\nauto substring_position = haystack.find(needle); // Or `rfind`\nauto hash = std::hash<sz::string_view>(haystack); // Compatible with STL's `std::hash`\n\nhaystack.end() - haystack.begin() == haystack.size(); // Or `rbegin`, `rend`\nhaystack.find_first_of(\" \\w\\t\") == 4; // Or `find_last_of`, `find_first_not_of`, `find_last_not_of`\nhaystack.starts_with(needle) == true; // Or `ends_with`\nhaystack.remove_prefix(needle.size()); // Why is this operation in-place?!\nhaystack.contains(needle) == true; // STL has this only from C++ 23 onwards\nhaystack.compare(needle) == 1; // Or `haystack <=> needle` in C++ 20 and beyond\n```\n\nStringZilla also provides string literals for automatic type resolution, [similar to STL][stl-literal]:\n\n```cpp\nusing sz::literals::operator\"\"_sz;\nusing std::literals::operator\"\"sv;\n\nauto a = \"some string\"; // char const *\nauto b = \"some string\"sv; // std::string_view\nauto b = \"some string\"_sz; // sz::string_view\n```\n\n[stl-literal]: https://en.cppreference.com/w/cpp/string/basic_string_view/operator%22%22sv\n\n### Memory Ownership and Small String Optimization\n\nMost operations in StringZilla don't assume any memory ownership.\nBut in addition to the read-only search-like operations StringZilla provides a minimalistic C and C++ implementations for a memory owning string \"class\".\nLike other efficient string implementations, it uses the [Small String Optimization][faq-sso] (SSO) to avoid heap allocations for short strings.\n\n[faq-sso]: https://cpp-optimizations.netlify.app/small_strings/\n\n```c\ntypedef union sz_string_t {\n    struct internal {\n        sz_ptr_t start;\n        sz_u8_t length;\n        char chars[SZ_STRING_INTERNAL_SPACE]; /// Ends with a null-terminator.\n    } internal;\n\n    struct external {\n        sz_ptr_t start;\n        sz_size_t length;        \n        sz_size_t space; /// The length of the heap-allocated buffer.\n        sz_size_t padding;\n    } external;\n\n} sz_string_t;\n```\n\nAs one can see, a short string can be kept on the stack, if it fits within `internal.chars` array.\nBefore 2015 GCC string implementation was just 8 bytes, and could only fit 7 characters.\nDifferent STL implementations today have different thresholds for the Small String Optimization.\nSimilar to GCC, StringZilla is 32 bytes in size, and similar to Clang it can fit 22 characters on stack.\nOur layout might be preferential, if you want to avoid branches.\nIf you use a different compiler, you may want to check it's SSO buffer size with a [simple Gist](https://gist.github.com/ashvardanian/c197f15732d9855c4e070797adf17b21).\n\n|                       | `libstdc++` in  GCC 13 | `libc++` in Clang 17 | StringZilla |\n| :-------------------- | ---------------------: | -------------------: | ----------: |\n| `sizeof(std::string)` |                     32 |                   24 |          32 |\n| Small String Capacity |                     15 |               __22__ |      __22__ |\n\nThis design has been since ported to many high-level programming languages.\nSwift, for example, [can store 15 bytes](https://developer.apple.com/documentation/swift/substring/withutf8(_:)#discussion) in the `String` instance itself.\nStringZilla implements SSO at the C level, providing the `sz_string_t` union and a simple API for primary operations.\n\n```c\nsz_memory_allocator_t allocator;\nsz_string_t string;\n\n// Init and make sure we are on stack\nsz_string_init(&string);\nsz_string_is_on_stack(&string); // == sz_true_k\n\n// Optionally pre-allocate space on the heap for future insertions.\nsz_string_grow(&string, 100, &allocator); // == sz_true_k\n\n// Append, erase, insert into the string.\nsz_string_expand(&string, 0, \"_Hello_\", 7, &allocator); // == sz_true_k\nsz_string_expand(&string, SZ_SIZE_MAX, \"world\", 5, &allocator); // == sz_true_k\nsz_string_erase(&string, 0, 1);\n\n// Unpacking & introspection.\nsz_ptr_t string_start;\nsz_size_t string_length;\nsz_size_t string_space;\nsz_bool_t string_is_external;\nsz_string_unpack(string, &string_start, &string_length, &string_space, &string_is_external);\nsz_equal(string_start, \"Hello_world\", 11); // == sz_true_k\n\n// Reclaim some memory.\nsz_string_shrink_to_fit(&string, &allocator); // == sz_true_k\nsz_string_free(&string, &allocator);\n```\n\nUnlike the conventional C strings, the `sz_string_t` is allowed to contain null characters.\nTo safely print those, pass the `string_length` to `printf` as well.\n\n```c\nprintf(\"%.*s\\n\", (int)string_length, string_start);\n```\n\n### What's Wrong with the C++ Standard Library?\n\n| C++ Code                             | Evaluation Result | Invoked Signature              |\n| :----------------------------------- | :---------------- | :----------------------------- |\n| `\"Loose\"s.replace(2, 2, \"vath\"s, 1)` | `\"Loathe\"` \ud83e\udd22      | `(pos1, count1, str2, pos2)`   |\n| `\"Loose\"s.replace(2, 2, \"vath\", 1)`  | `\"Love\"` \ud83e\udd70        | `(pos1, count1, str2, count2)` |\n\nStringZilla is designed to be a drop-in replacement for the C++ Standard Templates Library.\nThat said, some of the design decisions of STL strings are highly controversial, error-prone, and expensive.\nMost notably:\n\n1. Argument order for `replace`, `insert`, `erase` and similar functions is impossible to guess.\n2. Bounds-checking exceptions for `substr`-like functions are only thrown for one side of the range.\n3. Returning string copies in `substr`-like functions results in absurd volume of allocations.\n4. Incremental construction via `push_back`-like functions goes through too many branches.\n5. Inconsistency between `string` and `string_view` methods, like the lack of `remove_prefix` and `remove_suffix`.\n\nCheck the following set of asserts validating the `std::string` specification.\nIt's not realistic to expect the average developer to remember the [14 overloads of `std::string::replace`][stl-replace].\n\n[stl-replace]: https://en.cppreference.com/w/cpp/string/basic_string/replace\n\n```cpp\nusing str = std::string;\n\nassert(str(\"hello world\").substr(6) == \"world\");\nassert(str(\"hello world\").substr(6, 100) == \"world\"); // 106 is beyond the length of the string, but its OK\nassert_throws(str(\"hello world\").substr(100), std::out_of_range);   // 100 is beyond the length of the string\nassert_throws(str(\"hello world\").substr(20, 5), std::out_of_range); // 20 is beyond the length of the string\nassert_throws(str(\"hello world\").substr(-1, 5), std::out_of_range); // -1 casts to unsigned without any warnings...\nassert(str(\"hello world\").substr(0, -1) == \"hello world\");          // -1 casts to unsigned without any warnings...\n\nassert(str(\"hello\").replace(1, 2, \"123\") == \"h123lo\");\nassert(str(\"hello\").replace(1, 2, str(\"123\"), 1) == \"h23lo\");\nassert(str(\"hello\").replace(1, 2, \"123\", 1) == \"h1lo\");\nassert(str(\"hello\").replace(1, 2, \"123\", 1, 1) == \"h2lo\");\nassert(str(\"hello\").replace(1, 2, str(\"123\"), 1, 1) == \"h2lo\");\nassert(str(\"hello\").replace(1, 2, 3, 'a') == \"haaalo\");\nassert(str(\"hello\").replace(1, 2, {'a', 'b'}) == \"hablo\");\n```\n\nTo avoid those issues, StringZilla provides an alternative consistent interface.\nIt supports signed arguments, and doesn't have more than 3 arguments per function or\nThe standard API and our alternative can be conditionally disabled with `SZ_SAFETY_OVER_COMPATIBILITY=1`.\nWhen it's enabled, the _~~subjectively~~_ risky overloads from the Standard will be disabled.\n\n```cpp\nusing str = sz::string;\n\nstr(\"a:b\").front(1) == \"a\"; // no checks, unlike `substr`\nstr(\"a:b\").back(-1) == \"b\"; // accepting negative indices\nstr(\"a:b\").sub(1, -1) == \":\"; // similar to Python's `\"a:b\"[1:-1]`\nstr(\"a:b\").sub(-2, -1) == \":\"; // similar to Python's `\"a:b\"[-2:-1]`\nstr(\"a:b\").sub(-2, 1) == \"\"; // similar to Python's `\"a:b\"[-2:1]`\n\"a:b\"_sz[{-2, -1}] == \":\"; // works on views and overloads `operator[]`\n```\n\nAssuming StringZilla is a header-only library you can use the full API in some translation units and gradually transition to safer restricted API in others.\nBonus - all the bound checking is branchless, so it has a constant cost and won't hurt your branch predictor.\n\n### Beyond the C++ Standard Library - Learning from Python\n\nPython is arguably the most popular programming language for data science.\nIn part, that's due to the simplicity of its standard interfaces.\nStringZilla brings some of that functionality to C++.\n\n- Content checks: `isalnum`, `isalpha`, `isascii`, `isdigit`, `islower`, `isspace`, `isupper`.\n- Trimming character sets: `lstrip`, `rstrip`, `strip`.\n- Trimming string matches: `remove_prefix`, `remove_suffix`.\n- Ranges of search results: `splitlines`, `split`, `rsplit`.\n- Number of non-overlapping substring matches: `count`.\n- Partitioning: `partition`, `rpartition`.\n\nFor example, when parsing documents, it is often useful to split it into substrings.\nMost often, after that, you would compute the length of the skipped part, the offset and the length of the remaining part.\nThis results in a lot of pointer arithmetic and is error-prone.\nStringZilla provides a convenient `partition` function, which returns a tuple of three string views, making the code cleaner.\n\n```cpp\nauto parts = haystack.partition(':'); // Matching a character\nauto [before, match, after] = haystack.partition(':'); // Structure unpacking\nauto [before, match, after] = haystack.partition(char_set(\":;\")); // Character-set argument\nauto [before, match, after] = haystack.partition(\" : \"); // String argument\nauto [before, match, after] = haystack.rpartition(sz::whitespaces); // Split around the last whitespace\n```\n\nCombining those with the `split` function, one can easily parse a CSV file or HTTP headers.\n\n```cpp\nfor (auto line : haystack.split(\"\\r\\n\")) {\n    auto [key, _, value] = line.partition(':');\n    headers[key.strip()] = value.strip();\n}\n```\n\nSome other extensions are not present in the Python standard library either.\nLet's go through the C++ functionality category by category.\n\n- [Splits and Ranges](#splits-and-ranges).\n- [Concatenating Strings without Allocations](#concatenating-strings-without-allocations).\n- [Random Generation](#random-generation).\n- [Edit Distances and Fuzzy Search](#levenshtein-edit-distance-and-alignment-scores).\n\nSome of the StringZilla interfaces are not available even Python's native `str` class.\nHere is a sneak peek of the most useful ones.\n\n```cpp\ntext.hash(); // -> 64 bit unsigned integer \ntext.ssize(); // -> 64 bit signed length to avoid `static_cast<std::ssize_t>(text.size())`\ntext.contains_only(\" \\w\\t\"); // == text.find_first_not_of(char_set(\" \\w\\t\")) == npos;\ntext.contains(sz::whitespaces); // == text.find(char_set(sz::whitespaces)) != npos;\n\n// Simpler slicing than `substr`\ntext.front(10); // -> sz::string_view\ntext.back(10); // -> sz::string_view\n\n// Safe variants, which clamp the range into the string bounds\nusing sz::string::cap;\ntext.front(10, cap) == text.front(std::min(10, text.size()));\ntext.back(10, cap) == text.back(std::min(10, text.size()));\n\n// Character set filtering\ntext.lstrip(sz::whitespaces).rstrip(sz::newlines); // like Python\ntext.front(sz::whitespaces); // all leading whitespaces\ntext.back(sz::digits); // all numerical symbols forming the suffix\n\n// Incremental construction\nusing sz::string::unchecked;\ntext.push_back('x'); // no surprises here\ntext.push_back('x', unchecked); // no bounds checking, Rust style\ntext.try_push_back('x'); // returns `false` if the string is full and the allocation failed\n\nsz::concatenate(text, \"@\", domain, \".\", tld); // No allocations\n```\n\n### Splits and Ranges\n\nOne of the most common use cases is to split a string into a collection of substrings.\nWhich would often result in [StackOverflow lookups][so-split] and snippets like the one below.\n\n[so-split]: https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c\n\n```cpp\nstd::vector<std::string> lines = split(haystack, \"\\r\\n\"); // string delimiter\nstd::vector<std::string> words = split(lines, ' '); // character delimiter\n```\n\nThose allocate memory for each string and the temporary vectors.\nEach allocation can be orders of magnitude more expensive, than even serial `for`-loop over characters.\nTo avoid those, StringZilla provides lazily-evaluated ranges, compatible with the [Range-v3][range-v3] library.\n\n[range-v3]: https://github.com/ericniebler/range-v3\n\n```cpp\nfor (auto line : haystack.split(\"\\r\\n\"))\n    for (auto word : line.split(char_set(\" \\w\\t.,;:!?\")))\n        std::cout << word << std::endl;\n```\n\nEach of those is available in reverse order as well.\nIt also allows interleaving matches, if you want both inclusions of `xx` in `xxx`.\nDebugging pointer offsets is not a pleasant exercise, so keep the following functions in mind.\n\n- `haystack.[r]find_all(needle, interleaving)`\n- `haystack.[r]find_all(char_set(\"\"))`\n- `haystack.[r]split(needle)`\n- `haystack.[r]split(char_set(\"\"))`\n\nFor $N$ matches the split functions will report $N+1$ matches, potentially including empty strings.\nRanges have a few convenience methods as well:\n\n```cpp\nrange.size(); // -> std::size_t\nrange.empty(); // -> bool\nrange.template to<std::set<std::sting>>(); \nrange.template to<std::vector<std::sting_view>>(); \n```\n\n### Concatenating Strings without Allocations\n\nAnother common string operation is concatenation.\nThe STL provides `std::string::operator+` and `std::string::append`, but those are not very efficient, if multiple invocations are performed.\n\n```cpp\nstd::string name, domain, tld;\nauto email = name + \"@\" + domain + \".\" + tld; // 4 allocations\n```\n\nThe efficient approach would be to pre-allocate the memory and copy the strings into it.\n\n```cpp\nstd::string email;\nemail.reserve(name.size() + domain.size() + tld.size() + 2);\nemail.append(name), email.append(\"@\"), email.append(domain), email.append(\".\"), email.append(tld);\n```\n\nThat's mouthful and error-prone.\nStringZilla provides a more convenient `concatenate` function, which takes a variadic number of arguments.\nIt also overrides the `operator|` to concatenate strings lazily, without any allocations.\n\n```cpp\nauto email = sz::concatenate(name, \"@\", domain, \".\", tld);   // 0 allocations\nauto email = name | \"@\" | domain | \".\" | tld;                // 0 allocations\nsz::string email = name | \"@\" | domain | \".\" | tld;          // 1 allocations\n```\n\n### Random Generation\n\nSoftware developers often need to generate random strings for testing purposes.\nThe STL provides `std::generate` and `std::random_device`, that can be used with StringZilla.\n\n```cpp\nsz::string random_string(std::size_t length, char const *alphabet, std::size_t cardinality) {\n    sz::string result(length, '\\0');\n    static std::random_device seed_source; // Too expensive to construct every time\n    std::mt19937 generator(seed_source());\n    std::uniform_int_distribution<std::size_t> distribution(0, cardinality);\n    std::generate(result.begin(), result.end(), [&]() { return alphabet[distribution(generator)]; });\n    return result;\n}\n```\n\nMouthful and slow.\nStringZilla provides a C native method - `sz_generate` and a convenient C++ wrapper - `sz::generate`.\nSimilar to Python it also defines the commonly used character sets.\n\n```cpp\nauto protein = sz::string::random(300, \"ARNDCQEGHILKMFPSTWYV\"); // static method\nauto dna = sz::basic_string<custom_allocator>::random(3_000_000_000, \"ACGT\");\n\ndna.randomize(\"ACGT\"); // `noexcept` pre-allocated version\ndna.randomize(&std::rand, \"ACGT\"); // pass any generator, like `std::mt19937`\n\nchar uuid[36];\nsz::randomize(sz::string_span(uuid, 36), \"0123456789abcdef-\"); // Overwrite any buffer\n```\n\n### Levenshtein Edit Distance and Alignment Scores\n\nLevenshtein and Hamming edit distance are provided for both byte-strings and UTF-8 strings.\nThe latter will output the distance in Unicode code points, not bytes.\nNeedleman-Wunsch alignment scores are only defined for byte-strings.\n\n```cpp\n// Count number of substitutions in same length strings\nsz::hamming_distance(first, second[, upper_bound]) -> std::size_t;\nsz::hamming_distance_utf8(first, second[, upper_bound]) -> std::size_t;\n\n// Count number of insertions, deletions and substitutions\nsz::edit_distance(first, second[, upper_bound[, allocator]]) -> std::size_t;\nsz::edit_distance_utf8(first, second[, upper_bound[, allocator]]) -> std::size_t;\n\n// Substitution-parametrized Needleman-Wunsch global alignment score\nstd::int8_t costs[256][256]; // Substitution costs matrix\nsz::alignment_score(first, second, costs[, gap_score[, allocator]) -> std::ptrdiff_t;\n```\n\n### Sorting in C and C++\n\nLibC provides `qsort` and STL provides `std::sort`.\nBoth have their quarks.\nThe LibC standard has no way to pass a context to the comparison function, that's only possible with platform-specific extensions.\nThose have [different arguments order](https://stackoverflow.com/a/39561369) on every OS.\n\n```c\n// Linux: https://linux.die.net/man/3/qsort_r\nvoid qsort_r(void *elements, size_t count, size_t element_width, \n    int (*compare)(void const *left, void const *right, void *context),\n    void *context);\n// MacOS and FreeBSD: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/qsort_r.3.html\nvoid qsort_r(void *elements, size_t count, size_t element_width, \n    void *context,\n    int (*compare)(void *context, void const *left, void const *right));\n// Windows conflicts with ISO `qsort_s`: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/qsort-s?view=msvc-170\nvoid qsort_s(id *elements, size_t count, size_t element_width, \n    int (*compare)(void *context, void const *left, void const *right),\n    void *context);\n```\n\nC++ generic algorithm is not perfect either.\nThere is no guarantee in the standard that `std::sort` won't allocate any memory.\nIf you are running on embedded, in real-time or on 100+ CPU cores per node, you may want to avoid that.\nStringZilla doesn't solve the general case, but hopes to improve the performance for strings.\nUse `sz_sort`, or the high-level `sz::sorted_order`, which can be used sort any collection of elements convertible to `sz::string_view`.\n\n```cpp\nstd::vector<std::string> data({\"c\", \"b\", \"a\"});\nstd::vector<std::size_t> order = sz::sorted_order(data); //< Simple shortcut\n\n// Or, taking care of memory allocation:\nsz::sorted_order(data.begin(), data.end(), order.data(), [](auto const &x) -> sz::string_view { return x; });\n```\n\n### Standard C++ Containers with String Keys\n\nThe C++ Standard Templates Library provides several associative containers, often used with string keys.\n\n```cpp\nstd::map<std::string, int, std::less<std::string>> sorted_words;\nstd::unordered_map<std::string, int, std::hash<std::string>, std::equal_to<std::string>> words;\n```\n\nThe performance of those containers is often limited by the performance of the string keys, especially on reads.\nStringZilla can be used to accelerate containers with `std::string` keys, by overriding the default comparator and hash functions.\n\n```cpp\nstd::map<std::string, int, sz::string_view_less> sorted_words;\nstd::unordered_map<std::string, int, sz::string_view_hash, sz::string_view_equal_to> words;\n```\n\nAlternatively, a better approach would be to use the `sz::string` class as a key.\nThe right hash function and comparator would be automatically selected and the performance gains would be more noticeable if the keys are short.\n\n```cpp\nstd::map<sz::string, int> sorted_words;\nstd::unordered_map<sz::string, int> words;\n```\n\n### Compilation Settings and Debugging\n\n__`SZ_DEBUG`__:\n\n> For maximal performance, the C library does not perform any bounds checking in Release builds.\n> In C++, bounds checking happens only in places where the STL `std::string` would do it.\n> If you want to enable more aggressive bounds-checking, define `SZ_DEBUG` before including the header.\n> If not explicitly set, it will be inferred from the build type.\n\n__`SZ_USE_X86_AVX512`, `SZ_USE_X86_AVX2`, `SZ_USE_ARM_NEON`__:\n\n> One can explicitly disable certain families of SIMD instructions for compatibility purposes.\n> Default values are inferred at compile time.\n\n__`SZ_DYNAMIC_DISPATCH`__:\n\n> By default, StringZilla is a header-only library.\n> But if you are running on different generations of devices, it makes sense to pre-compile the library for all supported generations at once, and dispatch at runtime.\n> This flag does just that and is used to produce the `stringzilla.so` shared library, as well as the Python bindings.\n\n__`SZ_USE_MISALIGNED_LOADS`__:\n\n> By default, StringZilla avoids misaligned loads.\n> If supported, it replaces many byte-level operations with word-level ones.\n> Going from `char`-like types to `uint64_t`-like ones can significantly accelerate the serial (SWAR) backend.\n> So consider enabling it if you are building for some embedded device.\n\n__`SZ_AVOID_LIBC`__ and __`SZ_OVERRIDE_LIBC`__:\n\n> When using the C header-only library one can disable the use of LibC.\n> This may affect the type resolution system on obscure hardware platforms. \n> Moreover, one may let `stringzilla` override the common symbols like the `memcpy` and `memset` with its own implementations.\n> In that case you can use the [`LD_PRELOAD` trick][ld-preload-trick] to prioritize it's symbols over the ones from the LibC and accelerate existing string-heavy applications without recompiling them.\n\n[ld-preload-trick]: https://ashvardanian.com/posts/ld-preload-libsee\n\n__`SZ_AVOID_STL`__ and __`SZ_SAFETY_OVER_COMPATIBILITY`__:\n\n> When using the C++ interface one can disable implicit conversions from `std::string` to `sz::string` and back.\n> If not needed, the `<string>` and `<string_view>` headers will be excluded, reducing compilation time.\n> Moreover, if STL compatibility is a low priority, one can make the API safer by disabling the overloads, which are subjectively error prone.\n\n__`STRINGZILLA_BUILD_SHARED`, `STRINGZILLA_BUILD_TEST`, `STRINGZILLA_BUILD_BENCHMARK`, `STRINGZILLA_TARGET_ARCH`__ for CMake users:\n\n> When compiling the tests and benchmarks, you can explicitly set the target hardware architecture.\n> It's synonymous to GCC's `-march` flag and is used to enable/disable the appropriate instruction sets.\n> You can also disable the shared library build, if you don't need it.\n\n## Quick Start: Rust \ud83e\udd80\n\nStringZilla is available as a Rust crate, with documentation available on [docs.rs/stringzilla](https://docs.rs/stringzilla).\nTo use the latest crate release in your project, add the following to your `Cargo.toml`:\n\n```toml\n[dependencies]\nstringzilla = \">=3\"\n```\n\nOr if you want to use the latest pre-release version from the repository:\n\n```toml\n[dependencies]\nstringzilla = { git = \"https://github.com/ashvardanian/stringzilla\", branch = \"main-dev\" }\n```\n\nOnce installed, all of the functionality is available through the `stringzilla` namespace.\nMany interfaces will look familiar to the users of the `memchr` crate.\n\n```rust\nuse stringzilla::sz;\n\n// Identical to `memchr::memmem::find` and `memchr::memmem::rfind` functions\nsz::find(\"Hello, world!\", \"world\") // 7\nsz::rfind(\"Hello, world!\", \"world\") // 7\n\n// Generalizations of `memchr::memrchr[123]`\nsz::find_char_from(\"Hello, world!\", \"world\") // 2\nsz::rfind_char_from(\"Hello, world!\", \"world\") // 11\n```\n\nUnlike `memchr`, the throughput of `stringzilla` is [high in both normal and reverse-order searches][memchr-benchmarks].\nIt also provides no constraints on the size of the character set, while `memchr` allows only 1, 2, or 3 characters.\nIn addition to global functions, `stringzilla` provides a `StringZilla` extension trait:\n\n```rust\nuse stringzilla::StringZilla;\n\nlet my_string: String = String::from(\"Hello, world!\");\nlet my_str = my_string.as_str();\nlet my_cow_str = Cow::from(&my_string);\n\n// Use the generic function with a String\nassert_eq!(my_string.sz_find(\"world\"), Some(7));\nassert_eq!(my_string.sz_rfind(\"world\"), Some(7));\nassert_eq!(my_string.sz_find_char_from(\"world\"), Some(2));\nassert_eq!(my_string.sz_rfind_char_from(\"world\"), Some(11));\nassert_eq!(my_string.sz_find_char_not_from(\"world\"), Some(0));\nassert_eq!(my_string.sz_rfind_char_not_from(\"world\"), Some(12));\n\n// Same works for &str and Cow<'_, str>\nassert_eq!(my_str.sz_find(\"world\"), Some(7));\nassert_eq!(my_cow_str.as_ref().sz_find(\"world\"), Some(7));\n```\n\nThe library also exposes Levenshtein and Hamming edit-distances for byte-arrays and UTF-8 strings, as well as Needleman-Wunch alignment scores.\n\n```rust\nuse stringzilla::sz;\n\n// Handling arbitrary byte arrays:\nsz::edit_distance(\"Hello, world!\", \"Hello, world?\"); // 1\nsz::hamming_distance(\"Hello, world!\", \"Hello, world?\"); // 1\nsz::alignment_score(\"Hello, world!\", \"Hello, world?\", sz::unary_substitution_costs(), -1); // -1\n\n// Handling UTF-8 strings:\nsz::hamming_distance_utf8(\"\u03b1\u03b2\u03b3\u03b4\", \"\u03b1\u03b3\u03b3\u03b4\") // 1\nsz::edit_distance_utf8(\"fa\u00e7ade\", \"facade\") // 1\n```\n\n[memchr-benchmarks]: https://github.com/ashvardanian/memchr_vs_stringzilla\n\n## Quick Start: Swift \ud83c\udf4f\n\nStringZilla can be added as a dependency in the Swift Package Manager.\nIn your `Package.swift` file, add the following:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/ashvardanian/stringzilla\")\n]\n```\n\nThe package currently covers only the most basic functionality, but is planned to be extended to cover the full C++ API.\n\n```swift\nvar s = \"Hello, world! Welcome to StringZilla. \ud83d\udc4b\"\ns[s.findFirst(substring: \"world\")!...] // \"world! Welcome to StringZilla. \ud83d\udc4b\")    \ns[s.findLast(substring: \"o\")!...] // \"o StringZilla. \ud83d\udc4b\")\ns[s.findFirst(characterFrom: \"aeiou\")!...] // \"ello, world! Welcome to StringZilla. \ud83d\udc4b\")\ns[s.findLast(characterFrom: \"aeiou\")!...] // \"a. \ud83d\udc4b\")\ns[s.findFirst(characterNotFrom: \"aeiou\")!...] // \"Hello, world! Welcome to StringZilla. \ud83d\udc4b\"\ns.editDistance(from: \"Hello, world!\")! // 29\n```\n\n## Algorithms & Design Decisions \ud83d\udcda\n\nStringZilla aims to optimize some of the slowest string operations.\nSome popular operations, however, like equality comparisons and relative order checking, almost always complete on some of the very first bytes in either string.\nIn such operations vectorization is almost useless, unless huge and very similar strings are considered.\nStringZilla implements those operations as well, but won't result in substantial speedups.\n\n### Exact Substring Search\n\nSubstring search algorithms are generally divided into: comparison-based, automaton-based, and bit-parallel.\nDifferent families are effective for different alphabet sizes and needle lengths.\nThe more operations are needed per-character - the more effective SIMD would be.\nThe longer the needle - the more effective the skip-tables are.\nStringZilla uses different exact substring search algorithms for different needle lengths and backends:\n\n- When no SIMD is available - SWAR (SIMD Within A Register) algorithms are used on 64-bit words.\n- Boyer-Moore-Horspool (BMH) algorithm with Raita heuristic variation for longer needles.\n- SIMD algorithms are randomized to look at different parts of the needle.\n\nOn very short needles, especially 1-4 characters long, brute force with SIMD is the fastest solution.\nOn mid-length needles, bit-parallel algorithms are effective, as the character masks fit into 32-bit or 64-bit words.\nEither way, if the needle is under 64-bytes long, on haystack traversal we will still fetch every CPU cache line.\nSo the only way to improve performance is to reduce the number of comparisons.\nThe snippet below shows how StringZilla accomplishes that for needles of length two.\n\nhttps://github.com/ashvardanian/StringZilla/blob/266c01710dddf71fc44800f36c2f992ca9735f87/include/stringzilla/stringzilla.h#L1585-L1637\n\nGoing beyond that, to long needles, Boyer-Moore (BM) and its variants are often the best choice.\nIt has two tables: the good-suffix shift and the bad-character shift.\nCommon choice is to use the simplified BMH algorithm, which only uses the bad-character shift table, reducing the pre-processing time.\nWe do the same for mid-length needles up to 256 bytes long.\nThat way the stack-allocated shift table remains small.\n\nhttps://github.com/ashvardanian/StringZilla/blob/46e957cd4f9ecd4945318dd3c48783dd11323f37/include/stringzilla/stringzilla.h#L1774-L1825\n\nIn the C++ Standards Library, the `std::string::find` function uses the BMH algorithm with Raita's heuristic.\nBefore comparing the entire string, it matches the first, last, and the middle character.\nVery practical, but can be slow for repetitive characters.\nBoth SWAR and SIMD backends of StringZilla have a cheap pre-processing step, where we locate unique characters.\nThis makes the library a lot more practical when dealing with non-English corpora.\n\nhttps://github.com/ashvardanian/StringZilla/blob/46e957cd4f9ecd4945318dd3c48783dd11323f37/include/stringzilla/stringzilla.h#L1398-L1431\n\nAll those, still, have $O(hn)$ worst case complexity.\nTo guarantee $O(h)$ worst case time complexity, the Apostolico-Giancarlo (AG) algorithm adds an additional skip-table.\nPreprocessing phase is $O(n+sigma)$ in time and space.\nOn traversal, performs from $(h/n)$ to $(3h/2)$ comparisons.\nIt however, isn't practical on modern CPUs.\nA simpler idea, the Galil-rule might be a more relevant optimizations, if many matches must be found.\n\nOther algorithms previously considered and deprecated:\n\n- Apostolico-Giancarlo algorithm for longer needles. _Control-flow is too complex for efficient vectorization._\n- Shift-Or-based Bitap algorithm for short needles. _Slower than SWAR._\n- Horspool-style bad-character check in SIMD backends. _Effective only for very long needles, and very uneven character distributions between the needle and the haystack. Faster \"character-in-set\" check needed to generalize._\n\n> \u00a7 Reading materials.\n> [Exact String Matching Algorithms in Java](https://www-igm.univ-mlv.fr/~lecroq/string).\n> [SIMD-friendly algorithms for substring searching](http://0x80.pl/articles/simd-strfind.html).\n\n### Levenshtein Edit Distance\n\nLevenshtein distance is the best known edit-distance for strings, that checks, how many insertions, deletions, and substitutions are needed to transform one string to another.\nIt's extensively used in approximate string-matching, spell-checking, and bioinformatics.\n\nThe computational cost of the Levenshtein distance is $O(n * m)$, where $n$ and $m$ are the lengths of the string arguments.\nTo compute that, the naive approach requires $O(n * m)$ space to store the \"Levenshtein matrix\", the bottom-right corner of which will contain the Levenshtein distance.\nThe algorithm producing the matrix has been simultaneously studied/discovered by the Soviet mathematicians Vladimir Levenshtein in 1965, Taras Vintsyuk in 1968, and American computer scientists - Robert Wagner, David Sankoff, Michael J. Fischer in the following years.\nSeveral optimizations are known:\n\n1. __Space Optimization__: The matrix can be computed in $O(min(n,m))$ space, by only storing the last two rows of the matrix.\n2. __Divide and Conquer__: Hirschberg's algorithm can be applied to decompose the computation into subtasks.\n3. __Automata__: Levenshtein automata can be effective, if one of the strings doesn't change, and is a subject to many comparisons.\n4. __Shift-Or__: Bit-parallel algorithms transpose the matrix into a bit-matrix, and perform bitwise operations on it.\n\nThe last approach is quite powerful and performant, and is used by the great [RapidFuzz][rapidfuzz] library.\nIt's less known, than the others, derived from the Baeza-Yates-Gonnet algorithm, extended to bounded edit-distance search by Manber and Wu in 1990s, and further extended by Gene Myers in 1999 and Heikki Hyyro between 2002 and 2004.\n\nStringZilla introduces a different approach, extensively used in Unum's internal combinatorial optimization libraries.\nThe approach doesn't change the number of trivial operations, but performs them in a different order, removing the data dependency, that occurs when computing the insertion costs.\nThis results in much better vectorization for intra-core parallelism and potentially multi-core evaluation of a single request.\n\nNext design goals:\n\n- [ ] Generalize fast traversals to rectangular matrices.\n- [ ] Port x86 AVX-512 solution to Arm NEON.\n\n> \u00a7 Reading materials.\n> [Faster Levenshtein Distances with a SIMD-friendly Traversal Order](https://ashvardanian.com/posts/levenshtein-diagonal).\n\n[rapidfuzz]: https://github.com/rapidfuzz/RapidFuzz\n\n### Needleman-Wunsch Alignment Score for Bioinformatics\n\nThe field of bioinformatics studies various representations of biological structures.\nThe \"primary\" representations are generally strings over sparse alphabets:\n\n- [DNA][faq-dna] sequences, where the alphabet is {A, C, G, T}, ranging from ~100 characters for short reads to 3 billion for the human genome.\n- [RNA][faq-rna] sequences, where the alphabet is {A, C, G, U}, ranging from ~50 characters for tRNA to thousands for mRNA.\n- [Proteins][faq-protein], where the alphabet is made of 22 amino acids, ranging from 2 characters for [dipeptide][faq-dipeptide] to 35,000 for [Titin][faq-titin], the longest protein.\n\nThe shorter the representation, the more often researchers may want to use custom substitution matrices.\nMeaning that the cost of a substitution between two characters may not be the same for all pairs.\n\nStringZilla adapts the fairly efficient two-row Wagner-Fisher algorithm as a baseline serial implementation of the Needleman-Wunsch score.\nIt supports arbitrary alphabets up to 256 characters, and can be used with either [BLOSUM][faq-blosum], [PAM][faq-pam], or other substitution matrices.\nIt also uses SIMD for hardware acceleration of the substitution lookups.\nThis however, does not __yet__ break the data-dependency for insertion costs, where 80% of the time is wasted.\nWith that solved, the SIMD implementation will become 5x faster than the serial one.\n\n[faq-dna]: https://en.wikipedia.org/wiki/DNA\n[faq-rna]: https://en.wikipedia.org/wiki/RNA\n[faq-protein]: https://en.wikipedia.org/wiki/Protein\n[faq-blosum]: https://en.wikipedia.org/wiki/BLOSUM\n[faq-pam]: https://en.wikipedia.org/wiki/Point_accepted_mutation\n[faq-dipeptide]: https://en.wikipedia.org/wiki/Dipeptide\n[faq-titin]: https://en.wikipedia.org/wiki/Titin\n\n### Random Generation\n\nGenerating random strings from different alphabets is a very common operation.\nStringZilla accepts an arbitrary [Pseudorandom Number Generator][faq-prng] to produce noise, and an array of characters to sample from.\nSampling is optimized to avoid integer division, a costly operation on modern CPUs.\nFor that a 768-byte long lookup table is used to perform 2 lookups, 1 multiplication, 2 shifts, and 2 accumulations.\n\nhttps://github.com/ashvardanian/StringZilla/blob/266c01710dddf71fc44800f36c2f992ca9735f87/include/stringzilla/stringzilla.h#L2490-L2533\n\n[faq-prng]: https://en.wikipedia.org/wiki/Pseudorandom_number_generator\n\n### Sorting\n\nFor lexicographic sorting of strings, StringZilla uses a \"hybrid-hybrid\" approach with $O(n * log(n))$ and.\n\n1. Radix sort for first bytes exported into a continuous buffer for locality.\n2. IntroSort on partially ordered chunks to balance efficiency and worst-case performance.\n   1. IntroSort begins with a QuickSort.\n   2. If the recursion depth exceeds a certain threshold, it switches to a HeapSort.\n\nNext design goals:\n\n- [ ] Generalize to arrays with over 4 billion entries.\n- [ ] Algorithmic improvements may yield another 3x performance gain.\n- [ ] SIMD-acceleration for the Radix slice.\n\n### Hashing\n\n> [!WARNING]\n> Hash functions are not cryptographically safe and are currently under active development.\n> They may change in future __minor__ releases.\n\nChoosing the right hashing algorithm for your application can be crucial from both performance and security standpoint.\nIn StringZilla a 64-bit rolling hash function is reused for both string hashes and substring hashes, Rabin-style fingerprints.\nRolling hashes take the same amount of time to compute hashes with different window sizes, and are fast to update.\nThose are not however perfect hashes, and collisions are frequent.\nStringZilla attempts to use SIMD, but the performance is not __yet__ satisfactory.\nOn Intel Sapphire Rapids, the following numbers can be expected for N-way parallel variants.\n\n- 4-way AVX2 throughput with 64-bit integer multiplication (no native support): 0.28 GB/s.\n- 4-way AVX2 throughput with 32-bit integer multiplication: 0.54 GB/s.\n- 4-way AVX-512DQ throughput with 64-bit integer multiplication: 0.46 GB/s.\n- 4-way AVX-512 throughput with 32-bit integer multiplication: 0.58 GB/s.\n- 8-way AVX-512 throughput with 32-bit integer multiplication: 0.11 GB/s.\n\nNext design goals:\n\n- [ ] Try gear-hash and other rolling approaches.\n\n#### Why not CRC32?\n\nCyclic Redundancy Check 32 is one of the most commonly used hash functions in Computer Science.\nIt has in-hardware support on both x86 and Arm, for both 8-bit, 16-bit, 32-bit, and 64-bit words.\nThe `0x1EDC6F41` polynomial is used in iSCSI, Btrfs, ext4, and the `0x04C11DB7` in SATA, Ethernet, Zlib, PNG.\nIn case of Arm more than one polynomial is supported.\nIt is, however, somewhat limiting for Big Data usecases, which often have to deal with more than 4 Billion strings, making collisions unavoidable.\nMoreover, the existing SIMD approaches are tricky, combining general purpose computations with specialized instructions, to utilize more silicon in every cycle.\n\n> \u00a7 Reading materials.\n> [Comprehensive derivation of approaches](https://github.com/komrad36/CRC)\n> [Faster computation for 4 KB buffers on x86](https://www.corsix.org/content/fast-crc32c-4k)\n> [Comparing different lookup tables](https://create.stephan-brumme.com/crc32)\n> Great open-source implementations.\n> [By Peter Cawley](https://github.com/corsix/fast-crc32)\n> [By Stephan Brumme](https://github.com/stbrumme/crc32)\n\n#### Other Modern Alternatives\n\n[MurmurHash](https://github.com/aappleby/smhasher/blob/master/README.md) from 2008 by Austin Appleby is one of the best known non-cryptographic hashes.\nIt has a very short implementation and is capable of producing 32-bit and 128-bit hashes.\nThe [CityHash](https://opensource.googleblog.com/2011/04/introducing-cityhash) from 2011 by Google and the [xxHash](https://github.com/Cyan4973/xxHash) improve on that, better leveraging the super-scalar nature of modern CPUs and producing 64-bit and 128-bit hashes.\n\nNeither of those functions are cryptographic, unlike MD5, SHA, and BLAKE algorithms.\nMost of cryptographic hashes are based on the Merkle-Damg\u00e5rd construction, and aren't resistant to the length-extension attacks.\nCurrent state of the Art, might be the [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) algorithm.\nIt's resistant to a broad range of attacks, can process 2 bytes per CPU cycle, and comes with a very optimized official implementation for C and Rust.\nIt has the same 128-bit security level as the BLAKE2, and achieves its performance gains by reducing the number of mixing rounds, and processing data in 1 KiB chunks, which is great for longer strings, but may result in poor performance on short ones.\n\nAll mentioned libraries have undergone extensive testing and are considered production-ready.\nThey can definitely accelerate your application, but so may the downstream mixer.\nFor instance, when a hash-table is constructed, the hashes are further shrunk to address table buckets.\nIf the mixer looses entropy, the performance gains from the hash function may be lost.\nAn example would be power-of-two modulo, which is a common mixer, but is known to be weak.\nOne alternative would be the [fastrange](https://github.com/lemire/fastrange) by Daniel Lemire.\nAnother one is the [Fibonacci hash trick](https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/) using the Golden Ratio, also used in StringZilla.\n\n### Unicode, UTF-8, and Wide Characters\n\nMost StringZilla operations are byte-level, so they work well with ASCII and UTF8 content out of the box.\nIn some cases, like edit-distance computation, the result of byte-level evaluation and character-level evaluation may differ.\nSo StringZilla provides following functions to work with Unicode:\n\n- `sz_edit_distance_utf8` - computes the Levenshtein distance between two UTF-8 strings.\n- `sz_hamming_distance_utf8` - computes the Hamming distance between two UTF-8 strings.\n\nJava, JavaScript, Python 2, C#, and Objective-C, however, use wide characters (`wchar`) - two byte long codes, instead of the more reasonable fixed-length UTF32 or variable-length UTF8.\nThis leads [to all kinds of offset-counting issues][wide-char-offsets] when facing four-byte long Unicode characters.\nSo consider transcoding with [simdutf](https://github.com/simdutf/simdutf), if you are coming from such environments.\n\n[wide-char-offsets]: https://josephg.com/blog/string-length-lies/\n\n## Contributing \ud83d\udc7e\n\nPlease check out the [contributing guide](https://github.com/ashvardanian/StringZilla/blob/main/CONTRIBUTING.md) for more details on how to setup the development environment and contribute to this project.\nIf you like this project, you may also enjoy [USearch][usearch], [UCall][ucall], [UForm][uform], and [SimSIMD][simsimd]. \ud83e\udd17\n\n[usearch]: https://github.com/unum-cloud/usearch\n[ucall]: https://github.com/unum-cloud/ucall\n[uform]: https://github.com/unum-cloud/uform\n[simsimd]: https://github.com/ashvardanian/simsimd\n\nIf you like strings and value efficiency, you may also enjoy the following projects:\n\n- [simdutf](https://github.com/simdutf/simdutf) - transcoding UTF8, UTF16, and UTF32 LE and BE.\n- [hyperscan](https://github.com/intel/hyperscan) - regular expressions with SIMD acceleration.\n- [pyahocorasick](https://github.com/WojciechMula/pyahocorasick) - Aho-Corasick algorithm in Python.\n- [rapidfuzz](https://github.com/rapidfuzz/RapidFuzz) - fast string matching in C++ and Python.\n\n## License \ud83d\udcdc\n\nFeel free to use the project under Apache 2.0 or the Three-clause BSD license at your preference.\n",
    "bugtrack_url": null,
    "license": "Apache-2.0",
    "summary": "SIMD-accelerated string search, sort, hashes, fingerprints, & edit distances",
    "version": "3.8.4",
    "project_urls": null,
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7a5df8d0230edcfadfd855e0c687cdea614c04e9a4a43fd288a4c2a70423cd77",
                "md5": "9e1d09a8779cdb0fe2ba871859e8781e",
                "sha256": "f3bd666063a1417822c7b67e2b5167d94c2363a5b3652c49a9edf4b4a1612c22"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-macosx_10_9_universal2.whl",
            "has_sig": false,
            "md5_digest": "9e1d09a8779cdb0fe2ba871859e8781e",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 112331,
            "upload_time": "2024-04-27T17:52:43",
            "upload_time_iso_8601": "2024-04-27T17:52:43.443139Z",
            "url": "https://files.pythonhosted.org/packages/7a/5d/f8d0230edcfadfd855e0c687cdea614c04e9a4a43fd288a4c2a70423cd77/stringzilla-3.8.4-cp310-cp310-macosx_10_9_universal2.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d298e2170281e5096997bc0f8ca143cf5527877dfa200bb2de95ba717a763444",
                "md5": "c753e23b8557243df17488a614da5746",
                "sha256": "e24ef6125d2c61dbfed7ac3ff156cfe5126335eef053167f48c3c18f315226aa"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "c753e23b8557243df17488a614da5746",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 73362,
            "upload_time": "2024-04-27T17:52:45",
            "upload_time_iso_8601": "2024-04-27T17:52:45.730051Z",
            "url": "https://files.pythonhosted.org/packages/d2/98/e2170281e5096997bc0f8ca143cf5527877dfa200bb2de95ba717a763444/stringzilla-3.8.4-cp310-cp310-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6370957625746bcd22bf808bd85d9e0c1a489e33deeb0aaa1656aec83ac5ae31",
                "md5": "8237a4147865c1f92e88a3a50b4327e3",
                "sha256": "27c16084f3c20997d0a4357a2215a34bf5c58e91d19c04766829334b656d9151"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "8237a4147865c1f92e88a3a50b4327e3",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 73448,
            "upload_time": "2024-04-27T17:52:47",
            "upload_time_iso_8601": "2024-04-27T17:52:47.537219Z",
            "url": "https://files.pythonhosted.org/packages/63/70/957625746bcd22bf808bd85d9e0c1a489e33deeb0aaa1656aec83ac5ae31/stringzilla-3.8.4-cp310-cp310-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9910f876c41c3c58b0943da462fb6ada2e2d741fcfad2d242305de15217a7fec",
                "md5": "196aa9b5647687c15d8cd58b9a42669f",
                "sha256": "7359a514871df41ef79e513678bcba30ba6390b28188c914ab80ca07fe84a2e2"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "has_sig": false,
            "md5_digest": "196aa9b5647687c15d8cd58b9a42669f",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 207451,
            "upload_time": "2024-04-27T17:52:48",
            "upload_time_iso_8601": "2024-04-27T17:52:48.765716Z",
            "url": "https://files.pythonhosted.org/packages/99/10/f876c41c3c58b0943da462fb6ada2e2d741fcfad2d242305de15217a7fec/stringzilla-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5342db4c122f71e42782564ab5a86823f5f69406bdef822a43d3252c57c97d3a",
                "md5": "d5d4ec37676bc0240bf57a1796422938",
                "sha256": "bf544d5f5cdb2f28a8fe85f461a635263003d41ea81f7f1a4cc53cdf0f9567b3"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "d5d4ec37676bc0240bf57a1796422938",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 217534,
            "upload_time": "2024-04-27T17:52:50",
            "upload_time_iso_8601": "2024-04-27T17:52:50.613097Z",
            "url": "https://files.pythonhosted.org/packages/53/42/db4c122f71e42782564ab5a86823f5f69406bdef822a43d3252c57c97d3a/stringzilla-3.8.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ea789845553d216e65d2f76a71df8016ff9dae69fee7b63ca6fd9871faa86f49",
                "md5": "fd8859a53b3df8484fba0ea11955d8ec",
                "sha256": "4a89ba1b863be9dd068ca6a8019b69a9b03fb1c56043402cb7ed5fe3be033242"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "has_sig": false,
            "md5_digest": "fd8859a53b3df8484fba0ea11955d8ec",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 189444,
            "upload_time": "2024-04-27T17:52:52",
            "upload_time_iso_8601": "2024-04-27T17:52:52.467668Z",
            "url": "https://files.pythonhosted.org/packages/ea/78/9845553d216e65d2f76a71df8016ff9dae69fee7b63ca6fd9871faa86f49/stringzilla-3.8.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "512d995155ac2de1dafaa85a37da870620b6e14bcb7082b5807326ba2be6076a",
                "md5": "3cce6a92e2319a93556c5c7a91f6a3e2",
                "sha256": "9d6855dd10c554f798415df3d277729ae83962d81569f2c34895a60807ee8e81"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "has_sig": false,
            "md5_digest": "3cce6a92e2319a93556c5c7a91f6a3e2",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 256547,
            "upload_time": "2024-04-27T17:52:54",
            "upload_time_iso_8601": "2024-04-27T17:52:54.028984Z",
            "url": "https://files.pythonhosted.org/packages/51/2d/995155ac2de1dafaa85a37da870620b6e14bcb7082b5807326ba2be6076a/stringzilla-3.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ec267d0abf680589e754f9c510ffa3ce5eab577df424423b1b0f6da4abe099ae",
                "md5": "b60290eb0a4e19e6263411fba90b9476",
                "sha256": "578d28a7552f8d5ba67c781a01de925c17c23dee12a95ef90da6d0a1f2595cf4"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "has_sig": false,
            "md5_digest": "b60290eb0a4e19e6263411fba90b9476",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 195335,
            "upload_time": "2024-04-27T17:52:55",
            "upload_time_iso_8601": "2024-04-27T17:52:55.305142Z",
            "url": "https://files.pythonhosted.org/packages/ec/26/7d0abf680589e754f9c510ffa3ce5eab577df424423b1b0f6da4abe099ae/stringzilla-3.8.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f3a676d593557b20fd867c9fcfb09d7b5accc0846811d740eed23c5958e97846",
                "md5": "3438f94419e1060c2b03f78855e3e540",
                "sha256": "09791fd3757418336c83a5293a10e75a90b1191fd56d4cce194831e4b940d504"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-musllinux_1_2_aarch64.whl",
            "has_sig": false,
            "md5_digest": "3438f94419e1060c2b03f78855e3e540",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 203559,
            "upload_time": "2024-04-27T17:52:56",
            "upload_time_iso_8601": "2024-04-27T17:52:56.724449Z",
            "url": "https://files.pythonhosted.org/packages/f3/a6/76d593557b20fd867c9fcfb09d7b5accc0846811d740eed23c5958e97846/stringzilla-3.8.4-cp310-cp310-musllinux_1_2_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0939691174b5b85b6e12a2c0148e20dc560b570200a139b41b90e3fa451284c0",
                "md5": "aa0d5de40f74117cb02fc45fd0d4fee0",
                "sha256": "df6cf50033108391c5bf93a54b463d1dfcfdb3185131bb03efa3dc05dd2ebaae"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-musllinux_1_2_i686.whl",
            "has_sig": false,
            "md5_digest": "aa0d5de40f74117cb02fc45fd0d4fee0",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 194081,
            "upload_time": "2024-04-27T17:52:58",
            "upload_time_iso_8601": "2024-04-27T17:52:58.265978Z",
            "url": "https://files.pythonhosted.org/packages/09/39/691174b5b85b6e12a2c0148e20dc560b570200a139b41b90e3fa451284c0/stringzilla-3.8.4-cp310-cp310-musllinux_1_2_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a241eaa93a6899c46e2aa228d88b10e5f207eac3d0d068ece4ccdb085adee973",
                "md5": "c57f968eef60afb40d89cc99c6852824",
                "sha256": "5f03d5d7ae252cee460c267ece2e6aca5576545b1871d82a78c9f6be5acc2342"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-musllinux_1_2_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "c57f968eef60afb40d89cc99c6852824",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 213767,
            "upload_time": "2024-04-27T17:53:00",
            "upload_time_iso_8601": "2024-04-27T17:53:00.907465Z",
            "url": "https://files.pythonhosted.org/packages/a2/41/eaa93a6899c46e2aa228d88b10e5f207eac3d0d068ece4ccdb085adee973/stringzilla-3.8.4-cp310-cp310-musllinux_1_2_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "1c408ac1934c52a6d125bc50270e79a299469f2ee35358b43b8003c60e07e9a9",
                "md5": "d720a730058b171af9065ae8d85fb99e",
                "sha256": "0d367565948f6c47c711560a27f95e19945989f0524af1c183eb65a7740a3096"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-musllinux_1_2_s390x.whl",
            "has_sig": false,
            "md5_digest": "d720a730058b171af9065ae8d85fb99e",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 177923,
            "upload_time": "2024-04-27T17:53:02",
            "upload_time_iso_8601": "2024-04-27T17:53:02.666116Z",
            "url": "https://files.pythonhosted.org/packages/1c/40/8ac1934c52a6d125bc50270e79a299469f2ee35358b43b8003c60e07e9a9/stringzilla-3.8.4-cp310-cp310-musllinux_1_2_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f6530bc5dba54a04ca6d271a3d1af1b2a9dadd4647b582a9d2c6b1ba814bf354",
                "md5": "47bc1280ead7af32353925af6a3cd43a",
                "sha256": "750320f900e6755c2f29bc4a34d32fa0f5498438e92bc0d5097f9b88dd1b8fa6"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-musllinux_1_2_x86_64.whl",
            "has_sig": false,
            "md5_digest": "47bc1280ead7af32353925af6a3cd43a",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 252067,
            "upload_time": "2024-04-27T17:53:04",
            "upload_time_iso_8601": "2024-04-27T17:53:04.849868Z",
            "url": "https://files.pythonhosted.org/packages/f6/53/0bc5dba54a04ca6d271a3d1af1b2a9dadd4647b582a9d2c6b1ba814bf354/stringzilla-3.8.4-cp310-cp310-musllinux_1_2_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9d122722e9397a8012ab6263acc8877c8873ff12a112b22f721c7c84d64d7fa4",
                "md5": "b2e38a61128d07688b4951273f2936fc",
                "sha256": "d2acc7c7c71394edd5d4fc32252c213cb42a10f37c7169edf6562f6491ad7ff8"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-win32.whl",
            "has_sig": false,
            "md5_digest": "b2e38a61128d07688b4951273f2936fc",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 63429,
            "upload_time": "2024-04-27T17:53:06",
            "upload_time_iso_8601": "2024-04-27T17:53:06.749208Z",
            "url": "https://files.pythonhosted.org/packages/9d/12/2722e9397a8012ab6263acc8877c8873ff12a112b22f721c7c84d64d7fa4/stringzilla-3.8.4-cp310-cp310-win32.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "79883f9d33c759bb4691669bb440e5c04672691896f012144f942dfbbd9d6267",
                "md5": "6cb902e82c1716ac3ee444358189a936",
                "sha256": "3b45fd5b51daeeca74c5b811e4bfce408c49718430a70d4400cb88e083fa8381"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "6cb902e82c1716ac3ee444358189a936",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 72430,
            "upload_time": "2024-04-27T17:53:08",
            "upload_time_iso_8601": "2024-04-27T17:53:08.753038Z",
            "url": "https://files.pythonhosted.org/packages/79/88/3f9d33c759bb4691669bb440e5c04672691896f012144f942dfbbd9d6267/stringzilla-3.8.4-cp310-cp310-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7b5216932e16f81beadd8439eccf349c1eb50e490526469a60f77ca9e6368962",
                "md5": "c07248d1a1a200efb2e88b5f55894ef6",
                "sha256": "f4801c1847c356eb4f5087a95bf0d6ac48266827aea34202f93b9e9943b4f691"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp310-cp310-win_arm64.whl",
            "has_sig": false,
            "md5_digest": "c07248d1a1a200efb2e88b5f55894ef6",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": null,
            "size": 62926,
            "upload_time": "2024-04-27T17:53:10",
            "upload_time_iso_8601": "2024-04-27T17:53:10.034798Z",
            "url": "https://files.pythonhosted.org/packages/7b/52/16932e16f81beadd8439eccf349c1eb50e490526469a60f77ca9e6368962/stringzilla-3.8.4-cp310-cp310-win_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6e31db785eca26e96651becf91654b547e3e4a6c58f386a787609f4adc77497e",
                "md5": "4481b8fb960dd98d12c1ae7389838387",
                "sha256": "bd2a99cb4cd9b9bcfd91bb5311884a5560546e6bb6ab5d09e3d2b550a6d81878"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-macosx_10_9_universal2.whl",
            "has_sig": false,
            "md5_digest": "4481b8fb960dd98d12c1ae7389838387",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 112334,
            "upload_time": "2024-04-27T17:53:12",
            "upload_time_iso_8601": "2024-04-27T17:53:12.063672Z",
            "url": "https://files.pythonhosted.org/packages/6e/31/db785eca26e96651becf91654b547e3e4a6c58f386a787609f4adc77497e/stringzilla-3.8.4-cp311-cp311-macosx_10_9_universal2.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7ad27a8cf1f6896d07d8a9e6e5cda61bd21c979ebb89778ce57518a17c88dd4c",
                "md5": "9fb05d62171a3f2f472e1969c5372f58",
                "sha256": "420272d93de123d6633d0bc920eecf65e8afbb82071aa7c2b73e9c0947897952"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "9fb05d62171a3f2f472e1969c5372f58",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 73364,
            "upload_time": "2024-04-27T17:53:13",
            "upload_time_iso_8601": "2024-04-27T17:53:13.343832Z",
            "url": "https://files.pythonhosted.org/packages/7a/d2/7a8cf1f6896d07d8a9e6e5cda61bd21c979ebb89778ce57518a17c88dd4c/stringzilla-3.8.4-cp311-cp311-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ccf885c1f85df1fe16b19d32605450facb5868a48f4745fd0f2dbe8f65c39049",
                "md5": "0bdd49447f7041b7949a17838f3cf0ee",
                "sha256": "7fd7aa492c6a0c340f4b3a545ea137566a60f67f76d684c7cad549feeb910df3"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "0bdd49447f7041b7949a17838f3cf0ee",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 73445,
            "upload_time": "2024-04-27T17:53:15",
            "upload_time_iso_8601": "2024-04-27T17:53:15.241070Z",
            "url": "https://files.pythonhosted.org/packages/cc/f8/85c1f85df1fe16b19d32605450facb5868a48f4745fd0f2dbe8f65c39049/stringzilla-3.8.4-cp311-cp311-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a304b755fb69cd5cfe2b43942c1a4f762da53489c5c888afdb17b414b15dda54",
                "md5": "d34faf30cb184f35b726d9a89f0047b2",
                "sha256": "f5b4de47f73c82535bdb9344749fec0d865bbf735c698c76ea4970822468faf5"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "has_sig": false,
            "md5_digest": "d34faf30cb184f35b726d9a89f0047b2",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 209705,
            "upload_time": "2024-04-27T17:53:17",
            "upload_time_iso_8601": "2024-04-27T17:53:17.070516Z",
            "url": "https://files.pythonhosted.org/packages/a3/04/b755fb69cd5cfe2b43942c1a4f762da53489c5c888afdb17b414b15dda54/stringzilla-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b7b7700aafb33cd81d0dc8ccf53b57989c014344fef6a946f291a9c69e9d82fd",
                "md5": "3709316a714921eb065292f72dff0f02",
                "sha256": "2903cf95ebd681d7010bfb58386f6dc0d1e178f3e15bb3f9fcdf4124d4f7f4b2"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "3709316a714921eb065292f72dff0f02",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 220125,
            "upload_time": "2024-04-27T17:53:18",
            "upload_time_iso_8601": "2024-04-27T17:53:18.466120Z",
            "url": "https://files.pythonhosted.org/packages/b7/b7/700aafb33cd81d0dc8ccf53b57989c014344fef6a946f291a9c69e9d82fd/stringzilla-3.8.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f9ba5029a033ccbcc534152b68859f6643168d30037e8161ea1215caa6916914",
                "md5": "4379ba2592f756617b83aa70e1e6af9d",
                "sha256": "bd239f0583d769d3b8a50b4a4fd34e9aee034ca3e3ba15fa70149aa752cfb9fb"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "has_sig": false,
            "md5_digest": "4379ba2592f756617b83aa70e1e6af9d",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 191642,
            "upload_time": "2024-04-27T17:53:21",
            "upload_time_iso_8601": "2024-04-27T17:53:21.051987Z",
            "url": "https://files.pythonhosted.org/packages/f9/ba/5029a033ccbcc534152b68859f6643168d30037e8161ea1215caa6916914/stringzilla-3.8.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "2f6feb0c58946c25f7a600ba83cff0ca5c5e30bedd17daf55f48618bdc682a5d",
                "md5": "0e6e06fc190ebfd63785eba3a396c501",
                "sha256": "ae518d49c24b0ec3400c7cb99b30dd22df320a47b73b13b454ae56547aa83381"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "has_sig": false,
            "md5_digest": "0e6e06fc190ebfd63785eba3a396c501",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 258502,
            "upload_time": "2024-04-27T17:53:22",
            "upload_time_iso_8601": "2024-04-27T17:53:22.373398Z",
            "url": "https://files.pythonhosted.org/packages/2f/6f/eb0c58946c25f7a600ba83cff0ca5c5e30bedd17daf55f48618bdc682a5d/stringzilla-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7cec0706ce06be63f555ac6cdf5efcc204809c25b9afd1516b8f5dc63579ae27",
                "md5": "c990b9321b1e3e15e5f38be03c65e0d1",
                "sha256": "47e25c61fde5cca68df87bb1f2fc1d7eea5a08cdc6b2e943fe49991a42333b48"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "has_sig": false,
            "md5_digest": "c990b9321b1e3e15e5f38be03c65e0d1",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 197531,
            "upload_time": "2024-04-27T17:53:23",
            "upload_time_iso_8601": "2024-04-27T17:53:23.769985Z",
            "url": "https://files.pythonhosted.org/packages/7c/ec/0706ce06be63f555ac6cdf5efcc204809c25b9afd1516b8f5dc63579ae27/stringzilla-3.8.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ceb15bc3c406281ebe0c5a27a7e7ae04285e84ac72b2a61a191fa9a341faa250",
                "md5": "d496fdabc4a76c31f64b411b8b4f765f",
                "sha256": "5662c93ac208ce93a1a0a359979eba9641fa39ea4e3f4268c1afe9f77ae95ab4"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-musllinux_1_2_aarch64.whl",
            "has_sig": false,
            "md5_digest": "d496fdabc4a76c31f64b411b8b4f765f",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 206019,
            "upload_time": "2024-04-27T17:53:25",
            "upload_time_iso_8601": "2024-04-27T17:53:25.133943Z",
            "url": "https://files.pythonhosted.org/packages/ce/b1/5bc3c406281ebe0c5a27a7e7ae04285e84ac72b2a61a191fa9a341faa250/stringzilla-3.8.4-cp311-cp311-musllinux_1_2_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "2cb17ad2db4070afe42c89b5eab3c36e415c4d2415b275e065bcdda57706da52",
                "md5": "962da391815d907a35b258bd9c53c80b",
                "sha256": "3aa25cd8274bf1be42086d03b8388a359104b5e312fa741bb783ecfdbcf1deb4"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-musllinux_1_2_i686.whl",
            "has_sig": false,
            "md5_digest": "962da391815d907a35b258bd9c53c80b",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 196291,
            "upload_time": "2024-04-27T17:53:26",
            "upload_time_iso_8601": "2024-04-27T17:53:26.620324Z",
            "url": "https://files.pythonhosted.org/packages/2c/b1/7ad2db4070afe42c89b5eab3c36e415c4d2415b275e065bcdda57706da52/stringzilla-3.8.4-cp311-cp311-musllinux_1_2_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e1bc46aabf3b90cf0ff9b5d4fbd2831dcb02167c2c0173486dd63a9a43bcd9eb",
                "md5": "6a53c5a63549cd13c4b08406625a00cd",
                "sha256": "a8d551fd9cf3fbb2f28f4ea47e0c48ec4abb376550104a9198d6bf49b7b6e081"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-musllinux_1_2_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "6a53c5a63549cd13c4b08406625a00cd",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 216229,
            "upload_time": "2024-04-27T17:53:28",
            "upload_time_iso_8601": "2024-04-27T17:53:28.668376Z",
            "url": "https://files.pythonhosted.org/packages/e1/bc/46aabf3b90cf0ff9b5d4fbd2831dcb02167c2c0173486dd63a9a43bcd9eb/stringzilla-3.8.4-cp311-cp311-musllinux_1_2_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "77e97a6aa38ea4adfe310a070743fa9533eceb80879421f268608dddf405e9d1",
                "md5": "00201f885e9c06e3194dcdd657400bcf",
                "sha256": "17555659bc2bc7a2334b9d01064e9e8c696e19126ead74e326b91b8b24ca29a3"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-musllinux_1_2_s390x.whl",
            "has_sig": false,
            "md5_digest": "00201f885e9c06e3194dcdd657400bcf",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 180462,
            "upload_time": "2024-04-27T17:53:30",
            "upload_time_iso_8601": "2024-04-27T17:53:30.642234Z",
            "url": "https://files.pythonhosted.org/packages/77/e9/7a6aa38ea4adfe310a070743fa9533eceb80879421f268608dddf405e9d1/stringzilla-3.8.4-cp311-cp311-musllinux_1_2_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d98c9b5b996f80b584d931a99f897db72e2ffcc9771283a8951f9e8bb2ef2891",
                "md5": "0b416baddcc664257b50f288c8d76bc0",
                "sha256": "80264ceae5413561a044dc06ac9cb4cee4c12f803d9fbba43944984a160e93e2"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-musllinux_1_2_x86_64.whl",
            "has_sig": false,
            "md5_digest": "0b416baddcc664257b50f288c8d76bc0",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 254264,
            "upload_time": "2024-04-27T17:53:32",
            "upload_time_iso_8601": "2024-04-27T17:53:32.081140Z",
            "url": "https://files.pythonhosted.org/packages/d9/8c/9b5b996f80b584d931a99f897db72e2ffcc9771283a8951f9e8bb2ef2891/stringzilla-3.8.4-cp311-cp311-musllinux_1_2_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "463808292b30f6989e10e6f2a3877c1933fa041a100ea765c3434f2099a3182e",
                "md5": "46deeda7b6b263043fd07d8378dcf36a",
                "sha256": "fe9d52736e609404c690e489159556cc23533180265c7d2f3d8695fb6537dbe1"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-win32.whl",
            "has_sig": false,
            "md5_digest": "46deeda7b6b263043fd07d8378dcf36a",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 63430,
            "upload_time": "2024-04-27T17:53:33",
            "upload_time_iso_8601": "2024-04-27T17:53:33.407000Z",
            "url": "https://files.pythonhosted.org/packages/46/38/08292b30f6989e10e6f2a3877c1933fa041a100ea765c3434f2099a3182e/stringzilla-3.8.4-cp311-cp311-win32.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bf43a5a7c84b2de5ab2794824cf1e73442e2eb464eaa34af28468b97b8cccfa7",
                "md5": "a306e1bc0cbdbd60570d7db5183921a1",
                "sha256": "7ead113fd1b446ba1c8780a13f797df5e396f32eb205ae93f380319220f02a02"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "a306e1bc0cbdbd60570d7db5183921a1",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 74125,
            "upload_time": "2024-04-27T17:53:35",
            "upload_time_iso_8601": "2024-04-27T17:53:35.273034Z",
            "url": "https://files.pythonhosted.org/packages/bf/43/a5a7c84b2de5ab2794824cf1e73442e2eb464eaa34af28468b97b8cccfa7/stringzilla-3.8.4-cp311-cp311-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d3b2b6f16b7dcb6e6c53cabbbc22284c40045443fd1cc3fab3c44d1f885a3a30",
                "md5": "c93dd7104e73a2b15c4cf317dd764ebd",
                "sha256": "cd9e6247983d092a45f5a07e049dff5aac61190c37fbf0ed6da31fd529a845f5"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp311-cp311-win_arm64.whl",
            "has_sig": false,
            "md5_digest": "c93dd7104e73a2b15c4cf317dd764ebd",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": null,
            "size": 64160,
            "upload_time": "2024-04-27T17:53:36",
            "upload_time_iso_8601": "2024-04-27T17:53:36.752555Z",
            "url": "https://files.pythonhosted.org/packages/d3/b2/b6f16b7dcb6e6c53cabbbc22284c40045443fd1cc3fab3c44d1f885a3a30/stringzilla-3.8.4-cp311-cp311-win_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "59438aa7d66b150fb6913077bea0a842bbaa98d59bff8407d5cb0db55a065b2f",
                "md5": "a8ddf4da7ddb536613dc19c71186d7c7",
                "sha256": "fc68738b1f6177ff1e0618b970c4871b87f00e96c70c50ef1057a1755e01c2cd"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-macosx_10_9_universal2.whl",
            "has_sig": false,
            "md5_digest": "a8ddf4da7ddb536613dc19c71186d7c7",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 112537,
            "upload_time": "2024-04-27T17:53:38",
            "upload_time_iso_8601": "2024-04-27T17:53:38.602378Z",
            "url": "https://files.pythonhosted.org/packages/59/43/8aa7d66b150fb6913077bea0a842bbaa98d59bff8407d5cb0db55a065b2f/stringzilla-3.8.4-cp312-cp312-macosx_10_9_universal2.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bdf435fa59b11546de46236d97e4da085cf49820d3018a702de4abb72f4f317d",
                "md5": "f0f0b37e84cc0fdc0a02bf93571af06e",
                "sha256": "955c01345ee98bfeac2dff8d51b2b0afae35b32cf69797f288e52f7986fb997b"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "f0f0b37e84cc0fdc0a02bf93571af06e",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 73474,
            "upload_time": "2024-04-27T17:53:40",
            "upload_time_iso_8601": "2024-04-27T17:53:40.466903Z",
            "url": "https://files.pythonhosted.org/packages/bd/f4/35fa59b11546de46236d97e4da085cf49820d3018a702de4abb72f4f317d/stringzilla-3.8.4-cp312-cp312-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ad63286ae28901c416814533cc9355ce36dc9c4dc1c88683e4fd7e52720b1466",
                "md5": "f7c569528f6e69f0b510c24595b296d2",
                "sha256": "92dfc1a64ec53370c1041176895ce24fb8ec6b066b3cfb06ab5c159e996216d0"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "f7c569528f6e69f0b510c24595b296d2",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 73573,
            "upload_time": "2024-04-27T17:53:41",
            "upload_time_iso_8601": "2024-04-27T17:53:41.661722Z",
            "url": "https://files.pythonhosted.org/packages/ad/63/286ae28901c416814533cc9355ce36dc9c4dc1c88683e4fd7e52720b1466/stringzilla-3.8.4-cp312-cp312-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "379d0a168c6604827ccc494575b2175f83b80b093df4c05904fc39632ae3d585",
                "md5": "1c4f69b0d6fb24dc48673b18c8f6f62f",
                "sha256": "c09ee9864622469aa85b6d6563958c85e9a53952512ef44480af4e223ffc35cc"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "has_sig": false,
            "md5_digest": "1c4f69b0d6fb24dc48673b18c8f6f62f",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 209321,
            "upload_time": "2024-04-27T17:53:42",
            "upload_time_iso_8601": "2024-04-27T17:53:42.894235Z",
            "url": "https://files.pythonhosted.org/packages/37/9d/0a168c6604827ccc494575b2175f83b80b093df4c05904fc39632ae3d585/stringzilla-3.8.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "12c5e33c384422354ed627699171ca11194ef1fdccc4a77ee9222c796595e335",
                "md5": "0642ab8112a30f8205989395169c24de",
                "sha256": "c7df8582cb567164bd4836c267abdeebe2e852e8d0e2578b6253f1396559d939"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "0642ab8112a30f8205989395169c24de",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 219648,
            "upload_time": "2024-04-27T17:53:45",
            "upload_time_iso_8601": "2024-04-27T17:53:45.042715Z",
            "url": "https://files.pythonhosted.org/packages/12/c5/e33c384422354ed627699171ca11194ef1fdccc4a77ee9222c796595e335/stringzilla-3.8.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a7cc6082210e6390f84dc62e42242a86025a46b6ba44c2f14706ecc2e9b37bfe",
                "md5": "2817288446a15e7686319696ebaa5943",
                "sha256": "00a0909966c298961c2ceecd68b5fa7a04617b3e393fa37d9356e31035edfdc8"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "has_sig": false,
            "md5_digest": "2817288446a15e7686319696ebaa5943",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 191744,
            "upload_time": "2024-04-27T17:53:46",
            "upload_time_iso_8601": "2024-04-27T17:53:46.967180Z",
            "url": "https://files.pythonhosted.org/packages/a7/cc/6082210e6390f84dc62e42242a86025a46b6ba44c2f14706ecc2e9b37bfe/stringzilla-3.8.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bbeb056fdb5b5984ff3de00d2b50a0546403d71ef884eb2337776e8e34ff506c",
                "md5": "ea4562d5f05080dbccac68fefe4f6135",
                "sha256": "cb1845fe0841754a5bb5969a166d838aef0750f046cd0ab9ae08c2f3a5b5131d"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "has_sig": false,
            "md5_digest": "ea4562d5f05080dbccac68fefe4f6135",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 259174,
            "upload_time": "2024-04-27T17:53:48",
            "upload_time_iso_8601": "2024-04-27T17:53:48.524173Z",
            "url": "https://files.pythonhosted.org/packages/bb/eb/056fdb5b5984ff3de00d2b50a0546403d71ef884eb2337776e8e34ff506c/stringzilla-3.8.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "57b71a864092300bd15d44ffa4c6767f3dfe1b5c6883552d157ffe50b957cddc",
                "md5": "8ea74dccaabda5c46c74a1f741e49fc9",
                "sha256": "36ba12d5e1f36ce9a7439761aec054abf79ba3f9ef9e6cb94fc0c01445de9818"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "has_sig": false,
            "md5_digest": "8ea74dccaabda5c46c74a1f741e49fc9",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 197570,
            "upload_time": "2024-04-27T17:53:49",
            "upload_time_iso_8601": "2024-04-27T17:53:49.938663Z",
            "url": "https://files.pythonhosted.org/packages/57/b7/1a864092300bd15d44ffa4c6767f3dfe1b5c6883552d157ffe50b957cddc/stringzilla-3.8.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "032ce68366fbb56d26e1d629351e01c288f658b5aedf9b742779b866b57ca1f3",
                "md5": "f3c1f606c205491f2a542b6d7a9d26a5",
                "sha256": "4b2dd5f58b0e22f3db2a48245687220aacd119835256c461a3e5575ebedadaf1"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-musllinux_1_2_aarch64.whl",
            "has_sig": false,
            "md5_digest": "f3c1f606c205491f2a542b6d7a9d26a5",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 205580,
            "upload_time": "2024-04-27T17:53:51",
            "upload_time_iso_8601": "2024-04-27T17:53:51.411134Z",
            "url": "https://files.pythonhosted.org/packages/03/2c/e68366fbb56d26e1d629351e01c288f658b5aedf9b742779b866b57ca1f3/stringzilla-3.8.4-cp312-cp312-musllinux_1_2_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "df5fe7797531742d4b570b5d3b0542c3f1ca7d84fb97c9c588aabd41c135f93f",
                "md5": "2a1983a39edaae592083a6c4020904aa",
                "sha256": "3934a3d3327df8818e9b41c9398e542b79eb75ddaf98f773a639d6e6b23ae583"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-musllinux_1_2_i686.whl",
            "has_sig": false,
            "md5_digest": "2a1983a39edaae592083a6c4020904aa",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 196339,
            "upload_time": "2024-04-27T17:53:52",
            "upload_time_iso_8601": "2024-04-27T17:53:52.822096Z",
            "url": "https://files.pythonhosted.org/packages/df/5f/e7797531742d4b570b5d3b0542c3f1ca7d84fb97c9c588aabd41c135f93f/stringzilla-3.8.4-cp312-cp312-musllinux_1_2_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7b10af6d9cf5ca5282ef5bab3c46b1ccb4fa9279b56430806de966dbe8199be0",
                "md5": "99ae41f469b244427c4fbef3158c2b3f",
                "sha256": "d4b260238c66aedd2e52dcb2f9e962fa806d712d8674b60af68237f1972fc213"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-musllinux_1_2_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "99ae41f469b244427c4fbef3158c2b3f",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 215618,
            "upload_time": "2024-04-27T17:53:54",
            "upload_time_iso_8601": "2024-04-27T17:53:54.230543Z",
            "url": "https://files.pythonhosted.org/packages/7b/10/af6d9cf5ca5282ef5bab3c46b1ccb4fa9279b56430806de966dbe8199be0/stringzilla-3.8.4-cp312-cp312-musllinux_1_2_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9715a0199e31e31cc7c4c8699fbde6e84ffd82db3c20377cdfd3fa833ea0f2da",
                "md5": "41e06a650ca6ba4120352ca9208d4e86",
                "sha256": "b74d89c2dce3aed70aeef7a063d84bdc48202f1fae8c3b72c6e96c532463be3a"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-musllinux_1_2_s390x.whl",
            "has_sig": false,
            "md5_digest": "41e06a650ca6ba4120352ca9208d4e86",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 180625,
            "upload_time": "2024-04-27T17:53:55",
            "upload_time_iso_8601": "2024-04-27T17:53:55.804477Z",
            "url": "https://files.pythonhosted.org/packages/97/15/a0199e31e31cc7c4c8699fbde6e84ffd82db3c20377cdfd3fa833ea0f2da/stringzilla-3.8.4-cp312-cp312-musllinux_1_2_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ee7dd0f696c97337985e6087bf8393670dd9af69fc1c5d42a982dfb2aa1aad08",
                "md5": "1310105baf08ec98fb1e3f1898c6408b",
                "sha256": "bd167b7f6a2af68597c0f988232cff69cb94f65976a53e7dd148354d34a5fc4f"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-musllinux_1_2_x86_64.whl",
            "has_sig": false,
            "md5_digest": "1310105baf08ec98fb1e3f1898c6408b",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 254431,
            "upload_time": "2024-04-27T17:53:57",
            "upload_time_iso_8601": "2024-04-27T17:53:57.329571Z",
            "url": "https://files.pythonhosted.org/packages/ee/7d/d0f696c97337985e6087bf8393670dd9af69fc1c5d42a982dfb2aa1aad08/stringzilla-3.8.4-cp312-cp312-musllinux_1_2_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a665a6b301eb8e2ce4f8e5b357b4384fb2a92eeb8712f1a69e9b2a557ad41a02",
                "md5": "b592d5da260af62125dfe97c1d062bef",
                "sha256": "e5cfc096232758086fb63088e10266836b2e3e0d20fdb85f52ce406838425b1b"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-win32.whl",
            "has_sig": false,
            "md5_digest": "b592d5da260af62125dfe97c1d062bef",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 63522,
            "upload_time": "2024-04-27T17:53:58",
            "upload_time_iso_8601": "2024-04-27T17:53:58.703766Z",
            "url": "https://files.pythonhosted.org/packages/a6/65/a6b301eb8e2ce4f8e5b357b4384fb2a92eeb8712f1a69e9b2a557ad41a02/stringzilla-3.8.4-cp312-cp312-win32.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "fb893b15a5df94399a8d2b6b7c2036d9934f7d119bd2aabf4824703f4ee88675",
                "md5": "a0aeb198f7d1a1a3617ed31fc853b4ad",
                "sha256": "af1eac34543957b5afae983d302b02bb6700b2b0317ba1224b211ae7b97543d9"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "a0aeb198f7d1a1a3617ed31fc853b4ad",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 74196,
            "upload_time": "2024-04-27T17:53:59",
            "upload_time_iso_8601": "2024-04-27T17:53:59.981018Z",
            "url": "https://files.pythonhosted.org/packages/fb/89/3b15a5df94399a8d2b6b7c2036d9934f7d119bd2aabf4824703f4ee88675/stringzilla-3.8.4-cp312-cp312-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "246bb740cf3d1254693bbd71954f48ba330a3c3e051713e876ba0a130c4ce25d",
                "md5": "d0e9a00ccfac512dfccfc37665f37b19",
                "sha256": "2c033f3d74b32193a774159f5ae42c6f2cafffce422fd729e31234d6153d48b2"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp312-cp312-win_arm64.whl",
            "has_sig": false,
            "md5_digest": "d0e9a00ccfac512dfccfc37665f37b19",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": null,
            "size": 64142,
            "upload_time": "2024-04-27T17:54:01",
            "upload_time_iso_8601": "2024-04-27T17:54:01.822842Z",
            "url": "https://files.pythonhosted.org/packages/24/6b/b740cf3d1254693bbd71954f48ba330a3c3e051713e876ba0a130c4ce25d/stringzilla-3.8.4-cp312-cp312-win_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f45450dd23d55a2cf853173472bb7f4c5549253161dba654f37b55ff70b5d775",
                "md5": "1395d2a6fe42a0daa9987a98d718900a",
                "sha256": "35cf176a99c1bc6093a1f9ac2db38e21a7b6fb9076bdd48932f75ae12f5de3d5"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp36-cp36m-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "1395d2a6fe42a0daa9987a98d718900a",
            "packagetype": "bdist_wheel",
            "python_version": "cp36",
            "requires_python": null,
            "size": 73056,
            "upload_time": "2024-04-27T17:54:03",
            "upload_time_iso_8601": "2024-04-27T17:54:03.957049Z",
            "url": "https://files.pythonhosted.org/packages/f4/54/50dd23d55a2cf853173472bb7f4c5549253161dba654f37b55ff70b5d775/stringzilla-3.8.4-cp36-cp36m-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "2d9f4f8ace18a2616d276bc3c43a45774d1b0a763b883a917b8b336db24cc3c5",
                "md5": "1c4b61f68e8d0c401b8280aa731819f1",
                "sha256": "79413c9db45fec927ab74d9327729343fd29af334fca303ca21084fdf9029f04"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "has_sig": false,
            "md5_digest": "1c4b61f68e8d0c401b8280aa731819f1",
            "packagetype": "bdist_wheel",
            "python_version": "cp36",
            "requires_python": null,
            "size": 201999,
            "upload_time": "2024-04-27T17:54:05",
            "upload_time_iso_8601": "2024-04-27T17:54:05.314014Z",
            "url": "https://files.pythonhosted.org/packages/2d/9f/4f8ace18a2616d276bc3c43a45774d1b0a763b883a917b8b336db24cc3c5/stringzilla-3.8.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "80eb95b0142ab9dae341c49231902faace24c65f3f175dba8a6b7b28636c3ed4",
                "md5": "ef03a3e4daff75dfaa73b2b25a68b8c7",
                "sha256": "f40385051cd80517156b99a27a26ed98d58682b293bc5f7941d4ff1076cc79a0"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "ef03a3e4daff75dfaa73b2b25a68b8c7",
            "packagetype": "bdist_wheel",
            "python_version": "cp36",
            "requires_python": null,
            "size": 212783,
            "upload_time": "2024-04-27T17:54:06",
            "upload_time_iso_8601": "2024-04-27T17:54:06.775472Z",
            "url": "https://files.pythonhosted.org/packages/80/eb/95b0142ab9dae341c49231902faace24c65f3f175dba8a6b7b28636c3ed4/stringzilla-3.8.4-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "69b4820ce2372de6d92f46d9ca51a250476d8a99ce28af616065e435701f4158",
                "md5": "e92b7281975293e7ea063cafa1590ca5",
                "sha256": "aeccc9d86e13e89f896781b08d37e0b58cc87affa8ae3808678f14a56cbf3a46"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "has_sig": false,
            "md5_digest": "e92b7281975293e7ea063cafa1590ca5",
            "packagetype": "bdist_wheel",
            "python_version": "cp36",
            "requires_python": null,
            "size": 184511,
            "upload_time": "2024-04-27T17:54:08",
            "upload_time_iso_8601": "2024-04-27T17:54:08.899207Z",
            "url": "https://files.pythonhosted.org/packages/69/b4/820ce2372de6d92f46d9ca51a250476d8a99ce28af616065e435701f4158/stringzilla-3.8.4-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "189a9ea0f2c61f83ed31507c6c09983f64878751cd9d91ec08c64d4d13e15c9a",
                "md5": "49bfd45b2c45ebe1f9ba7e35329edbc8",
                "sha256": "1f2de3f313fd329e7bef8300176f43419a9488912c391ff8fcb4a60ba9d06b38"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "has_sig": false,
            "md5_digest": "49bfd45b2c45ebe1f9ba7e35329edbc8",
            "packagetype": "bdist_wheel",
            "python_version": "cp36",
            "requires_python": null,
            "size": 252361,
            "upload_time": "2024-04-27T17:54:10",
            "upload_time_iso_8601": "2024-04-27T17:54:10.374864Z",
            "url": "https://files.pythonhosted.org/packages/18/9a/9ea0f2c61f83ed31507c6c09983f64878751cd9d91ec08c64d4d13e15c9a/stringzilla-3.8.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "df28a6555c58494c767195e947156469684fdea38406cca6c2315668ad24a75c",
                "md5": "67bd15af440f05dc17a28f71fc8d3ad3",
                "sha256": "0655a4b3111cb092b5cf95535e8c8aadce892b1930539a5235be7170ef3086fd"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "has_sig": false,
            "md5_digest": "67bd15af440f05dc17a28f71fc8d3ad3",
            "packagetype": "bdist_wheel",
            "python_version": "cp36",
            "requires_python": null,
            "size": 190572,
            "upload_time": "2024-04-27T17:54:11",
            "upload_time_iso_8601": "2024-04-27T17:54:11.816015Z",
            "url": "https://files.pythonhosted.org/packages/df/28/a6555c58494c767195e947156469684fdea38406cca6c2315668ad24a75c/stringzilla-3.8.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "388a48ed645c160cb3d4f68737e8aeb92a17f2deb1c75cfab4ef2c1e859a55f2",
                "md5": "c85d62ebc52e34ec9961d73f1b972980",
                "sha256": "05a953c7c040bd4a4182cf18c9e0738e3cc3e84aaac7725a148b3f08894b44de"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp36-cp36m-musllinux_1_2_aarch64.whl",
            "has_sig": false,
            "md5_digest": "c85d62ebc52e34ec9961d73f1b972980",
            "packagetype": "bdist_wheel",
            "python_version": "cp36",
            "requires_python": null,
            "size": 198574,
            "upload_time": "2024-04-27T17:54:13",
            "upload_time_iso_8601": "2024-04-27T17:54:13.355455Z",
            "url": "https://files.pythonhosted.org/packages/38/8a/48ed645c160cb3d4f68737e8aeb92a17f2deb1c75cfab4ef2c1e859a55f2/stringzilla-3.8.4-cp36-cp36m-musllinux_1_2_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "51a49c47d1f08b86be062dbd4e615ea2e645dce7c9cbabede865c91637ab4170",
                "md5": "d06e139373d30c906a4267a838ebbea3",
                "sha256": "80b939bc5d8531d464ff09567d1e2b1454c70ad929c6ed4a98673d0a77317f18"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp36-cp36m-musllinux_1_2_i686.whl",
            "has_sig": false,
            "md5_digest": "d06e139373d30c906a4267a838ebbea3",
            "packagetype": "bdist_wheel",
            "python_version": "cp36",
            "requires_python": null,
            "size": 190176,
            "upload_time": "2024-04-27T17:54:14",
            "upload_time_iso_8601": "2024-04-27T17:54:14.740940Z",
            "url": "https://files.pythonhosted.org/packages/51/a4/9c47d1f08b86be062dbd4e615ea2e645dce7c9cbabede865c91637ab4170/stringzilla-3.8.4-cp36-cp36m-musllinux_1_2_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b24148a2b3240b7eddf2cbd5794125b9eedf9e12981d7d2c97576f1d79921995",
                "md5": "329fade4fdd147421de02ea87edc04a8",
                "sha256": "0651c45222683c17383ba856d5a2f413fd87e18b5d673dab6a2ac2441b634466"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp36-cp36m-musllinux_1_2_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "329fade4fdd147421de02ea87edc04a8",
            "packagetype": "bdist_wheel",
            "python_version": "cp36",
            "requires_python": null,
            "size": 209135,
            "upload_time": "2024-04-27T17:54:16",
            "upload_time_iso_8601": "2024-04-27T17:54:16.324432Z",
            "url": "https://files.pythonhosted.org/packages/b2/41/48a2b3240b7eddf2cbd5794125b9eedf9e12981d7d2c97576f1d79921995/stringzilla-3.8.4-cp36-cp36m-musllinux_1_2_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "35235af9b6fddb76de23ad4d6a6ce3948015170c2b603ae66b331432134cdafd",
                "md5": "3ce478dd7c646bfce1efda42442a8c21",
                "sha256": "02964b44e6c249eded8be88260f8778b995e055160ad56222d465980ff717ac4"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp36-cp36m-musllinux_1_2_s390x.whl",
            "has_sig": false,
            "md5_digest": "3ce478dd7c646bfce1efda42442a8c21",
            "packagetype": "bdist_wheel",
            "python_version": "cp36",
            "requires_python": null,
            "size": 173676,
            "upload_time": "2024-04-27T17:54:17",
            "upload_time_iso_8601": "2024-04-27T17:54:17.786885Z",
            "url": "https://files.pythonhosted.org/packages/35/23/5af9b6fddb76de23ad4d6a6ce3948015170c2b603ae66b331432134cdafd/stringzilla-3.8.4-cp36-cp36m-musllinux_1_2_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "719f4ec4abdc21064a60412110aad77c161673da0abf08aedacab3ce79dcde85",
                "md5": "14d7168c017e09bfb567b19a7daaf10e",
                "sha256": "a715d24d60dae78f353715014f292bdd2c334cdba49efeadc0bc3c0819bd9959"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp36-cp36m-musllinux_1_2_x86_64.whl",
            "has_sig": false,
            "md5_digest": "14d7168c017e09bfb567b19a7daaf10e",
            "packagetype": "bdist_wheel",
            "python_version": "cp36",
            "requires_python": null,
            "size": 247668,
            "upload_time": "2024-04-27T17:54:19",
            "upload_time_iso_8601": "2024-04-27T17:54:19.783792Z",
            "url": "https://files.pythonhosted.org/packages/71/9f/4ec4abdc21064a60412110aad77c161673da0abf08aedacab3ce79dcde85/stringzilla-3.8.4-cp36-cp36m-musllinux_1_2_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "013ad1303ada0bf5c5d04ba32d507d3fdbdfdf240dff847721e2ced796d9850c",
                "md5": "b0be1e83b4a88fcfc0d16ea3ae91691c",
                "sha256": "0579608548ec67f1af92da44d0d337c2765912da12a6087c8eb44268ca0484c4"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp36-cp36m-win32.whl",
            "has_sig": false,
            "md5_digest": "b0be1e83b4a88fcfc0d16ea3ae91691c",
            "packagetype": "bdist_wheel",
            "python_version": "cp36",
            "requires_python": null,
            "size": 63421,
            "upload_time": "2024-04-27T17:54:21",
            "upload_time_iso_8601": "2024-04-27T17:54:21.255860Z",
            "url": "https://files.pythonhosted.org/packages/01/3a/d1303ada0bf5c5d04ba32d507d3fdbdfdf240dff847721e2ced796d9850c/stringzilla-3.8.4-cp36-cp36m-win32.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "810d450e4ed798d8d290de97572eca9e96b98b86f0d813d0caa96a1cc21957fd",
                "md5": "ad3c5108718d77d4cede20f80dc5fdd1",
                "sha256": "7ea0b75026949736685bbdce8cc9e37c614b7f920b3cf8fc0471cd3b05eb8132"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp36-cp36m-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "ad3c5108718d77d4cede20f80dc5fdd1",
            "packagetype": "bdist_wheel",
            "python_version": "cp36",
            "requires_python": null,
            "size": 72432,
            "upload_time": "2024-04-27T17:54:22",
            "upload_time_iso_8601": "2024-04-27T17:54:22.623077Z",
            "url": "https://files.pythonhosted.org/packages/81/0d/450e4ed798d8d290de97572eca9e96b98b86f0d813d0caa96a1cc21957fd/stringzilla-3.8.4-cp36-cp36m-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "27bc6628728e9dea2eba52319d93fa27ad63b27ab6176c835b4e717901f0e080",
                "md5": "54206fb047cc2e5d168373a6bf984e02",
                "sha256": "bb6070c412935a8f7a958a40081db5ff453619a4490cd65923af1e16d41418f5"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp37-cp37m-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "54206fb047cc2e5d168373a6bf984e02",
            "packagetype": "bdist_wheel",
            "python_version": "cp37",
            "requires_python": null,
            "size": 73270,
            "upload_time": "2024-04-27T17:54:24",
            "upload_time_iso_8601": "2024-04-27T17:54:24.631559Z",
            "url": "https://files.pythonhosted.org/packages/27/bc/6628728e9dea2eba52319d93fa27ad63b27ab6176c835b4e717901f0e080/stringzilla-3.8.4-cp37-cp37m-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0fcdcc50b00d8d4ec1e3c5031dca4f38409f6d96e4b69c2262e6c4ba902554c0",
                "md5": "60121b8cebef009a32106ed8c2008825",
                "sha256": "eaeba05b216d12a8c482cfd444d8932a7af9fe257100360e9ada2414ee41b338"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "has_sig": false,
            "md5_digest": "60121b8cebef009a32106ed8c2008825",
            "packagetype": "bdist_wheel",
            "python_version": "cp37",
            "requires_python": null,
            "size": 202908,
            "upload_time": "2024-04-27T17:54:26",
            "upload_time_iso_8601": "2024-04-27T17:54:26.604426Z",
            "url": "https://files.pythonhosted.org/packages/0f/cd/cc50b00d8d4ec1e3c5031dca4f38409f6d96e4b69c2262e6c4ba902554c0/stringzilla-3.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "17037ce96058a500fb9d96d9d6d64bb91f8182e056ba2c668f82607ac552f3a8",
                "md5": "9f5063e2e330a2f784acd76b038b617f",
                "sha256": "8b01444a0a2e616b37a98ec636b89bf1e77d6d8e59f1582cd9b735fe1d178239"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "9f5063e2e330a2f784acd76b038b617f",
            "packagetype": "bdist_wheel",
            "python_version": "cp37",
            "requires_python": null,
            "size": 213191,
            "upload_time": "2024-04-27T17:54:28",
            "upload_time_iso_8601": "2024-04-27T17:54:28.547867Z",
            "url": "https://files.pythonhosted.org/packages/17/03/7ce96058a500fb9d96d9d6d64bb91f8182e056ba2c668f82607ac552f3a8/stringzilla-3.8.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b1e22426ee1fd342ebff848f5573b1738da3cb0bfe73b7a23590f175688a7361",
                "md5": "de651e70f9c0d79e734fcc41ac3ce4ac",
                "sha256": "415ad83a66f86afd36f26fade42543cc68532b2527f7f3ffbdf3d31964dba9a7"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "has_sig": false,
            "md5_digest": "de651e70f9c0d79e734fcc41ac3ce4ac",
            "packagetype": "bdist_wheel",
            "python_version": "cp37",
            "requires_python": null,
            "size": 185483,
            "upload_time": "2024-04-27T17:54:30",
            "upload_time_iso_8601": "2024-04-27T17:54:30.716386Z",
            "url": "https://files.pythonhosted.org/packages/b1/e2/2426ee1fd342ebff848f5573b1738da3cb0bfe73b7a23590f175688a7361/stringzilla-3.8.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "178cb421bf68adb58c909542d01e0a39c8b6c60f08f1fa24e0d296da1a1f4a53",
                "md5": "65a1c5c68a63d69362161c87dd6365ce",
                "sha256": "49cd0d9247d215eb71afe130761a80fc4c226a02b43701f06e1fe9782acca0e4"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "has_sig": false,
            "md5_digest": "65a1c5c68a63d69362161c87dd6365ce",
            "packagetype": "bdist_wheel",
            "python_version": "cp37",
            "requires_python": null,
            "size": 253217,
            "upload_time": "2024-04-27T17:54:32",
            "upload_time_iso_8601": "2024-04-27T17:54:32.300217Z",
            "url": "https://files.pythonhosted.org/packages/17/8c/b421bf68adb58c909542d01e0a39c8b6c60f08f1fa24e0d296da1a1f4a53/stringzilla-3.8.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8ae59dbcee75569eb3a64a25d689434ee5d698bdfea66d1af626be0fb3e1fcd9",
                "md5": "3498f8effda731ec17da37575db5016d",
                "sha256": "f43bcc9c18e637bb2aeeca5196af97360db65526b50f1fdfb3639b7a3d73161f"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "has_sig": false,
            "md5_digest": "3498f8effda731ec17da37575db5016d",
            "packagetype": "bdist_wheel",
            "python_version": "cp37",
            "requires_python": null,
            "size": 191170,
            "upload_time": "2024-04-27T17:54:34",
            "upload_time_iso_8601": "2024-04-27T17:54:34.314671Z",
            "url": "https://files.pythonhosted.org/packages/8a/e5/9dbcee75569eb3a64a25d689434ee5d698bdfea66d1af626be0fb3e1fcd9/stringzilla-3.8.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "322cab6a0c935bf32cf38814ffc02d4ba906f18f23e8e992c34a9b41d1731096",
                "md5": "b738a7d647175935397ecdd786ff8395",
                "sha256": "007352f429707b6f737cccca40876aa81fb82358be3c1f401c7d99dcf2fc07f4"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp37-cp37m-musllinux_1_2_aarch64.whl",
            "has_sig": false,
            "md5_digest": "b738a7d647175935397ecdd786ff8395",
            "packagetype": "bdist_wheel",
            "python_version": "cp37",
            "requires_python": null,
            "size": 199204,
            "upload_time": "2024-04-27T17:54:35",
            "upload_time_iso_8601": "2024-04-27T17:54:35.967641Z",
            "url": "https://files.pythonhosted.org/packages/32/2c/ab6a0c935bf32cf38814ffc02d4ba906f18f23e8e992c34a9b41d1731096/stringzilla-3.8.4-cp37-cp37m-musllinux_1_2_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c15d9f8560a8f84f8f2b8f61011b6505c9c2de27eb8bb9f4e724b6aa2a1b703b",
                "md5": "12cbab9458031a2f289c6fae89d3e8ce",
                "sha256": "a81ece28db65ed2f6f886471fa23623267ce19df74062dc3f2ce9044ee2d851d"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp37-cp37m-musllinux_1_2_i686.whl",
            "has_sig": false,
            "md5_digest": "12cbab9458031a2f289c6fae89d3e8ce",
            "packagetype": "bdist_wheel",
            "python_version": "cp37",
            "requires_python": null,
            "size": 190697,
            "upload_time": "2024-04-27T17:54:37",
            "upload_time_iso_8601": "2024-04-27T17:54:37.617687Z",
            "url": "https://files.pythonhosted.org/packages/c1/5d/9f8560a8f84f8f2b8f61011b6505c9c2de27eb8bb9f4e724b6aa2a1b703b/stringzilla-3.8.4-cp37-cp37m-musllinux_1_2_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "99b1f4c031225e0ac06e1c653687467bb346cfd1f1c51dbd8a1554213aea6f85",
                "md5": "e0d950a7109dae4f11b4e28af820665e",
                "sha256": "ea67852d8e10ebb45c0c8b3018c657fb47c9f7898cf6ea9b4e6e2377ae3343c4"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp37-cp37m-musllinux_1_2_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "e0d950a7109dae4f11b4e28af820665e",
            "packagetype": "bdist_wheel",
            "python_version": "cp37",
            "requires_python": null,
            "size": 209732,
            "upload_time": "2024-04-27T17:54:39",
            "upload_time_iso_8601": "2024-04-27T17:54:39.350818Z",
            "url": "https://files.pythonhosted.org/packages/99/b1/f4c031225e0ac06e1c653687467bb346cfd1f1c51dbd8a1554213aea6f85/stringzilla-3.8.4-cp37-cp37m-musllinux_1_2_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5b1be546393c228085380c55e518f9ed4a2405fc31b8573769d1b305d6622c55",
                "md5": "6acd12b299fe04cc842cc0196de5719e",
                "sha256": "28060d6944817a040015541c21843e7488f14d9320a81a683c62c7e5797c7c68"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp37-cp37m-musllinux_1_2_s390x.whl",
            "has_sig": false,
            "md5_digest": "6acd12b299fe04cc842cc0196de5719e",
            "packagetype": "bdist_wheel",
            "python_version": "cp37",
            "requires_python": null,
            "size": 174585,
            "upload_time": "2024-04-27T17:54:41",
            "upload_time_iso_8601": "2024-04-27T17:54:41.070311Z",
            "url": "https://files.pythonhosted.org/packages/5b/1b/e546393c228085380c55e518f9ed4a2405fc31b8573769d1b305d6622c55/stringzilla-3.8.4-cp37-cp37m-musllinux_1_2_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "07c45a47e6c1fd95f511ed9aab3644862b94d25d298757b23942ab8dd89e062b",
                "md5": "2f57b421f026051accfc091ccf0a1e2c",
                "sha256": "8af15f6c03296fbce97680a962da9b69f95c144b8ca1694cdb5edfad70088b2d"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp37-cp37m-musllinux_1_2_x86_64.whl",
            "has_sig": false,
            "md5_digest": "2f57b421f026051accfc091ccf0a1e2c",
            "packagetype": "bdist_wheel",
            "python_version": "cp37",
            "requires_python": null,
            "size": 248424,
            "upload_time": "2024-04-27T17:54:42",
            "upload_time_iso_8601": "2024-04-27T17:54:42.850320Z",
            "url": "https://files.pythonhosted.org/packages/07/c4/5a47e6c1fd95f511ed9aab3644862b94d25d298757b23942ab8dd89e062b/stringzilla-3.8.4-cp37-cp37m-musllinux_1_2_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e49856f4e3a2f8c226d817f8c25c3ecf9f4af007b6abf2499bbe58b2acbc411c",
                "md5": "3d59a8620bf19bb1671465810279fd4b",
                "sha256": "c18aee0f951d6a448e9bca0036c3f84757f59c1c5254318df65fd8f2ca59d47f"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp37-cp37m-win32.whl",
            "has_sig": false,
            "md5_digest": "3d59a8620bf19bb1671465810279fd4b",
            "packagetype": "bdist_wheel",
            "python_version": "cp37",
            "requires_python": null,
            "size": 63467,
            "upload_time": "2024-04-27T17:54:44",
            "upload_time_iso_8601": "2024-04-27T17:54:44.299820Z",
            "url": "https://files.pythonhosted.org/packages/e4/98/56f4e3a2f8c226d817f8c25c3ecf9f4af007b6abf2499bbe58b2acbc411c/stringzilla-3.8.4-cp37-cp37m-win32.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "04a14cbd1b754a65503200abd6786ff6a125b20076a2f5895af40bd010ca2ae8",
                "md5": "34bad3a73ab040be26d46d7909e49f27",
                "sha256": "eb603eeb097b6974959989cbf85b4f563b66bb0572e330e25c2c1610d904bef4"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp37-cp37m-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "34bad3a73ab040be26d46d7909e49f27",
            "packagetype": "bdist_wheel",
            "python_version": "cp37",
            "requires_python": null,
            "size": 72529,
            "upload_time": "2024-04-27T17:54:45",
            "upload_time_iso_8601": "2024-04-27T17:54:45.753021Z",
            "url": "https://files.pythonhosted.org/packages/04/a1/4cbd1b754a65503200abd6786ff6a125b20076a2f5895af40bd010ca2ae8/stringzilla-3.8.4-cp37-cp37m-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "2328d3535307061e97de9ea4ffdee5c456e1b09a7f357a142a900cbc0734f456",
                "md5": "a1f43134da33c9ba56ab4e127d0ee24c",
                "sha256": "be4d203f985464b1df57aff3037ff804b66937ce85d746b6e9c46ad813a06522"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-macosx_10_9_universal2.whl",
            "has_sig": false,
            "md5_digest": "a1f43134da33c9ba56ab4e127d0ee24c",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 112272,
            "upload_time": "2024-04-27T17:54:47",
            "upload_time_iso_8601": "2024-04-27T17:54:47.342684Z",
            "url": "https://files.pythonhosted.org/packages/23/28/d3535307061e97de9ea4ffdee5c456e1b09a7f357a142a900cbc0734f456/stringzilla-3.8.4-cp38-cp38-macosx_10_9_universal2.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "dbc11fd47a3fb3c4b3816517a750007e635f247d3525eebcd9fb9ac5ac91bf9d",
                "md5": "dbd491f87299e7568152f8a3819b0607",
                "sha256": "8181c964aa05490a3d3a68902c739d511e2714cff51678b720277e676de8d782"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "dbd491f87299e7568152f8a3819b0607",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 73374,
            "upload_time": "2024-04-27T17:54:48",
            "upload_time_iso_8601": "2024-04-27T17:54:48.766329Z",
            "url": "https://files.pythonhosted.org/packages/db/c1/1fd47a3fb3c4b3816517a750007e635f247d3525eebcd9fb9ac5ac91bf9d/stringzilla-3.8.4-cp38-cp38-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "33b9a8a723bae73571764a172bba71b809f2af0b98c251dd05bae17c52585fb4",
                "md5": "474c0f716623bd67d0ec884df49811c0",
                "sha256": "05a2c27cdab7c06dd881a08ec0f27855ff2b2a68ec45ad972b1cd97ba007d389"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "474c0f716623bd67d0ec884df49811c0",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 73412,
            "upload_time": "2024-04-27T17:54:50",
            "upload_time_iso_8601": "2024-04-27T17:54:50.366773Z",
            "url": "https://files.pythonhosted.org/packages/33/b9/a8a723bae73571764a172bba71b809f2af0b98c251dd05bae17c52585fb4/stringzilla-3.8.4-cp38-cp38-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "2f29b3617ebdfa2b9158fb3f0c2c08d68b3765dc178f2d243a37b8da67330fb7",
                "md5": "99a325342e23878e8f558f4f77cb51dd",
                "sha256": "fac443cd5256a0b11ea5433e0ec1fc3baaf8c6ddef3c295f408eb236df79350b"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "has_sig": false,
            "md5_digest": "99a325342e23878e8f558f4f77cb51dd",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 205197,
            "upload_time": "2024-04-27T17:54:51",
            "upload_time_iso_8601": "2024-04-27T17:54:51.806288Z",
            "url": "https://files.pythonhosted.org/packages/2f/29/b3617ebdfa2b9158fb3f0c2c08d68b3765dc178f2d243a37b8da67330fb7/stringzilla-3.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5e13a9e422fbe1acca1ac20e9168056e533fe7b63c0745fef3188aa23cbb1005",
                "md5": "fd11f5ad82e6fbea1d35e9d2dc21edbd",
                "sha256": "5c10bf85abffc012c825adb388cc07ea83d6d8f240f10034ea8b382e9e99f15b"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "fd11f5ad82e6fbea1d35e9d2dc21edbd",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 215059,
            "upload_time": "2024-04-27T17:54:53",
            "upload_time_iso_8601": "2024-04-27T17:54:53.401207Z",
            "url": "https://files.pythonhosted.org/packages/5e/13/a9e422fbe1acca1ac20e9168056e533fe7b63c0745fef3188aa23cbb1005/stringzilla-3.8.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f2f75d5faad29c49bebe72cbc1f6a89fd8157a701f82ecbf939259710ee647e6",
                "md5": "ea25a1bd4d073cdff5217d5370921b32",
                "sha256": "628b9a3c51ef0f636b4154b010d6e7499f23f1313d5b1e5957a87496a5fe252e"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "has_sig": false,
            "md5_digest": "ea25a1bd4d073cdff5217d5370921b32",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 187226,
            "upload_time": "2024-04-27T17:54:55",
            "upload_time_iso_8601": "2024-04-27T17:54:55.533290Z",
            "url": "https://files.pythonhosted.org/packages/f2/f7/5d5faad29c49bebe72cbc1f6a89fd8157a701f82ecbf939259710ee647e6/stringzilla-3.8.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "131bb7f47636711e0d3db08cc84f63c952e0a1b0cb3b5c96b84750e7192e8d09",
                "md5": "f4047e65e2eef145ffd02314382b1908",
                "sha256": "522b7e0e5e687849d3c17db478166297e4ad58e4442235cc589c28344e9f759a"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "has_sig": false,
            "md5_digest": "f4047e65e2eef145ffd02314382b1908",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 254671,
            "upload_time": "2024-04-27T17:54:57",
            "upload_time_iso_8601": "2024-04-27T17:54:57.193367Z",
            "url": "https://files.pythonhosted.org/packages/13/1b/b7f47636711e0d3db08cc84f63c952e0a1b0cb3b5c96b84750e7192e8d09/stringzilla-3.8.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "be9446fb38a87ce81a320f144aaf6bb0785ccee108320a868ed2414100ed366d",
                "md5": "fdd5b29b5e221d6e5918655a0d61a659",
                "sha256": "2f7d79b78d45edde8ad288e8520cb5ec28e751e4346278dbb4bb37dba1f019cf"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "has_sig": false,
            "md5_digest": "fdd5b29b5e221d6e5918655a0d61a659",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 193072,
            "upload_time": "2024-04-27T17:54:58",
            "upload_time_iso_8601": "2024-04-27T17:54:58.798670Z",
            "url": "https://files.pythonhosted.org/packages/be/94/46fb38a87ce81a320f144aaf6bb0785ccee108320a868ed2414100ed366d/stringzilla-3.8.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5ea95bfd5dfbe765341f60e2586b5fd531c16c301c5f3cdb7c94b9b2ab1d4409",
                "md5": "89e49c0e94b1d6699f5c2aad8947c57a",
                "sha256": "abf8e0361074bdf686ece702dc8ee4b96e77ec16cdac525fc371915be1393858"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-musllinux_1_2_aarch64.whl",
            "has_sig": false,
            "md5_digest": "89e49c0e94b1d6699f5c2aad8947c57a",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 201320,
            "upload_time": "2024-04-27T17:55:00",
            "upload_time_iso_8601": "2024-04-27T17:55:00.615466Z",
            "url": "https://files.pythonhosted.org/packages/5e/a9/5bfd5dfbe765341f60e2586b5fd531c16c301c5f3cdb7c94b9b2ab1d4409/stringzilla-3.8.4-cp38-cp38-musllinux_1_2_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "2d577f681b09cf72b6397af1160b0de1bc6f24c277ffa28a4964e6da2e7db883",
                "md5": "5ad659be10d246ea2494234c20ebbcfe",
                "sha256": "6cee3815395aee8dcfe90a9902473709db3be0d6aa3c7cbc221ec6b512960af4"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-musllinux_1_2_i686.whl",
            "has_sig": false,
            "md5_digest": "5ad659be10d246ea2494234c20ebbcfe",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 192266,
            "upload_time": "2024-04-27T17:55:03",
            "upload_time_iso_8601": "2024-04-27T17:55:03.313265Z",
            "url": "https://files.pythonhosted.org/packages/2d/57/7f681b09cf72b6397af1160b0de1bc6f24c277ffa28a4964e6da2e7db883/stringzilla-3.8.4-cp38-cp38-musllinux_1_2_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "1ef4ac856601d929f0a2cc84bd94d54f8cf7431ae578480fca373ff7c4e096a3",
                "md5": "5284267049b284b20fca96200bf972a2",
                "sha256": "c875d36d96ceb6f3202e1f27df844c7b081af6ae7b0a38099e5b30c32aded82c"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-musllinux_1_2_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "5284267049b284b20fca96200bf972a2",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 211517,
            "upload_time": "2024-04-27T17:55:05",
            "upload_time_iso_8601": "2024-04-27T17:55:05.042031Z",
            "url": "https://files.pythonhosted.org/packages/1e/f4/ac856601d929f0a2cc84bd94d54f8cf7431ae578480fca373ff7c4e096a3/stringzilla-3.8.4-cp38-cp38-musllinux_1_2_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f7a59e6721a6cf6f95e3e60d859199536715bb3501e5f1fbf4169f7c903718c4",
                "md5": "99912cc5cf763efa55c8e4fc70bf0d27",
                "sha256": "d3ec288d8a8b4567459270edc3f94118cb77b725a7e689936e9fb639e4f07d0b"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-musllinux_1_2_s390x.whl",
            "has_sig": false,
            "md5_digest": "99912cc5cf763efa55c8e4fc70bf0d27",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 176074,
            "upload_time": "2024-04-27T17:55:06",
            "upload_time_iso_8601": "2024-04-27T17:55:06.940001Z",
            "url": "https://files.pythonhosted.org/packages/f7/a5/9e6721a6cf6f95e3e60d859199536715bb3501e5f1fbf4169f7c903718c4/stringzilla-3.8.4-cp38-cp38-musllinux_1_2_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "4235cd53a5eb6b23126e6d99ab013191806705e2ffb9844d78edb6c5e5359152",
                "md5": "a8fe24809abdc9672ae7238a6c251521",
                "sha256": "04917a08e980d51679388f8bb98544471ae4e2ecc54bdc7f0877a6c79637b92c"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-musllinux_1_2_x86_64.whl",
            "has_sig": false,
            "md5_digest": "a8fe24809abdc9672ae7238a6c251521",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 250051,
            "upload_time": "2024-04-27T17:55:08",
            "upload_time_iso_8601": "2024-04-27T17:55:08.494475Z",
            "url": "https://files.pythonhosted.org/packages/42/35/cd53a5eb6b23126e6d99ab013191806705e2ffb9844d78edb6c5e5359152/stringzilla-3.8.4-cp38-cp38-musllinux_1_2_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3afb56d1795b204499fa923e686ab96bf69cbdf05b8a8b6373713fbc2c6d8db4",
                "md5": "674b31c50dd98143887292e3021c9e38",
                "sha256": "8adb3cf998c10e9718b2f11dcc7e251685038c406166add1f72d51fc9c8a8e4c"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-win32.whl",
            "has_sig": false,
            "md5_digest": "674b31c50dd98143887292e3021c9e38",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 63486,
            "upload_time": "2024-04-27T17:55:10",
            "upload_time_iso_8601": "2024-04-27T17:55:10.241608Z",
            "url": "https://files.pythonhosted.org/packages/3a/fb/56d1795b204499fa923e686ab96bf69cbdf05b8a8b6373713fbc2c6d8db4/stringzilla-3.8.4-cp38-cp38-win32.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ece817d300366eb2556c5704424415ac513225e0d663be2dfde1736669c68979",
                "md5": "eda181f2c087e74c6ad62d3d84b17aea",
                "sha256": "a0c072faac344f118ae95c6876ead2c5c6fcaa2fd1054e1b173007fc6da35930"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp38-cp38-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "eda181f2c087e74c6ad62d3d84b17aea",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": null,
            "size": 72554,
            "upload_time": "2024-04-27T17:55:11",
            "upload_time_iso_8601": "2024-04-27T17:55:11.810869Z",
            "url": "https://files.pythonhosted.org/packages/ec/e8/17d300366eb2556c5704424415ac513225e0d663be2dfde1736669c68979/stringzilla-3.8.4-cp38-cp38-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5bf68af6e48987ed3a1657e7c947ed46ad62f2ce98bf0a868a08f5431ce11650",
                "md5": "d76c8ae3bf7003040fd502e24362b4f1",
                "sha256": "8f2aa9014cd0f3580ceaa265c38029d87997c7be030dbff8f34de5afc21e2885"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-macosx_10_9_universal2.whl",
            "has_sig": false,
            "md5_digest": "d76c8ae3bf7003040fd502e24362b4f1",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 112273,
            "upload_time": "2024-04-27T17:55:13",
            "upload_time_iso_8601": "2024-04-27T17:55:13.441539Z",
            "url": "https://files.pythonhosted.org/packages/5b/f6/8af6e48987ed3a1657e7c947ed46ad62f2ce98bf0a868a08f5431ce11650/stringzilla-3.8.4-cp39-cp39-macosx_10_9_universal2.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "cbe41d463ba0798e7f8ead2118c9f7b7328fd322e3bc5eb7a16c0892fd57508e",
                "md5": "93f32ab39ab9249be78e3e58d3305565",
                "sha256": "da6c69c3857cc69cbb85e9749c4285270cee90fc46a2140182166aee723fe705"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "93f32ab39ab9249be78e3e58d3305565",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 73367,
            "upload_time": "2024-04-27T17:55:15",
            "upload_time_iso_8601": "2024-04-27T17:55:15.365610Z",
            "url": "https://files.pythonhosted.org/packages/cb/e4/1d463ba0798e7f8ead2118c9f7b7328fd322e3bc5eb7a16c0892fd57508e/stringzilla-3.8.4-cp39-cp39-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "fe042b1e3e5945b8d3a0792fbf9a8cba64eee6ee813648a9fd7f365b8edfa71d",
                "md5": "4a783cbb4017b90b556e4acbe34beb93",
                "sha256": "3ae603283924861690549b22fa95d4847d6dab9a7f46ecfe027acb50de25895f"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "4a783cbb4017b90b556e4acbe34beb93",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 73406,
            "upload_time": "2024-04-27T17:55:16",
            "upload_time_iso_8601": "2024-04-27T17:55:16.961501Z",
            "url": "https://files.pythonhosted.org/packages/fe/04/2b1e3e5945b8d3a0792fbf9a8cba64eee6ee813648a9fd7f365b8edfa71d/stringzilla-3.8.4-cp39-cp39-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ffcc8e361f789be42dd348955fdef3dc8f90653795ed4af68dfdd0154a538917",
                "md5": "57a18a7a619d0cf33411b9d4a626b576",
                "sha256": "0dc474ca794385c91bb143e7d40091ae3763e022043045e45440609ad18f6602"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "has_sig": false,
            "md5_digest": "57a18a7a619d0cf33411b9d4a626b576",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 206275,
            "upload_time": "2024-04-27T17:55:19",
            "upload_time_iso_8601": "2024-04-27T17:55:19.148019Z",
            "url": "https://files.pythonhosted.org/packages/ff/cc/8e361f789be42dd348955fdef3dc8f90653795ed4af68dfdd0154a538917/stringzilla-3.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "41706ec81436654719529c2fd44cb51d8bab6cf4571825a4f519e1c36a6b00c9",
                "md5": "1681eaa14b0ded7801e8d86a6a36851d",
                "sha256": "fe9f04c6b8ccb628c4602510032d774e0184dcccc5953c1cdec4e06d8ab7e9d7"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "1681eaa14b0ded7801e8d86a6a36851d",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 215991,
            "upload_time": "2024-04-27T17:55:20",
            "upload_time_iso_8601": "2024-04-27T17:55:20.738482Z",
            "url": "https://files.pythonhosted.org/packages/41/70/6ec81436654719529c2fd44cb51d8bab6cf4571825a4f519e1c36a6b00c9/stringzilla-3.8.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "fc0c6f0e006056152a94eed837f6047024c7da10a2e9d316305a937d06507ebd",
                "md5": "025cecba217922587f3e4e5c6360e433",
                "sha256": "e137bb5f62e34473f7da805b033e435a4a3c8eb82f60621fff3c793e7a61298a"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "has_sig": false,
            "md5_digest": "025cecba217922587f3e4e5c6360e433",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 188156,
            "upload_time": "2024-04-27T17:55:23",
            "upload_time_iso_8601": "2024-04-27T17:55:23.133161Z",
            "url": "https://files.pythonhosted.org/packages/fc/0c/6f0e006056152a94eed837f6047024c7da10a2e9d316305a937d06507ebd/stringzilla-3.8.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "65d21c8727da0c933ebdbacbf22c82160e783c673f1941f53ebd754598917b76",
                "md5": "e6e41060d6accdf38f2e132ee2360cad",
                "sha256": "4f38a61b20788c69698d4a23a2977a77edd81d9e6f428af324f2b1674ea59ba5"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "has_sig": false,
            "md5_digest": "e6e41060d6accdf38f2e132ee2360cad",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 255395,
            "upload_time": "2024-04-27T17:55:24",
            "upload_time_iso_8601": "2024-04-27T17:55:24.626743Z",
            "url": "https://files.pythonhosted.org/packages/65/d2/1c8727da0c933ebdbacbf22c82160e783c673f1941f53ebd754598917b76/stringzilla-3.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f3c571e3e59f02e7bf9f85bba39363df51a74219ee24bf4f2d9a6a14663bf691",
                "md5": "fdb540b556b2ccd89b99d9572c29bd79",
                "sha256": "e114d5310c2f592bcd7fc0f4538ab252da15fad5686831cdef4ad9bbe34b634d"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "has_sig": false,
            "md5_digest": "fdb540b556b2ccd89b99d9572c29bd79",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 194028,
            "upload_time": "2024-04-27T17:55:26",
            "upload_time_iso_8601": "2024-04-27T17:55:26.471714Z",
            "url": "https://files.pythonhosted.org/packages/f3/c5/71e3e59f02e7bf9f85bba39363df51a74219ee24bf4f2d9a6a14663bf691/stringzilla-3.8.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "51faaa47de559a3ddab8c4d921326ecc0ab4e8272df942000884fe1627470b88",
                "md5": "bac6b8fca48fd00186bf10c9a21f0348",
                "sha256": "69a1b607259c8b30567bccb073735b2e317389466ddd65467c5028f8dcdd1b2d"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-musllinux_1_2_aarch64.whl",
            "has_sig": false,
            "md5_digest": "bac6b8fca48fd00186bf10c9a21f0348",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 202318,
            "upload_time": "2024-04-27T17:55:28",
            "upload_time_iso_8601": "2024-04-27T17:55:28.080568Z",
            "url": "https://files.pythonhosted.org/packages/51/fa/aa47de559a3ddab8c4d921326ecc0ab4e8272df942000884fe1627470b88/stringzilla-3.8.4-cp39-cp39-musllinux_1_2_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d8bfa23ffb4ef307b291bd3347ec67b4828b50388929d62aa658234f797bef0a",
                "md5": "cbe9e39a7f4844442360b2042d7a8cc1",
                "sha256": "afed8d4698cd556f52ca1432bc71d8931f7d37e2c5abbffcc854ace6607512df"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-musllinux_1_2_i686.whl",
            "has_sig": false,
            "md5_digest": "cbe9e39a7f4844442360b2042d7a8cc1",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 192964,
            "upload_time": "2024-04-27T17:55:29",
            "upload_time_iso_8601": "2024-04-27T17:55:29.644208Z",
            "url": "https://files.pythonhosted.org/packages/d8/bf/a23ffb4ef307b291bd3347ec67b4828b50388929d62aa658234f797bef0a/stringzilla-3.8.4-cp39-cp39-musllinux_1_2_i686.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bdda193689651ff10af821f9b93368007dbff8530d6c74b10473d0eb18648bfb",
                "md5": "10345babd147c9285bcf841064022187",
                "sha256": "5ddb135b132d36084382753d51de0a485422c7564e19714fe6ef27773c500322"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-musllinux_1_2_ppc64le.whl",
            "has_sig": false,
            "md5_digest": "10345babd147c9285bcf841064022187",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 212354,
            "upload_time": "2024-04-27T17:55:31",
            "upload_time_iso_8601": "2024-04-27T17:55:31.292134Z",
            "url": "https://files.pythonhosted.org/packages/bd/da/193689651ff10af821f9b93368007dbff8530d6c74b10473d0eb18648bfb/stringzilla-3.8.4-cp39-cp39-musllinux_1_2_ppc64le.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f33d465ae1571fcda10c4c230a5269ec549e6cf625848cf3976e52550e2a0cf2",
                "md5": "4f3ee74ba62eb3eacfd106bcaccea110",
                "sha256": "fbea73089546a3c5f618f76e90a1d23b1d2acb079a195db3be496c2a312bd26d"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-musllinux_1_2_s390x.whl",
            "has_sig": false,
            "md5_digest": "4f3ee74ba62eb3eacfd106bcaccea110",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 177066,
            "upload_time": "2024-04-27T17:55:32",
            "upload_time_iso_8601": "2024-04-27T17:55:32.992729Z",
            "url": "https://files.pythonhosted.org/packages/f3/3d/465ae1571fcda10c4c230a5269ec549e6cf625848cf3976e52550e2a0cf2/stringzilla-3.8.4-cp39-cp39-musllinux_1_2_s390x.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "159291c119467ca5115eeb2d51ed55b723ec88cd589e8b2fcf74b1134fc1c619",
                "md5": "0ef872b638036dad0626944e367cf95f",
                "sha256": "62031223bc03254284fe8b8b8dfcdce6f53b4c2f03151a2dcc6ee93f1b13da90"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-musllinux_1_2_x86_64.whl",
            "has_sig": false,
            "md5_digest": "0ef872b638036dad0626944e367cf95f",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 250851,
            "upload_time": "2024-04-27T17:55:34",
            "upload_time_iso_8601": "2024-04-27T17:55:34.626546Z",
            "url": "https://files.pythonhosted.org/packages/15/92/91c119467ca5115eeb2d51ed55b723ec88cd589e8b2fcf74b1134fc1c619/stringzilla-3.8.4-cp39-cp39-musllinux_1_2_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d52ebcf2e84dedd0ea54cd72b49f5b6fac0e06fd0fad1483c588ed280ea9c02e",
                "md5": "b04b6b3c79efb736a9d7002c54c78bbf",
                "sha256": "3a91eda1c16a31d97a2b94f0bf10446f5e71050a3fcd557f3978f8771708c9fb"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-win32.whl",
            "has_sig": false,
            "md5_digest": "b04b6b3c79efb736a9d7002c54c78bbf",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 63487,
            "upload_time": "2024-04-27T17:55:36",
            "upload_time_iso_8601": "2024-04-27T17:55:36.201970Z",
            "url": "https://files.pythonhosted.org/packages/d5/2e/bcf2e84dedd0ea54cd72b49f5b6fac0e06fd0fad1483c588ed280ea9c02e/stringzilla-3.8.4-cp39-cp39-win32.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "fb423d125aacde0b44158416992e5ca2bbc5364cc0eec1d1e4adee3ac4d320ef",
                "md5": "c8e0e8788f5207d8a9df31b4282720c4",
                "sha256": "fd48113734e40c071e18f0dfa73508f4c3ad59f83c9f753c2e07119271ca2623"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "c8e0e8788f5207d8a9df31b4282720c4",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 74218,
            "upload_time": "2024-04-27T17:55:37",
            "upload_time_iso_8601": "2024-04-27T17:55:37.958966Z",
            "url": "https://files.pythonhosted.org/packages/fb/42/3d125aacde0b44158416992e5ca2bbc5364cc0eec1d1e4adee3ac4d320ef/stringzilla-3.8.4-cp39-cp39-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "127bbf3e00571b00a86554b31cc549c01efbea6b85960ccfc9e7b38e3513cf97",
                "md5": "2205fd70c43da08d880206fbbf43aafa",
                "sha256": "265fbea2c28f6c4f0246978f19faf7053e0f60d786f64bfd2854b2db78c3f429"
            },
            "downloads": -1,
            "filename": "stringzilla-3.8.4-cp39-cp39-win_arm64.whl",
            "has_sig": false,
            "md5_digest": "2205fd70c43da08d880206fbbf43aafa",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": null,
            "size": 64188,
            "upload_time": "2024-04-27T17:55:39",
            "upload_time_iso_8601": "2024-04-27T17:55:39.823105Z",
            "url": "https://files.pythonhosted.org/packages/12/7b/bf3e00571b00a86554b31cc549c01efbea6b85960ccfc9e7b38e3513cf97/stringzilla-3.8.4-cp39-cp39-win_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-27 17:52:43",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "stringzilla"
}
        
Elapsed time: 0.27074s