Phonetics 0.1.0
dotnet add package Phonetics --version 0.1.0
NuGet\Install-Package Phonetics -Version 0.1.0
<PackageReference Include="Phonetics" Version="0.1.0" />
<PackageVersion Include="Phonetics" Version="0.1.0" />
<PackageReference Include="Phonetics" />
paket add Phonetics --version 0.1.0
#r "nuget: Phonetics, 0.1.0"
#:package Phonetics@0.1.0
#addin nuget:?package=Phonetics&version=0.1.0
#tool nuget:?package=Phonetics&version=0.1.0
Phonetics
Phonetic name encoding and sound-alike matching for .NET. Encode words by sound so that names spelled differently but pronounced alike still match. Zero external dependencies.
using Phonetics;
Phonetic.SoundsLike("Catherine", "Kathryn"); // true
Phonetic.SoundsLike("Smith", "Smyth"); // true
Phonetic.SoundsLike("Robert", "Rupert"); // true
Phonetic algorithms are the core of deduplication, record linkage, name screening, and search: they turn Robert and Rupert, Catherine and Kathryn, Smith and Smyth into the same code. Python teams reach for jellyfish (about 12 million downloads a month) and Java teams for Apache commons-codec. On .NET the incumbent was Phonix, which collected 946 thousand downloads and then stopped: its last release was February 2020. Phonetics is a from-scratch, faithfully ported, and exhaustively tested successor, and the maintained companion to the author's FuzzMatch (fuzzy string distance).
Install
dotnet add package Phonetics
Quickstart
using Phonetics;
Phonetic.Soundex("Robert"); // "R163" (Rupert is also R163)
Phonetic.Metaphone("Thompson"); // "0MPS" (0 is the soft TH sound)
Phonetic.DoubleMetaphone("Smith"); // ("SM0", "XMT") primary and alternate
Phonetic.Nysiis("Catherine"); // "CATARA"
Phonetic.MatchRatingCodex("Smith"); // "SMTH"
Phonetic.Caverphone("Thompson"); // "TMPSN11111"
Phonetic.ColognePhonetic("Wikipedia"); // "3412"
// Compare two words by sound (Double Metaphone by default):
Phonetic.SoundsLike("Catherine", "Kathryn"); // true
Phonetic.SoundsLike("Smith", "Smyth", PhoneticAlgorithm.MatchRatingApproach); // true
Algorithms
All eight algorithms are faithful ports of Apache commons-codec 1.17.1 and are verified against its published test vectors (see Correctness). Double Metaphone is the recommended default: Soundex is kept for legacy compatibility but is the weakest of the set.
| Algorithm | Good for | Example | Reference / oracle |
|---|---|---|---|
Soundex |
Legacy systems, the classic 4-char code | Soundex("Robert") → R163 |
commons-codec Soundex.US_ENGLISH, cross-checked with jellyfish |
RefinedSoundex |
Spell checking, finer buckets | RefinedSoundex("Robert") → R901096 |
commons-codec RefinedSoundex |
Metaphone |
English pronunciation, compact code | Metaphone("Thompson") → 0MPS |
commons-codec Metaphone (Philips' original) |
DoubleMetaphone |
The workhorse: names of any origin | DoubleMetaphone("Smith") → SM0 / XMT |
commons-codec DoubleMetaphone |
Nysiis |
US name matching, better than Soundex | Nysiis("Catherine") → CATARA |
commons-codec Nysiis (strict) |
MatchRatingApproach |
Encode plus a built-in comparison | MatchRatingCodex("Smith") → SMTH |
commons-codec MatchRatingApproachEncoder |
Caverphone (2.0) |
Genealogy, English-language names | Caverphone("Thompson") → TMPSN11111 |
commons-codec Caverphone2 |
ColognePhonetic |
German-language names | ColognePhonetic("Wikipedia") → 3412 |
commons-codec ColognePhonetic |
Caverphone1(string) produces the original Caverphone 1.0 code. DoubleMetaphone(string) returns a tuple (Primary, Alternate); the alternate captures a second plausible pronunciation and equals the primary when there is only one.
var (primary, alternate) = Phonetic.DoubleMetaphone("Kuczewski");
// primary == "KSSK", alternate == "KXFS"
SoundsLike comparison semantics
Phonetic.SoundsLike(a, b, algorithm) encodes both inputs and compares them. The comparison rule depends on the algorithm:
- Double Metaphone (the default): a match if any of the four cross comparisons of the two primary and two alternate codes agree on a non-empty code (
primaryA == primaryB,primaryA == alternateB,alternateA == primaryB, oralternateA == alternateB). This is the standard Double Metaphone matching rule and is looser, and more forgiving, than comparing primaries alone. - Match Rating Approach: the MRA minimum-rating comparison. Both names are encoded, then compared position by position from both ends; the number of unmatched characters must meet a length-based minimum rating. Names whose codex lengths differ by three or more never match.
- Every other algorithm: exact equality of the two codes (both must be non-empty).
Phonetic.SoundsLike("Robert", "Rupert", PhoneticAlgorithm.Soundex); // true
Phonetic.SoundsLike("Meyer", "Mayr", PhoneticAlgorithm.ColognePhonetic); // true
Phonetic.SoundsLike("Byrne", "Boern", PhoneticAlgorithm.MatchRatingApproach); // true
Encoders
The static Phonetic methods delegate to a default instance of each encoder. To hold a configured encoder, or to pass one around behind the IPhoneticEncoder interface, construct it directly. Encoders are immutable and thread-safe.
IPhoneticEncoder encoder = new DoubleMetaphoneEncoder(maxCodeLength: 6);
string code = encoder.Encode("Wojciechowski");
var soundex = new SoundexEncoder(maxLength: 6, paddingChar: null); // no padding
soundex.Encode("Robert"); // "R163"
var nysiisFull = new NysiisEncoder(strict: false); // no six-character truncation
DoubleMetaphoneEncoder also exposes EncodeBoth(string) returning both codes, and MatchRatingApproachEncoder exposes SoundsLike(a, b) directly.
Name matching: the three-part arc
Phonetics is one piece of a name-matching pipeline:
- Phonetics buckets by sound. Group records by a phonetic key so that only plausibly-similar names are ever compared.
- FuzzMatch scores within the bucket. A fuzzy distance (Levenshtein, Jaro-Winkler, token ratios) ranks the close spellings inside each bucket.
- Sanctions.Net screens against watch lists. Name screening against sanctions and PEP lists is this exact problem wrapped in list management.
The bucket-then-score pattern is the standard way to make record linkage both accurate and fast. Bucketing alone over-merges; scoring every pair is quadratic. Together they are neither.
using Phonetics;
string[] names = ["Catherine", "Kathryn", "Katharine", "Catharine", "Katrina"];
var buckets = names
.GroupBy(name => Phonetic.DoubleMetaphone(name).Primary)
.ToDictionary(group => group.Key, group => group.ToArray());
// buckets["K0RN"] == ["Catherine", "Kathryn", "Katharine", "Catharine"]
// buckets["KTRN"] == ["Katrina"]
Within each bucket you would then reach for FuzzMatch to rank exact closeness. Phonetics references FuzzMatch by name only and takes no dependency on it, so you add whichever half you need.
Input handling and Unicode
The behavior is deliberate and consistent, and documented per algorithm:
- Null input throws
ArgumentNullException. Empty input returns each algorithm's empty or padding code: an empty string for Soundex, Metaphone, NYSIIS, MRA and Cologne;1111111111for Caverphone 2.0 and111111for Caverphone 1.0 (their fixed-length padding). For Double Metaphone, empty input returns the tuple("", "")rather than commons-codec'snull; this is a deliberate .NET-friendly choice (no null to guard) and the only place the Double Metaphone output shape differs from commons-codec. - Case is ignored; input is upper-cased with the invariant culture before encoding.
- Non-letters are handled per each algorithm's reference. Soundex, Refined Soundex and NYSIIS strip everything that is not a letter. Caverphone strips everything outside
atoz. Cologne skips characters outsideAtoZ. Metaphone and Double Metaphone keep interior spaces (Double Metaphone treats them as word boundaries) and ignore other stray characters. One consequence, inherited verbatim from commons-codec: a Double Metaphone word that ends in aJsound (for exampleKudej) yields an alternate code with a single trailing space (KT); the alternate is compared and returned exactly as commons-codec produces it, space included, so if you bucket on.Alternateand want to drop that space, trim it yourself. - Accents are folded to ASCII before encoding for every algorithm (for example
Müllerencodes likeMuller,StraßelikeStrasse,JosélikeJose, and the SlavicDvořáklikeDvorak), so accented spellings match their plain forms. Folding covers the whole Latin-1, Latin Extended-A and common Latin Extended-B ranges: letters with a combining diacritic are folded by canonical decomposition (dropping the mark), and stroke, slash, and ligature letters (the sharp s,æ,œ,ø,ł,đ,þ, and so on) by an explicit table. This is the one deliberate divergence from commons-codec, which throws on unmapped accented letters in the Soundex family. Every un-accented (ASCII) input matches commons-codec exactly, verified byte-for-byte against a real commons-codec 1.17.1 run over 7,946 names (see Correctness). German umlauts and the sharp s are handled correctly for Cologne (the sharp s becomesSS); note that for Cologne specifically, foldingæ,œandøto Latin letters means they are encoded rather than skipped as commons-codec would skip an unmapped character, which only affects those three non-German ligatures.
Correctness
Every algorithm is a faithful port of a named reference, and correctness is the product. The test suite runs the encoders against three independent oracles:
- A real Apache commons-codec 1.17.1 run (the strongest oracle): commons-codec was executed inside a container over a 7,946-name corpus (US census surnames and given names plus adversarial synthetic endings), and every code captured. All eleven encoders reproduce that output byte-for-byte, with zero mismatches. This is not circular: the oracle is the genuine Java library, generated independently of this port.
- Apache commons-codec 1.17.1 published test vectors: the exact
(input, code)pairs from commons-codec's own unit tests, extracted verbatim. This includes the full Double Metaphone context-rule coverage (silent GH, CC, SCH, initial vowels, WR, the -MB ending, X, Z, the W and H rules, and the Slavo-Germanic branches), both the primary and the alternate codes, the Caverphone author examples, and the Cologne examples such asMüller-Lüdenscheidt→65752682. - jellyfish 1.2.1 (an independent second implementation): confirms Soundex parity on clean-letter names and Match Rating codex parity on names without doubled consonants.
Where the two references implement the same algorithm differently, this library documents which behavior it chose:
- Soundex: matches commons-codec
US_ENGLISHand jellyfish on clean-letter names; the two references differ only on how punctuation and control characters are cleaned, where this library follows commons-codec (strip non-letters). The classicAshcraft→A261H-and-W-between-consonants rule is covered. - Metaphone: ports Philips' original via commons-codec. jellyfish's Metaphone is its own variant (no four-character cap, different
SC,GHand final-consonant handling) and deliberately differs. - NYSIIS: this is the original NYSIIS, truncated to six characters (commons-codec strict mode). jellyfish's NYSIIS is a different, arbitrary-length variant and differs.
- Match Rating Approach: matches commons-codec, which removes the second letter of a doubled consonant; jellyfish does not perform that step and diverges on names such as
Dodge(DGhere,DDGin jellyfish). - Double Metaphone alternate codes: taken from commons-codec, which is the canonical published implementation of Philips' improved algorithm.
Beyond the oracles, the suite pins the classic vectors every implementation must pass (Robert and Rupert → R163, Ashcraft → A261, Tymczak → T522, Pfister → P236), checks 16-way concurrent encoding against a single-threaded baseline, fuzzes 1000 hostile inputs (lone surrogates, emoji, right-to-left text, control characters, ten-thousand-character strings) asserting no encoder ever throws and every code keeps a valid shape, and verifies every README sample compiles and runs as pasted.
Performance
Phonetic encoders are linear scans of short strings, so they are fast. Measured with this repository's perf harness (release build, .NET 8, x64), on a modern desktop:
Soundex: about 3.2 million encodings per second.DoubleMetaphone: about 1.8 million encodings per second.
The asserted CI floors are far lower so slow runners stay green.
Roadmap
- Modified NYSIIS (this release ships the original).
- Beider-Morse Phonetic Matching, the big one for cross-language genealogy.
- More language-specific phonetics (Daitch-Mokotoff, and others).
- An
ISoundsLikescorer that returns a confidence rather than a boolean.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 126 | 8/6/2026 |