Current section
Files
Jump to
Current section
Files
lib/levenshtein.ex
defmodule Calque.Levenshtein do
@moduledoc """
Levenshtein (edit) distance between two strings (insert, delete, substitute).
Unicode-safe by operating on graphemes.
"""
@doc false
@spec distance(String.t(), String.t()) :: non_neg_integer()
def distance(a, "") do
String.length(a)
end
def distance("", b) do
String.length(b)
end
def distance(a, b) when a == b do
0
end
def distance(a, b) do
do_distance(String.graphemes(a), String.graphemes(b))
end
@doc false
defp do_distance(a, b) do
prev_row = Enum.to_list(0..length(b))
final_row =
Enum.reduce(a, prev_row, fn sc, prev_row ->
build_row(sc, b, prev_row)
end)
List.last(final_row)
end
@doc false
defp build_row(sc, t, prev_row) do
first_cell = hd(prev_row) + 1
up_left0 = hd(prev_row)
{row_rev, _last_up} =
Enum.reduce(Enum.with_index(t, 1), {[first_cell], up_left0}, fn {tc, j}, {row_rev, up_left} ->
left = hd(row_rev)
up = Enum.at(prev_row, j)
cost = if sc == tc, do: 0, else: 1
cell = min(min(left + 1, up + 1), up_left + cost)
{[cell | row_rev], up}
end)
Enum.reverse(row_rev)
end
end