Download the PHP package zbmowrey/weighted-levenshtein without Composer
On this page you can find all versions of the php package zbmowrey/weighted-levenshtein. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download zbmowrey/weighted-levenshtein
More information about zbmowrey/weighted-levenshtein
Files in zbmowrey/weighted-levenshtein
Package weighted-levenshtein
Short Description Weighted Levenshtein, Optimal String Alignment, and Damerau-Levenshtein distance for PHP 8.4+ — a port of infoscout/weighted-levenshtein.
License MIT
Informations about the package weighted-levenshtein
weighted-levenshtein
Weighted Levenshtein, Optimal String Alignment, and Damerau-Levenshtein edit-distance algorithms for PHP 8.4+. Pure PHP, strict types, no extensions.
PHP port of weighted-levenshtein by David Su / InfoScout. The algorithms, default behavior, and asymmetric cost semantics are preserved exactly; only the API has been reshaped to feel native in modern PHP.
Why weighted distance?
Most edit-distance libraries treat every insertion, deletion, substitution, or transposition as cost 1. That's fine for generic fuzzy-matching but often not what you actually want.
- OCR correction. Substituting
0forOis a likely confusion; substitutingXforOis not. Give the first a smaller cost. - Typo correction. On a QWERTY keyboard,
XandZare neighbors;XandKare not. Or: humans transpose adjacent letters frequently, so transpositions should be cheap. - Domain-specific noise. If your input pipeline tends to drop trailing whitespace or duplicate digits, weight those operations accordingly.
This library lets you specify a cost per character (for insert/delete) and per ordered pair (for substitute/transpose), then runs the appropriate dynamic-programming algorithm.
Installation
Requires PHP 8.4 or newer.
Quick start
Cost maps are immutable value objects. Build them with withCost():
Full examples
These are the same examples as the original Python README, translated 1:1. Every snippet has a corresponding test in tests/Readme/.
Short aliases are available as free functions:
Built-in cost map presets
Two opinionated presets ship under Zbmowrey\WeightedLevenshtein\Presets\ for the two most common weighted-distance use cases. Both return immutable CharPairCostMap instances, so you can layer your own overrides with withCost().
OCR output (OcrConfusions)
OcrConfusions::common(float $cost = 0.25) covers a curated list of high-confidence OCR confusions in both directions. The full set:
- Zero cluster:
0↔O↔o,0↔D,O↔D,O↔Q - One cluster:
1↔l↔I↔i(every pair) - Seven cluster:
1↔7,2↔7,7↔T,7↔Z↔z - Other digit/digit:
2↔Z↔z,3↔5,3↔8 - Other digit/letter:
5↔S↔s,8↔B,6↔G,6↔b,9↔g,9↔q - Lowercase letter pairs:
c↔e,n↔h,u↔v,m↔n,f↔t,r↔n - Uppercase letter pairs:
C↔G,E↔F,M↔N,P↔R,U↔V,V↔Y
The list is intentionally conservative — only pairs that confuse across most fonts are included. Font-specific or low-resolution-only confusions (e.g. 4↔A, 0↔6, n↔u) and multi-character confusions (rn↔m, cl↔d, vv↔w) are not registered. Layer them on with withCost() if your data needs them.
Pairs not in the list keep the default cost of 1.0. Layer your domain-specific tweaks with withCost():
Human typos (QwertyKeyboard)
Costs are derived from the Euclidean distance between keys on a standard staggered US QWERTY layout. Orthogonal and close-diagonal neighbors get the adjacent cost (default 0.5); one-key-removed pairs get the near cost (default 0.75); everything else stays at 1.0. Both lowercase and uppercase letters are populated. Mixed-case and mixed-shift-state pairs (e.g. q/W, Q/1) are left at default — those errors are rare in practice.
Override the thresholds if 0.5/0.75 don't fit your data:
API reference
Zbmowrey\WeightedLevenshtein\Distance
| Method | Description |
|---|---|
Distance::levenshtein(string $a, string $b, ?CharCostMap $insertCosts = null, ?CharCostMap $deleteCosts = null, ?CharPairCostMap $substituteCosts = null): float |
Wagner-Fischer Levenshtein distance. |
Distance::optimalStringAlignment(string $a, string $b, ?CharCostMap $insertCosts = null, ?CharCostMap $deleteCosts = null, ?CharPairCostMap $substituteCosts = null, ?CharPairCostMap $transposeCosts = null): float |
Wagner-Fischer with a single adjacent transposition check; substrings used in a transposition cannot also be edited. |
Distance::damerauLevenshtein(string $a, string $b, ?CharCostMap $insertCosts = null, ?CharCostMap $deleteCosts = null, ?CharPairCostMap $substituteCosts = null, ?CharPairCostMap $transposeCosts = null): float |
True Damerau-Levenshtein distance with arbitrary non-overlapping adjacent transpositions. |
Zbmowrey\WeightedLevenshtein\CharCostMap
Immutable per-character cost map for insert/delete operations.
| Method | Description |
|---|---|
CharCostMap::uniform(float $defaultCost = 1.0): self |
Construct a map where every character has cost $defaultCost. |
withCost(string $char, float $cost): self |
Return a new map with $cost for the single ASCII byte $char. |
cost(string $char): float |
Look up the cost for $char. |
Zbmowrey\WeightedLevenshtein\CharPairCostMap
Immutable per-ordered-pair cost map for substitute/transpose operations.
| Method | Description |
|---|---|
CharPairCostMap::uniform(float $defaultCost = 1.0): self |
Construct a map where every ordered pair has cost $defaultCost. |
withCost(string $from, string $to, float $cost): self |
Return a new map with $cost for the ordered pair ($from, $to). |
cost(string $from, string $to): float |
Look up the cost for the ordered pair. |
Zbmowrey\WeightedLevenshtein\Presets\OcrConfusions
| Method | Description |
|---|---|
OcrConfusions::common(float $cost = 0.25): CharPairCostMap |
Curated OCR confusion substitutions in both directions. |
Zbmowrey\WeightedLevenshtein\Presets\QwertyKeyboard
| Method | Description |
|---|---|
QwertyKeyboard::substituteCosts(float $adjacentCost = 0.5, float $nearCost = 0.75): CharPairCostMap |
Adjacency-weighted substitution cost map for ASCII letters and digits on a US QWERTY layout. |
QwertyKeyboard::transposeCosts(float $adjacentCost = 0.5, float $nearCost = 0.75): CharPairCostMap |
Adjacency-weighted transposition cost map using the same layout. |
Free function aliases
In namespace Zbmowrey\WeightedLevenshtein:
lev(...)— alias forDistance::levenshtein(...).osa(...)— alias forDistance::optimalStringAlignment(...).dam_lev(...)— alias forDistance::damerauLevenshtein(...).
Limitations
- ASCII only. Inputs must consist of bytes 0–127. Any byte ≥ 128 raises
InvalidArgumentException. This matches the original library, whose internal arrays are indexed byord()over a 128-wide alphabet. - Case sensitive.
'a'and'A'are distinct characters. - Costs are asymmetric.
CharPairCostMapis keyed by ordered pairs. Setting('A', 'B')does not set('B', 'A'). If you want symmetry, set both.
Performance
Pure PHP. Suitable for typical fuzzy-matching workloads in the range of low thousands of comparisons per second on strings of ~100 characters. If you need C-speed, use the original Python library, or PHP's built-in levenshtein() for the unweighted case. Two-row rolling buffer is used for plain Levenshtein; full DP matrices are used for OSA and Damerau-Levenshtein because both need O(m × n) state.
Contributing
Contributions welcome. Open an issue or PR. The full QA suite is composer qa (PHPUnit + PHPStan level max + PHP-CS-Fixer dry-run). All three must be green for a PR to land.
License
MIT. See LICENSE. PHP port copyright © 2026 Zach Mowrey. Original Python library copyright © 2016 InfoScout, distributed under the MIT License.