A Goblin Reactor product

Edit distance

stride-align documentation

Algorithms that count the cheapest sequence of single-character operations needed to transform one sequence into another: Levenshtein, Damerau-Levenshtein (two flavours), Indel, Hamming.

All edit-distance scorers in this module accept bytes, str (UCS-1 / UCS-2 / UCS-4 — Chinese, Japanese, Korean, Arabic, emoji all route into the SIMD path without a UTF-8 round-trip), and any NumPy integer ndarray. Inputs at different widths are not mixed within one call.

Output-processing variants

Every algorithm exposes the same family of entry points; the suffix controls how results are returned. <algo> below stands for any of levenshtein, damerau_levenshtein, true_damerau_levenshtein, indel, hamming.

Suffix Signature Return type Use when
<algo>_scores(query, targets) one-to-many distance ndarray[int64] shape (len(targets),) you have one query and want raw distances to every candidate
<algo>_normalized_scores(query, targets) one-to-many similarity ndarray[float64] in [0, 1] you want length-normalised similarity (1.0 == identical, 0.0 == fully different)
<algo>_best(query, targets) one-to-one (target, int_distance) or None you only need the single closest target — saves the heap step
<algo>_normalized_best(query, targets) one-to-one normalised (target, float_similarity) or None best target by normalised similarity rather than raw distance

Cross-product (queries × targets) and top-k workloads live on the cdist family — see cdist.

All variants release the GIL while the SIMD kernel runs, dispatch the best backend for the current CPU, and accept score_cutoff / prefix_weight / n= kwargs where the algorithm supports them.

Algorithms

levenshtein_* — classic Levenshtein

Cost-1 insertions, deletions, and substitutions. Bit-parallel Myers backend (single-word for len <= 64, K-specialised multi-word kernels for K=2..K=4, generic K beyond). Supports score_cutoff for early bail.

import stride_align as sa

sa.levenshtein_scores("kitten", ["sitting", "kitchen", "mittens"])
# array([3, 2, 2], dtype=int64)

sa.levenshtein_normalized_best("北京大学", ["清华大学", "北京交通大学", "复旦大学"])
# ('北京交通大学', 0.5)

damerau_levenshtein_* — OSA (optimal-string-alignment) variant

Adds cost-1 transposition of adjacent characters, with the restriction that a substring can be edited at most once. This is what most libraries (and rapidfuzz.distance.OSA) actually implement; what they call "Damerau-Levenshtein" is usually OSA. Cheap kernel, suitable for sub-100-char fuzzy matching.

true_damerau_levenshtein_* — unrestricted Damerau-Levenshtein

The unrestricted version that allows multiple edits on the same substring — Damerau's original 1964 definition. Byte-specialised flat Lowrance-Wagner DP with an array-indexed last-occurrence table. Use when correctness on overlapping transpositions matters (linguistic analysis, OCR error models).

indel_* — insert-and-delete only

Substitutions disallowed; matches the algorithm behind rapidfuzz.distance.Indel and the underlying engine for fuzz.ratio. Identity: indel_normalized_score == fuzz.ratio / 100. Hyyrö fused single-step recurrence with kernel-level score_cutoff early-exit.

hamming_* — equal-length substitutions

Cost-1 substitutions only, requires len(query) == len(target) — raises ValueError on the first mismatched target. Optional pad kwarg in the rapidfuzz shim form.

Cutoff push-down

Edit-distance kernels accept score_cutoff=k. If a target's provable lower bound on the distance exceeds k, the per-target loop bails out and the lane reports k + 1 (the cutoff sentinel) instead of finishing. Cheaper than computing the full DP and filtering afterwards.

sa.levenshtein_scores("kitten", targets, score_cutoff=2)
# Any target whose true distance is >= 3 returns 3 (the sentinel).

SIMD dispatch

The runtime picks the widest supported backend at first call (stride_align._cpu.detect_best_backend()) — x86 SSE4.1 / AVX2 / AVX-512BW+VL / AVX10-256 / AVX10-512, ARM NEON / SVE / SVE2, LoongArch LSX / LASX, PowerPC VSX, with a scalar fallback. The choice is logged once at backend import. No per-call dispatch overhead.