Packages

Fuzzy string matching, distance, similarity, ranking, and entity resolution in Gleam

Current section

Files

Jump to
starfuzz src starfuzz@phonetic.erl
Raw

src/starfuzz@phonetic.erl

-module(starfuzz@phonetic).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/starfuzz/phonetic.gleam").
-export([soundex/1, same_soundex/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" A module providing phonetic matching capabilities.\n"
" Phonetic matching indexes words by how they sound (homophones),\n"
" allowing comparison regardless of spelling variations (e.g., \"Robert\" and \"Rupert\").\n"
).
-file("src/starfuzz/phonetic.gleam", 8).
-spec replacement(binary()) -> binary().
replacement(Char) ->
case Char of
<<"B"/utf8>> ->
<<"1"/utf8>>;
<<"F"/utf8>> ->
<<"1"/utf8>>;
<<"P"/utf8>> ->
<<"1"/utf8>>;
<<"V"/utf8>> ->
<<"1"/utf8>>;
<<"C"/utf8>> ->
<<"2"/utf8>>;
<<"G"/utf8>> ->
<<"2"/utf8>>;
<<"J"/utf8>> ->
<<"2"/utf8>>;
<<"K"/utf8>> ->
<<"2"/utf8>>;
<<"Q"/utf8>> ->
<<"2"/utf8>>;
<<"S"/utf8>> ->
<<"2"/utf8>>;
<<"X"/utf8>> ->
<<"2"/utf8>>;
<<"Z"/utf8>> ->
<<"2"/utf8>>;
<<"D"/utf8>> ->
<<"3"/utf8>>;
<<"T"/utf8>> ->
<<"3"/utf8>>;
<<"L"/utf8>> ->
<<"4"/utf8>>;
<<"M"/utf8>> ->
<<"5"/utf8>>;
<<"N"/utf8>> ->
<<"5"/utf8>>;
<<"R"/utf8>> ->
<<"6"/utf8>>;
_ ->
<<"*"/utf8>>
end.
-file("src/starfuzz/phonetic.gleam", 68).
-spec pad_zero(binary()) -> binary().
pad_zero(S) ->
case string:length(S) of
Len when Len < 4 ->
pad_zero(<<S/binary, "0"/utf8>>);
_ ->
gleam@string:slice(S, 0, 4)
end.
-file("src/starfuzz/phonetic.gleam", 26).
?DOC(
" Encodes the input string into its Soundex representation (typically a letter followed by 3 numbers).\n"
"\n"
" Under the Soundex rules:\n"
" - Letter casing is ignored.\n"
" - Adjacent duplicated sounds are collapsed.\n"
" - 'H' and 'W' act as silently omitted vowels unless they split different consonant sounds.\n"
).
-spec soundex(binary()) -> binary().
soundex(Input) ->
case gleam@string:is_empty(Input) of
true ->
<<""/utf8>>;
false ->
Upper = string:uppercase(Input),
Chars = gleam@string:to_graphemes(Upper),
First@1 = case gleam@list:first(Chars) of
{ok, First} -> First;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"starfuzz/phonetic"/utf8>>,
function => <<"soundex"/utf8>>,
line => 33,
value => _assert_fail,
start => 1009,
'end' => 1049,
pattern_start => 1020,
pattern_end => 1029})
end,
Tail@1 = case gleam@list:rest(Chars) of
{ok, Tail} -> Tail;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"starfuzz/phonetic"/utf8>>,
function => <<"soundex"/utf8>>,
line => 34,
value => _assert_fail@1,
start => 1056,
'end' => 1094,
pattern_start => 1067,
pattern_end => 1075})
end,
Last_initial = replacement(First@1),
{Res_chars, _, _} = gleam@list:fold(
Tail@1,
{[First@1], Last_initial, 1},
fun(State, Char) ->
{Acc, Last, Len} = State,
case Len >= 4 of
true ->
State;
false ->
Sub = replacement(Char),
case Sub /= <<"*"/utf8>> of
true ->
case Sub /= Last of
true ->
{[Sub | Acc], Sub, Len + 1};
false ->
{Acc, Sub, Len}
end;
false ->
case (Char /= <<"H"/utf8>>) andalso (Char /= <<"W"/utf8>>) of
true ->
{Acc, <<"*"/utf8>>, Len};
false ->
{Acc, Last, Len}
end
end
end
end
),
Result_string = gleam@string:join(
lists:reverse(Res_chars),
<<""/utf8>>
),
pad_zero(Result_string)
end.
-file("src/starfuzz/phonetic.gleam", 76).
?DOC(" Compares if two strings result in the exact same Soundex code.\n").
-spec same_soundex(binary(), binary()) -> boolean().
same_soundex(A, B) ->
soundex(A) =:= soundex(B).