Soundex
February 16, 2010
Soundex is an algorithm originally developed by Margaret Odell and Robert Russell in the early 1900s to convert people’s last names into a standard encoding in a way that brings together similar names, thus correcting misspellings. Knuth (AoCP3, introduction to Chapter 6) describes the algorithm as follows:
- Retain the first letter of the name, and drop all occurrences of a, e, h, i, o, u, w, y in other positions.
- Assign the following numbers to the remaining letters after the first:
- b, f, p, v → 1
- c, g, j, k, q, s, x, z → 2
- d, t → 3
- l → 4
- m, n → 5
- r → 6
- If two or more letters with the same code were adjacent in the original name (before step 1), omit all but the first.
- Convert to the form “letter, digit, digit, digit” by adding trailing zeros (if there are less than three digits), or by dropping rightmost digits (if there are more than three).
The soundex method isn’t perfect. As Knuth points out, related names like Rogers and Rodgers, or Sinclair and St. Clair, or Tchebysheff and Chebyshev, are not matched. On the other hand, soundex works well enough to be generally useful, and if your first attempt at a match fails, it isn’t too hard to try again with an alternate spelling.
Your task is to write a function that takes a name and returns its associated soundex code. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.
My Haskell solution (see http://bonsaicode.wordpress.com/2010/02/16/programming-praxis-soundex/ for a version with comments):
import Data.Char
import Data.List
soundex :: String -> String
soundex = f . map head . group . map toUpper where
f [] = []
f (x:xs) = x : take 3 [toNum c | c <- xs ++ repeat ‘0’,
notElem c "AEHIOUWY"]
toNum c = maybe ‘0’ snd . find (elem c . fst) $
zip (words "BFPV CGJKQSXZ DT L MN R") [‘1’..]
Hm. I think I may have accidentally used src instead of css for the language in the post above. Here’s the correct version.
It’s doesn’t return the same results for ‘lloyd’ and ‘Lukasiewicz’ but it’s close enough
[…] Praxis – Soundex By Remco Niemeijer In today’s Programming Praxis exercise we have to implement the soundex algorithm for encoding people’s […]
That Haskell up there makes me want to coo :)
http://pastebin.com/bgDF5adL
C#, I’m sure it could be faster but it works nicely.
using System;
using System.Collections.Generic;
namespace Soundex
{
class Program
{
static void Main()
{
string str = “Soundex”;
Console.WriteLine(str.Soundex());
Console.ReadKey();
}
}
static class Extensions
{
public static string Soundex(this string str)
{
char f = ”;
char last = ”;
List num;
var chr = new List(
str.ToLower().ToCharArray()
).FindAll(c =>
c >= ‘a’ && c <= 'z' && c != 'a' && c != 'e' && c != 'h' &&
c != 'i' && c != 'o' && c != 'u' && c != 'w' && c != 'y'
);
f = chr[0];
chr.RemoveAt(0);
num = new List(chr.Count);
chr.ForEach(
delegate(char c)
{
if (num.Count < 3 && c != last)
{
switch (c)
{
case 'b': case 'f':
case 'p': case 'v':
num.Add('1');
break;
case 'c': case 'g':
case 'j': case 'k':
case 'q': case 's':
case 'x': case 'z':
num.Add('2');
break;
case 'd': case 't':
num.Add('3');
break;
case 'l':
num.Add('4');
break;
case 'm': case 'n':
num.Add('5');
break;
default:
num.Add('6');
break;
}
}
last = c;
}
);
return ((f < 95) ? f : (char)(f – 32)) + new string(num.ToArray()).PadRight(3, '0');
}
}
}
Python version.
_code is a mapping of letter to soundex code or “” for letters in the first group.
The algorithm doesn’t say how to handle punctuation (spaces, hyphens, apostrophes), so I strip them.
groupby processes its first argument to find groups of items having the same key, as determined by the second argument. _code.get does a dictionary lookup, returning the soundex code. So, groupby returns (k,v)-pairs, where k is a soundex code, and v is a group of adjacent letters having that code. The join concatentates the soundex codes, skipping the first.
My implementation in C