C#
レーベンシュタイン距離
2つの文字列が似ているかどうか調べるためにレーベンシュタイン距離を測ろうと思ったが、Wikipediaに掲載されているコードは、ちょっと使いづらい。多分わかりやすさを優先したのだろう。素直にC#に移植すれば、こんな感じになると思う。
public static int LevenshteinDistance(string str1, string str2)
{
int[,] d = new int[str1.Length + 1, str2.Length + 1];
for (int i1 = 0; i1 <= str1.Length; i1++)
{
d[i1, 0] = i1;
}
for (int i2 = 0; i2 <= str2.Length; i2++)
{
d[0, i2] = i2;
}
for (int i1 = 1; i1 <= str1.Length; i1++)
{
for (int i2 = 1; i2 <= str2.Length; i2++)
{
int cost = (str1[i1 - 1] == str2[i2 - 1]) ? 0 : 1;
d[i1, i2] = Math.Min(Math.Min(
d[i1 - 1, i2 ] + 1,
d[i1 , i2 - 1] + 1),
d[i1 - 1, i2 - 1] + cost);
}
}
return d[str1.Length, str2.Length];
}
このコードにはいくつか問題がある。まず(1)の箇所。比較する文字列が比較的短いうちは問題にならないが、例えば1000字の文字列同士を比較しようとすると、およそ1メガ要素の配列が必要で、メモリの確保が心配になる。
それから(2)のループの内容は(3)のループに組み込めるので、(2)のループを回す分だけ無駄になっている。中身のない空ループを書いているのと変わらない。
いろいろ整理すると、こんな感じになるかな。
public static int LevenshteinDistance(string str1, string str2)
{
int n1 = 0;
int n2 = str2.Length + 2;
int[] d = new int[n2 << 1];
for (int i = 0; i < n2; i++)
{
d[i] = i;
}
d[n2 - 1]++;
d[d.Length - 1] = 0;
for (int i = 0; i < str1.Length; i++)
{
d[n2] = i + 1;
for (int j = 0; j < str2.Length; j++)
{
int v = d[n1++];
if (str1[i] == str2[j])
{
v--;
}
v = (v < d[n1]) ? v : d[n1];
v = (v < d[n2]) ? v : d[n2];
d[++n2] = ++v;
}
n1 = d[n1 + 1];
n2 = d[n2 + 1];
}
return d[d.Length - n2 - 2];
}
これで少し速くなり、メモリ的にも有利になると思うが、わかりにくいので、オリジナルのコードをコメントにして残しておくと良いかもしれない。
2つの文字列が似ているかどうかを調べるには、もうひと工夫必要。長いほうの文字列の文字数で割って比率を出せば良いらしい。
public static float LevenshteinRate(string str1, string str2)
{
int len1 = (str1 != null) ? str1.Length : 0;
int len2 = (str2 != null) ? str2.Length : 0;
if (len1 > len2)
{
int tmp = len1;
len1 = len2;
len2 = tmp;
}
if (len1 == 0)
{
return (len2 == 0) ? 0.0f : 1.0f;
}
return LevenshteinDistance(str1, str2) / (float)len2;
}
戻り値は0~1の単精度浮動小数点値で、0ならまったく同じことを、1ならまったく違うことを意味する。やってみると比率0.5ぐらいでは、似てるかどうか判断がつかない感じで丁度良い。0.5未満なら似てると判断して良いだろう。こんな関数があったら、データベースで似ている名前を検索するのに便利だ。
このメソッドでは「A」と「a」は異なる文字と判定されるが、文字列が似ているかどうかを判断するときには、多分同じ文字と判定してほしいだろう。「あ」と「ア」なども同様。そうすると、距離や比率を測る前に、あらかじめ文字列の“ゆらぎ”みたいなのを統一(正規化)しておく必要がある。
(2020/01/01 初稿)
(2020/01/18 更新)