Phonetics 0.1.0

dotnet add package Phonetics --version 0.1.0
                    
NuGet\Install-Package Phonetics -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Phonetics" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Phonetics" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Phonetics" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Phonetics --version 0.1.0
                    
#r "nuget: Phonetics, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Phonetics@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Phonetics&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Phonetics&version=0.1.0
                    
Install as a Cake Tool

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, or alternateA == 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:

  1. Phonetics buckets by sound. Group records by a phonetic key so that only plausibly-similar names are ever compared.
  2. FuzzMatch scores within the bucket. A fuzzy distance (Levenshtein, Jaro-Winkler, token ratios) ranks the close spellings inside each bucket.
  3. 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; 1111111111 for Caverphone 2.0 and 111111 for Caverphone 1.0 (their fixed-length padding). For Double Metaphone, empty input returns the tuple ("", "") rather than commons-codec's null; 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 a to z. Cologne skips characters outside A to Z. 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 a J sound (for example Kudej) 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 .Alternate and want to drop that space, trim it yourself.
  • Accents are folded to ASCII before encoding for every algorithm (for example Müller encodes like Muller, Straße like Strasse, José like Jose, and the Slavic Dvořák like Dvorak), 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 becomes SS); 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 as Müller-Lüdenscheidt65752682.
  • 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_ENGLISH and 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 classic AshcraftA261 H-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, GH and 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 (DG here, DDG in 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 RupertR163, AshcraftA261, TymczakT522, PfisterP236), 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 ISoundsLike scorer that returns a confidence rather than a boolean.

License

MIT

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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