2016 Update

This commit is contained in:
Tina Müller 2016-12-05 22:15:40 +01:00
parent 948b86eafa
commit dcf5d15da3
7965 changed files with 139854 additions and 31002 deletions

View file

@ -1,2 +1,11 @@
Soundex is an algorithm for creating indices for words based on their pronunciation.
;Task:
The goal is for homophones to be encoded to the same representation so that they can be matched despite minor differences in spelling (from [[wp:soundex|the WP article]]).
<br><br>
;Caution:
There is a major issue in many of the implementations concerning the separation of two consonants that have the same soundex code! According to the official Rules [[https://www.archives.gov/research/census/soundex.html]]. So check for instance if '''Ashcraft''' is coded to '''A-261'''.
* If a vowel (A, E, I, O, U) separates two consonants that have the same soundex code, the consonant to the right of the vowel is coded. Tymczak is coded as T-522 (T, 5 for the M, 2 for the C, Z ignored (see "Side-by-Side" rule above), 2 for the K). Since the vowel "A" separates the Z and K, the K is coded.
* If "H" or "W" separate two consonants that have the same soundex code, the consonant to the right of the vowel is not coded. Example: Ashcraft is coded A-261 (A, 2 for the S, C ignored, 6 for the R, 1 for the F). It is not coded A-226.

View file

@ -0,0 +1,39 @@
defmodule Soundex do
def soundex([]), do: []
def soundex(str) do
[head|tail] = String.upcase(str) |> to_char_list
[head | isoundex(tail, [], todigit(head))]
end
defp isoundex([], acc, _) do
case length(acc) do
n when n == 3 -> Enum.reverse(acc)
n when n < 3 -> isoundex([], [?0 | acc], :ignore)
n when n > 3 -> isoundex([], Enum.slice(acc, n-3, n), :ignore)
end
end
defp isoundex([head|tail], acc, lastn) do
dig = todigit(head)
if dig != ?0 and dig != lastn do
isoundex(tail, [dig | acc], dig)
else
case head do
?H -> isoundex(tail, acc, lastn)
?W -> isoundex(tail, acc, lastn)
n when n in ?A..?Z -> isoundex(tail, acc, dig)
_ -> isoundex(tail, acc, lastn) # This clause handles non alpha characters
end
end
end
@digits '01230120022455012623010202'
defp todigit(chr) do
if chr in ?A..?Z, do: Enum.at(@digits, chr - ?A),
else: ?0 # Treat non alpha characters as a vowel
end
end
IO.puts Soundex.soundex("Soundex")
IO.puts Soundex.soundex("Example")
IO.puts Soundex.soundex("Sownteks")
IO.puts Soundex.soundex("Ekzampul")

View file

@ -0,0 +1,42 @@
function soundex(t) {
t = t.toUpperCase().replace(/[^A-Z]/g, '');
return (t[0] || '0') + t.replace(/[HW]/g, '')
.replace(/[BFPV]/g, '1')
.replace(/[CGJKQSXZ]/g, '2')
.replace(/[DT]/g, '3')
.replace(/[L]/g, '4')
.replace(/[MN]/g, '5')
.replace(/[R]/g, '6')
.replace(/(.)\1+/g, '$1')
.substr(1)
.replace(/[AEOIUHWY]/g, '')
.concat('000')
.substr(0, 3);
}
// tests
[ ["Example", "E251"], ["Sownteks", "S532"], ["Lloyd", "L300"], ["12346", "0000"],
["4-H", "H000"], ["Ashcraft", "A261"], ["Ashcroft", "A261"], ["auerbach", "A612"],
["bar", "B600"], ["barre", "B600"], ["Baragwanath", "B625"], ["Burroughs", "B620"],
["Burrows", "B620"], ["C.I.A.", "C000"], ["coöp", "C100"], ["D-day", "D000"],
["d jay", "D200"], ["de la Rosa", "D462"], ["Donnell", "D540"], ["Dracula", "D624"],
["Drakula", "D624"], ["Du Pont", "D153"], ["Ekzampul", "E251"], ["example", "E251"],
["Ellery", "E460"], ["Euler", "E460"], ["F.B.I.", "F000"], ["Gauss", "G200"],
["Ghosh", "G200"], ["Gutierrez", "G362"], ["he", "H000"], ["Heilbronn", "H416"],
["Hilbert", "H416"], ["Jackson", "J250"], ["Johnny", "J500"], ["Jonny", "J500"],
["Kant", "K530"], ["Knuth", "K530"], ["Ladd", "L300"], ["Lloyd", "L300"],
["Lee", "L000"], ["Lissajous", "L222"], ["Lukasiewicz", "L222"], ["naïve", "N100"],
["Miller", "M460"], ["Moses", "M220"], ["Moskowitz", "M232"], ["Moskovitz", "M213"],
["O'Conner", "O256"], ["O'Connor", "O256"], ["O'Hara", "O600"], ["O'Mally", "O540"],
["Peters", "P362"], ["Peterson", "P362"], ["Pfister", "P236"], ["R2-D2", "R300"],
["rÄ≈sumÅ∙", "R250"], ["Robert", "R163"], ["Rupert", "R163"], ["Rubin", "R150"],
["Soundex", "S532"], ["sownteks", "S532"], ["Swhgler", "S460"], ["'til", "T400"],
["Tymczak", "T522"], ["Uhrbach", "U612"], ["Van de Graaff", "V532"],
["VanDeusen", "V532"], ["Washington", "W252"], ["Wheaton", "W350"],
["Williams", "W452"], ["Woolcock", "W422"]
].forEach(function(v) {
var a = v[0], t = v[1], d = soundex(a);
if (d !== t) {
console.log('soundex("' + a + '") was ' + d + ' should be ' + t);
}
});

View file

@ -0,0 +1,129 @@
(() => {
'use strict';
// Simple Soundex or NARA Soundex (if blnNara = true)
// soundex :: Bool -> String -> String
const soundex = (blnNara, name) => {
// code :: Char -> Char
const code = c => ['AEIOU', 'BFPV', 'CGJKQSXZ', 'DT', 'L', 'MN', 'R', 'HW']
.reduce((a, x, i) =>
a ? a : (x.indexOf(c) !== -1 ? i.toString() : a), '');
// isAlpha :: Char -> Boolean
const isAlpha = c => {
const d = c.charCodeAt(0);
return d > 64 && d < 91;
};
const s = name.toUpperCase()
.split('')
.filter(isAlpha);
return (s[0] || '0') +
s.map(code)
.join('')
.replace(/7/g, blnNara ? '' : '7')
.replace(/(.)\1+/g, '$1')
.substr(1)
.replace(/[07]/g, '')
.concat('000')
.substr(0, 3);
};
// curry :: ((a, b) -> c) -> a -> b -> c
const curry = f => a => b => f(a, b),
[simpleSoundex, naraSoundex] = [false, true]
.map(bln => curry(soundex)(bln));
// TEST
return [
["Example", "E251"],
["Sownteks", "S532"],
["Lloyd", "L300"],
["12346", "0000"],
["4-H", "H000"],
["Ashcraft", "A261"],
["Ashcroft", "A261"],
["auerbach", "A612"],
["bar", "B600"],
["barre", "B600"],
["Baragwanath", "B625"],
["Burroughs", "B620"],
["Burrows", "B620"],
["C.I.A.", "C000"],
["coöp", "C100"],
["D-day", "D000"],
["d jay", "D200"],
["de la Rosa", "D462"],
["Donnell", "D540"],
["Dracula", "D624"],
["Drakula", "D624"],
["Du Pont", "D153"],
["Ekzampul", "E251"],
["example", "E251"],
["Ellery", "E460"],
["Euler", "E460"],
["F.B.I.", "F000"],
["Gauss", "G200"],
["Ghosh", "G200"],
["Gutierrez", "G362"],
["he", "H000"],
["Heilbronn", "H416"],
["Hilbert", "H416"],
["Jackson", "J250"],
["Johnny", "J500"],
["Jonny", "J500"],
["Kant", "K530"],
["Knuth", "K530"],
["Ladd", "L300"],
["Lloyd", "L300"],
["Lee", "L000"],
["Lissajous", "L222"],
["Lukasiewicz", "L222"],
["naïve", "N100"],
["Miller", "M460"],
["Moses", "M220"],
["Moskowitz", "M232"],
["Moskovitz", "M213"],
["O'Conner", "O256"],
["O'Connor", "O256"],
["O'Hara", "O600"],
["O'Mally", "O540"],
["Peters", "P362"],
["Peterson", "P362"],
["Pfister", "P236"],
["R2-D2", "R300"],
["rÄ≈sumÅ∙", "R250"],
["Robert", "R163"],
["Rupert", "R163"],
["Rubin", "R150"],
["Soundex", "S532"],
["sownteks", "S532"],
["Swhgler", "S460"],
["'til", "T400"],
["Tymczak", "T522"],
["Uhrbach", "U612"],
["Van de Graaff", "V532"],
["VanDeusen", "V532"],
["Washington", "W252"],
["Wheaton", "W350"],
["Williams", "W452"],
["Woolcock", "W422"]
].reduce((a, [name, naraCode]) => {
const naraTest = naraSoundex(name),
simpleTest = simpleSoundex(name);
const logNara = naraTest !== naraCode ? (
`${name} was ${naraTest} should be ${naraCode}`
) : '',
logDelta = (naraTest !== simpleTest ? (
`${name} -> NARA: ${naraTest} vs Simple: ${simpleTest}`
) : '');
return logNara.length || logDelta.length ? (
a + [logNara, logDelta].join('\n')
) : a;
}, '');
})();

View file

@ -0,0 +1,75 @@
%____________________________________________________________________
% Implements the American soundex algorithm
% as described at https://en.wikipedia.org/wiki/Soundex
% In SWI Prolog, a 'string' is specified in 'single quotes',
% while a "list of codes" may be specified in "double quotes".
% So, "abc" is equivalent to [97, 98, 99], while
% 'abc' = abc (an atom), and 'Abc' is also an atom. There are
% conversion methods that can produce lists of characters:
% ?- atom_chars('Abc', X).
% X = ['A', b, c].
% or lists of codes (mapping to unicode code points):
% ?- atom_codes('Abc', X).
% X = [65, 98, 99].
% and the conversion predicates are bidirectional.
% ?- atom_codes(A, [65, 98, 99]).
% A = 'Abc'.
% A single character code may be specified as 0'C, where C is the
% character you want to convert to a code.
%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
% Relates groups of consonants to representative digits
creplace(Ch, 0'1) :- member(Ch, "bfpv").
creplace(Ch, 0'2) :- member(Ch, "cgjkqsxz").
creplace(Ch, 0'3) :- member(Ch, "dt").
creplace(0'l, 0'4).
creplace(Ch, 0'5) :- member(Ch, "mn").
creplace(0'r, 0'6).
% strips elements contained in <Set> from a string
strip(Set, [H|T], Tr) :- memberchk(H, Set), !, strip(Set, T, Tr).
strip(Set, [H|T], [H|Tr]) :- !, strip(Set, T, Tr).
strip(_, [], []).
% Replace consonants with appropriate digits
consonants([H|T], [Ch|Tr]) :- creplace(H, Ch), !, consonants(T, Tr).
consonants([H|T], [H|Tr]) :- !, consonants(T, Tr).
consonants([], []).
% Replace adjacent digits with single digit
adjacent([Ch, Ch|T], [Ch|Tr]) :- between(0'0, 0'9, Ch), !, adjacent(T, Tr).
adjacent([H|T], [H|Tr]) :- !, adjacent(T, Tr).
adjacent([], []).
% Replace first character with original one if its a digit
chk_digit([H,D|T], [H|T]) :- between(0'0, 0'9, D), !.
chk_digit([_,H|T], [H|T]).
% Faithul representation of soundex rules:
% 1: Save 1st letter, strip "hw"
% 2: Replace consonants with appropriate digits
% 3: Replace adjacent digits with single occurrence
% 4: Remove vowels except 1st letter
% 5: If 1st symbol is a digit, replace it with saved 1st letter
% 6: Ensure trailing zeroes
do_soundex([H|T], Res) :-
strip("hw", T, Ts), consonants([H|Ts], Tc),
adjacent(Tc, [C|Ta]), strip("aeiouy", Ta, Tv),
chk_digit([H,C|Tv], Td), append(Td, "0000", Tr),
atom_codes(Tf, Tr), sub_string(Tf, 0, 4, _, Res).
% Prepare string, convert to lower case and do the soundex alogorithm
soundex(Text, Res) :-
downcase_atom(Text, Lower), atom_codes(Lower, T),
do_soundex(T, Res).
% Perform tests to check that the right values are produced
test(S,V) :- not(soundex(S,V)), writef('%w failed\n', [S]).
test :- test('Robert', 'r163'), !, fail.
test :- test('Rupert', 'r163'), !, fail.
test :- test('Rubin', 'r150'), !, fail.
test :- test('Ashcroft', 'a261'), !, fail.
test :- test('Ashcraft', 'a261'), !, fail.
test :- test('Tymczak', 't522'), !, fail.
test :- test('Pfister', 'p236'), !, fail.
test. % Succeeds only if all the tests succeed

View file

@ -1,100 +1,99 @@
/*REXX program demonstrates Soundex codes from some words | commandLine.*/
_=; @.= /*set a couple of vars to "null".*/
parse arg @.0 . /*allow input from command line. */
@.1 = "12346" ; #.1 = '0000'
@.4 = "4-H" ; #.4 = 'H000'
@.11 = "Ashcraft" ; #.11 = 'A261'
@.12 = "Ashcroft" ; #.12 = 'A261'
@.18 = "auerbach" ; #.18 = 'A612'
@.20 = "Baragwanath" ; #.20 = 'B625'
@.22 = "bar" ; #.22 = 'B600'
@.23 = "barre" ; #.23 = 'B600'
@.20 = "Baragwanath" ; #.20 = 'B625'
@.28 = "Burroughs" ; #.28 = 'B620'
@.29 = "Burrows" ; #.29 = 'B620'
@.30 = "C.I.A." ; #.30 = 'C000'
@.37 = "coöp" ; #.37 = 'C100'
@.43 = "D-day" ; #.43 = 'D000'
@.44 = "d jay" ; #.44 = 'D200'
@.45 = "de la Rosa" ; #.45 = 'D462'
@.46 = "Donnell" ; #.46 = 'D540'
@.47 = "Dracula" ; #.47 = 'D624'
@.48 = "Drakula" ; #.48 = 'D624'
@.49 = "Du Pont" ; #.49 = 'D153'
@.50 = "Ekzampul" ; #.50 = 'E251'
@.51 = "example" ; #.51 = 'E251'
@.55 = "Ellery" ; #.55 = 'E460'
@.59 = "Euler" ; #.59 = 'E460'
@.60 = "F.B.I." ; #.60 = 'F000'
@.70 = "Gauss" ; #.70 = 'G200'
@.71 = "Ghosh" ; #.71 = 'G200'
@.72 = "Gutierrez" ; #.72 = 'G362'
@.80 = "he" ; #.80 = 'H000'
@.81 = "Heilbronn" ; #.81 = 'H416'
@.84 = "Hilbert" ; #.84 = 'H416'
@.100 = "Jackson" ; #.100 = 'J250'
@.104 = "Johnny" ; #.104 = 'J500'
@.105 = "Jonny" ; #.105 = 'J500'
@.110 = "Kant" ; #.110 = 'K530'
@.116 = "Knuth" ; #.116 = 'K530'
@.120 = "Ladd" ; #.120 = 'L300'
@.124 = "Llyod" ; #.124 = 'L300'
@.125 = "Lee" ; #.125 = 'L000'
@.126 = "Lissajous" ; #.126 = 'L222'
@.128 = "Lukasiewicz" ; #.128 = 'L222'
@.130 = "naïve" ; #.130 = 'N100'
@.141 = "Miller" ; #.141 = 'M460'
@.143 = "Moses" ; #.143 = 'M220'
@.146 = "Moskowitz" ; #.146 = 'M232'
@.147 = "Moskovitz" ; #.147 = 'M213'
@.150 = "O'Conner" ; #.150 = 'O256'
@.151 = "O'Connor" ; #.151 = 'O256'
@.152 = "O'Hara" ; #.152 = 'O600'
@.153 = "O'Mally" ; #.153 = 'O540'
@.161 = "Peters" ; #.161 = 'P362'
@.162 = "Peterson" ; #.162 = 'P362'
@.165 = "Pfister" ; #.165 = 'P236'
@.180 = "R2-D2" ; #.180 = 'R300'
@.182 = "rÄ≈sumÅ∙" ; #.182 = 'R250'
@.184 = "Robert" ; #.184 = 'R163'
@.185 = "Rupert" ; #.185 = 'R163'
@.187 = "Rubin" ; #.187 = 'R150'
@.191 = "Soundex" ; #.191 = 'S532'
@.192 = "sownteks" ; #.192 = 'S532'
@.199 = "Swhgler" ; #.199 = 'S460'
@.202 = "'til" ; #.202 = 'T400'
@.208 = "Tymczak" ; #.208 = 'T522'
@.216 = "Uhrbach" ; #.216 = 'U612'
@.221 = "Van de Graaff" ; #.221 = 'V532'
@.222 = "VanDeusen" ; #.222 = 'V532'
@.230 = "Washington" ; #.230 = 'W252'
@.233 = "Wheaton" ; #.233 = 'W350'
@.234 = "Williams" ; #.234 = 'W452'
@.236 = "Woolcock" ; #.236 = 'W422'
/*REXX program demonstrates Soundex codes from some words or from the command line.*/
_=; @.= /*set a couple of vars to "null".*/
parse arg @.0 . /*allow input from command line. */
@.1 = "12346" ; #.1 = '0000'
@.4 = "4-H" ; #.4 = 'H000'
@.11 = "Ashcraft" ; #.11 = 'A261'
@.12 = "Ashcroft" ; #.12 = 'A261'
@.18 = "auerbach" ; #.18 = 'A612'
@.20 = "Baragwanath" ; #.20 = 'B625'
@.22 = "bar" ; #.22 = 'B600'
@.23 = "barre" ; #.23 = 'B600'
@.20 = "Baragwanath" ; #.20 = 'B625'
@.28 = "Burroughs" ; #.28 = 'B620'
@.29 = "Burrows" ; #.29 = 'B620'
@.30 = "C.I.A." ; #.30 = 'C000'
@.37 = "coöp" ; #.37 = 'C100'
@.43 = "D-day" ; #.43 = 'D000'
@.44 = "d jay" ; #.44 = 'D200'
@.45 = "de la Rosa" ; #.45 = 'D462'
@.46 = "Donnell" ; #.46 = 'D540'
@.47 = "Dracula" ; #.47 = 'D624'
@.48 = "Drakula" ; #.48 = 'D624'
@.49 = "Du Pont" ; #.49 = 'D153'
@.50 = "Ekzampul" ; #.50 = 'E251'
@.51 = "example" ; #.51 = 'E251'
@.55 = "Ellery" ; #.55 = 'E460'
@.59 = "Euler" ; #.59 = 'E460'
@.60 = "F.B.I." ; #.60 = 'F000'
@.70 = "Gauss" ; #.70 = 'G200'
@.71 = "Ghosh" ; #.71 = 'G200'
@.72 = "Gutierrez" ; #.72 = 'G362'
@.80 = "he" ; #.80 = 'H000'
@.81 = "Heilbronn" ; #.81 = 'H416'
@.84 = "Hilbert" ; #.84 = 'H416'
@.100 = "Jackson" ; #.100 = 'J250'
@.104 = "Johnny" ; #.104 = 'J500'
@.105 = "Jonny" ; #.105 = 'J500'
@.110 = "Kant" ; #.110 = 'K530'
@.116 = "Knuth" ; #.116 = 'K530'
@.120 = "Ladd" ; #.120 = 'L300'
@.124 = "Llyod" ; #.124 = 'L300'
@.125 = "Lee" ; #.125 = 'L000'
@.126 = "Lissajous" ; #.126 = 'L222'
@.128 = "Lukasiewicz" ; #.128 = 'L222'
@.130 = "naïve" ; #.130 = 'N100'
@.141 = "Miller" ; #.141 = 'M460'
@.143 = "Moses" ; #.143 = 'M220'
@.146 = "Moskowitz" ; #.146 = 'M232'
@.147 = "Moskovitz" ; #.147 = 'M213'
@.150 = "O'Conner" ; #.150 = 'O256'
@.151 = "O'Connor" ; #.151 = 'O256'
@.152 = "O'Hara" ; #.152 = 'O600'
@.153 = "O'Mally" ; #.153 = 'O540'
@.161 = "Peters" ; #.161 = 'P362'
@.162 = "Peterson" ; #.162 = 'P362'
@.165 = "Pfister" ; #.165 = 'P236'
@.180 = "R2-D2" ; #.180 = 'R300'
@.182 = "rÄ≈sumÅ∙" ; #.182 = 'R250'
@.184 = "Robert" ; #.184 = 'R163'
@.185 = "Rupert" ; #.185 = 'R163'
@.187 = "Rubin" ; #.187 = 'R150'
@.191 = "Soundex" ; #.191 = 'S532'
@.192 = "sownteks" ; #.192 = 'S532'
@.199 = "Swhgler" ; #.199 = 'S460'
@.202 = "'til" ; #.202 = 'T400'
@.208 = "Tymczak" ; #.208 = 'T522'
@.216 = "Uhrbach" ; #.216 = 'U612'
@.221 = "Van de Graaff" ; #.221 = 'V532'
@.222 = "VanDeusen" ; #.222 = 'V532'
@.230 = "Washington" ; #.230 = 'W252'
@.233 = "Wheaton" ; #.233 = 'W350'
@.234 = "Williams" ; #.234 = 'W452'
@.236 = "Woolcock" ; #.236 = 'W422'
do k=0 for 300; if @.k=='' then iterate; $=soundex(@.k)
say word('nope [ok]',1+($==#.k | k==0)) _ $ 'is the Soundex for' @.k
do k=0 for 300; if @.k=='' then iterate; $=soundex(@.k)
say word('nope [ok]', 1 +($==#.k | k==0)) _ $ "is the Soundex for" @.k
if k==0 then leave
end /*k*/
exit /*stick a fork in it, we're done.*/
/*───────────────────────────────────SOUNDEX subroutine─────────────────*/
soundex: procedure; arg thing /*ARG automatically uppercases it*/
old_alphabet = 'AEIOUYHWBFPVCGJKQSXZDTLMNR'
new_alphabet = '@@@@@@**111122222222334556'
word=
do i=1 for length(thing) /*handle special chars: - ' _ etc*/
_=substr(thing, i, 1)
if datatype(_,'M') then word=word || _ /*it's a letter, then OK*/
end /*i*/
exit /*stick a fork in it, we're done.*/
/*──────────────────────────────────────────────────────────────────────────────────────*/
soundex: procedure; arg x /*ARG uppercases the var X. */
old_alphabet= 'AEIOUYHWBFPVCGJKQSXZDTLMNR'
new_alphabet= '@@@@@@**111122222222334556'
word= /* [+] exclude non-letters. */
do i=1 for length(x); _=substr(x, i, 1) /*obtain a character from word*/
if datatype(_,'M') then word=word || _ /*Upper/lower letter? Then OK*/
end /*i*/
value=strip(left(word, 1)) /*first character is left alone. */
word=translate(word, new_alphabet, old_alphabet)
prev=translate(value,new_alphabet, old_alphabet) /*the previous code.*/
value=strip(left(word, 1)) /*1st character is left alone.*/
word=translate(word, new_alphabet, old_alphabet) /*define the current word. */
prev=translate(value,new_alphabet, old_alphabet) /* " " previous " */
do j=2 to length(word) /*process remainder of the word. */
?=substr(word, j, 1)
if ?\==prev & datatype(?,'W') then do; value=value || ?; prev=?; end
else if ?=='@' then prev=?
end /*j*/
do j=2 to length(word) /*process remainder of word. */
?=substr(word, j, 1)
if ?\==prev & datatype(?,'W') then do; value=value || ?; prev=?; end
else if ?=='@' then prev=?
end /*j*/
return left(value,4,0) /*return padded value with zeroes*/
return left(value,4,0) /*padded value with zeroes. */

View file

@ -11,13 +11,13 @@
(if (zerop (length s))
""
(let* ((su (upcase-str s))
(o (chr-str su 0)))
(o [su 0]))
(for ((i 1) (l (length su)) cp cg)
((< i l) (sub-str (cat-str ^(,o "000") nil) 0 4))
((< i l) [`@{o}000` 0 4])
((inc i) (set cp cg))
(set cg (get-code (chr-str su i)))
(if (and cg (null (eql cg cp)))
(set o (cat-str ^(,o ,cg) nil))))))))
(set cg (get-code [su i]))
(if (and cg (not (eql cg cp)))
(set o `@o@cg`)))))))
@(next :args)
@(repeat)
@arg

View file

@ -0,0 +1,8 @@
declare -A value=(
[B]=1 [F]=1 [P]=1 [V]=1
[C]=2 [G]=2 [J]=2 [K]=2 [Q]=2 [S]=2 [X]=2 [Z]=2
[D]=3 [T]=3
[L]=4
[M]=5 [N]=5
[R]=6
)

View file

@ -0,0 +1,36 @@
soundex() {
local -u word=${1//[^[:alpha:]]/.}
local letter=${word:0:1}
local soundex=$letter
local previous=$letter
word=${word:1}
word=${word//[AEIOUY]/.}
word=${word//[WH]/=}
while [[ ${#soundex} -lt 4 && -n $word ]]; do
letter=${word:0:1}
if [[ $letter == "." ]]; then
previous=""
elif [[ $letter == "=" ]]; then
if [[ $previous == [A-Z] && ${word:1:1} == [A-Z] ]] &&
[[ ${value[$previous]} -eq ${value[${word:1:1}]} ]]
then
word=${word:1}
fi
elif [[ -z $previous ]] ||
[[ $letter != $previous && ${value[$letter]} -ne ${value[$previous]} ]]
then
previous=$letter
soundex+=${value[$letter]}
fi
word=${word:1}
done
# right pad with zeros
soundex+="000"
echo "${soundex:0:4}"
}

View file

@ -0,0 +1,44 @@
soundex2() {
local -u word=${1//[^[:alpha:]]/}
# 1. Save the first letter. Remove all occurrences of 'h' and 'w' except first letter.
local first=${word:0:1}
word=${word:1}
word=$first${word//[HW]/}
# 2. Replace all consonants (include the first letter) with digits as in [2.] above.
local consonants=$(IFS=; echo "${!value[*]}")
local tmp letter
local -i i
for ((i=0; i < ${#word}; i++)); do
letter=${word:i:1}
if [[ $consonants == *$letter* ]]; then
tmp+=${value[$letter]}
else
tmp+=$letter
fi
done
word=$tmp
# 3. Replace all adjacent same digits with one digit.
local char
tmp=${word:0:1}
local previous=${word:0:1}
for ((i=1; i < ${#word}; i++)); do
char=${word:i:1}
[[ $char != [[:digit:]] || $char != $previous ]] && tmp+=$char
previous=$char
done
word=$tmp
# 4. Remove all occurrences of a, e, i, o, u, y except first letter.
tmp=${word:1}
word=${word:0:1}${tmp//[AEIOUY]/}
# 5. If first symbol is a digit replace it with letter saved on step 1.
[[ $word == [[:digit:]]* ]] && word=$first${word:1}
# 6. right pad with zeros
word+="000"
echo "${word:0:4}"
}

View file

@ -0,0 +1,21 @@
soundex3() {
local -u word=${1//[^[:alpha:]]/}
# 1. Save the first letter. Remove all occurrences of 'h' and 'w' except first letter.
local first=${word:0:1}
word=$first$( tr -d "HW" <<< "${word:1}" )
# 2. Replace all consonants (include the first letter) with digits as in [2.] above.
# 3. Replace all adjacent same digits with one digit.
local consonants=$( IFS=; echo "${!value[*]}" )
local values=$( IFS=; echo "${value[*]}" )
word=$( tr -s "$consonants" "$values" <<< "$word" )
# 4. Remove all occurrences of a, e, i, o, u, y except first letter.
# 5. If first symbol is a digit replace it with letter saved on step 1.
word=$first$( tr -d "AEIOUY" <<< "${word:1}" )
# 6. right pad with zeros
word+="000"
echo "${word:0:4}"
}

View file

@ -0,0 +1,28 @@
declare -A tests=(
[Soundex]=S532 [Example]=E251 [Sownteks]=S532 [Ekzampul]=E251
[Euler]=E460 [Gauss]=G200 [Hilbert]=H416 [Knuth]=K530
[Lloyd]=L300 [Lukasiewicz]=L222 [Ellery]=E460 [Ghosh]=G200
[Heilbronn]=H416 [Kant]=K530 [Ladd]=L300 [Lissajous]=L222
[Wheaton]=W350 [Burroughs]=B620 [Burrows]=B620 ["O'Hara"]=O600
[Washington]=W252 [Lee]=L000 [Gutierrez]=G362 [Pfister]=P236
[Jackson]=J250 [Tymczak]=T522 [VanDeusen]=V532 [Ashcraft]=A261
)
run_tests() {
local func=$1
echo "Testing with function $func"
local -i all=0 fail=0
for name in "${!tests[@]}"; do
s=$($func "$name")
if [[ $s != "${tests[$name]}" ]]; then
echo "FAIL - $s - $name -- EXPECTING ${tests[$name]}"
((fail++))
fi
((all++))
done
echo "$fail out of $all failures"
}
run_tests soundex
run_tests soundex2
run_tests soundex3