ダメラウ・レーベンシュタイン距離

ダメラウ・レーベンシュタイン距離（ダメラウ・レーベンシュタインきょり、英: Damerau–Levenshtein distance）は、2つの配列の間の編集距離を測定するために情報理論と計算機科学で使われる文字列計量である。2つの単語間のダメラウ・レーベンシュタイン距離は、一方の単語を他方の単語に変換するのに必要な最小の操作回数である。ここで1回の「操作」とは1文字の挿入、削除、置換、あるいは2つの隣り合う文字の交換である。ダメラウ・レーベンシュタイン距離はフレデリック J. ダメラウ（英語版）とウラジミール I. レーベンシュタインにちなんで名付けられた 。 古典的なレーベンシュタイン距離を定義する操作は3つの古典的単文字編集操作、すなわち文字の挿入、削除、および置換である。ダメラウ・レーベンシュタイン距離を定義する操作にはこれらに加えて隣接文字交換が含まれている 。 ダメラウによると、スペルミス全体の80%以上は、これら4操作のうちの1操作の誤り1つで表現できる。ダメラウの論文では、最大1回の編集操作で訂正できる綴り間違いのみが考慮されていた。当初の動機は、スペルチェッカなどのアプリケーション・ソフトウェアを改善するために人間のスペルミス間の距離を測定することであったが、ダメラウ・レーベンシュタイン距離は、生物学においてタンパク質配列の変異を測定するためにも使われている 。 同じ文字列を2度以上編集することを許さない条件のもとで求められる編集距離を制限編集距離と呼ぶ。制限編集距離は最適文字列整列距離と呼ばれる量に一致することが知られている。この条件を課さない（つまり同じ文字列を1回より多く編集してよい）場合には、単に編集距離と呼ぶ。制限レーベンシュタイン距離は常にレーベンシュタイン距離に一致する。これは、レーベンシュタイン距離の計算では編集操作は1文字ごとであり、1度編集した文字列を2度編集する必要がないからである。しかし2文字の編集操作が存在するダメラウ・レーベンシュタイン距離に関しては、制限ダメラウ・レーベンシュタイン距離とダメラウ・レーベンシュタイン距離とが一致しない場合がある。 例として、CAとABCの編集距離を求める。CAに1回の隣接文字交換および1回の文字挿入を施せば CA → AC → ABC となるから、ダメラウ・レーベンシュタイン距離は LD ⁡ ( CA , ABC ) = 2 {\displaystyle \operatorname {LD} ({\text{CA}},{\text{ABC}})=2} である。これに対して、制限距離（あるいは最適文字列整列距離 Optimal String Alignment, OSA）を求めるための編集は1回の文字削除に続いて2回の文字挿入を施す CA → A → AB → ABC であり、制限距離は OSA ⁡ ( CA , ABC ) = 3 {\textstyle \operatorname {OSA} ({\text{CA}},{\text{ABC}})=3} である。ダメラウ・レーベンシュタイン距離を求めるときに使った編集 CA → AC → ABC が使えないのは、B を挿入する時点で同じ文字列を2回編集しており、この編集は制限距離を求める操作では禁止されているからである。制限距離については、三角不等式が一般には成立しないことに注意する。実際、 OSA ⁡ ( CA , AC ) + OSA ⁡ ( AC , ABC ) < OSA ⁡ ( CA , ABC ) {\displaystyle \operatorname {OSA} ({\text{CA}},{\text{AC}})+\operatorname {OSA} ({\text{AC}},{\text{ABC}})<\operatorname {OSA} ({\text{CA}},{\text{ABC}})} である。つまり制限距離は計量ではない。 ここでは計算が簡単な制限ダメラウ・レーベンシュタイン距離を求める。文字列 a {\textstyle a} の先頭から数えて i {\textstyle i} 番目の1文字を a [ i ] {\textstyle a[i]} とし、 a {\textstyle a} の先頭から i {\textstyle i} 番目までの長さ i {\textstyle i} の部分文字列を a [ 1 , i ] {\textstyle a[1,i]} とする。2つの文字列 a {\displaystyle a} および b {\textstyle b} の間の制限ダメラウ・レーベンシュタイン距離を定義するためにまず、文字列 a {\textstyle a} の部分文字列 a [ 1 , i ] {\textstyle a[1,i]} と、 b {\textstyle b} の部分文字列 b [ 1 , j ] {\textstyle b[1,j]} の間の制限距離関数 d a , b ⁡ ( i , j ) {\textstyle \operatorname {d} _{a,b}(i,j)} を、次のように再帰的に定義する： d a , b ⁡ ( i , j ) = min { 0 if  i = j = 0 d a , b ⁡ ( i − 1 , j ) + 1 if  i > 0 d a , b ⁡ ( i , j − 1 ) + 1 if  j > 0 d a , b ⁡ ( i − 1 , j − 1 ) + 1 ( a [ i ] ≠ b [ j ] ) if  i , j > 0 d a , b ⁡ ( i − 2 , j − 2 ) + 1 if  i , j > 1  and  a [ i ] = b [ j − 1 ]  and  a [ i − 1 ] = b [ j ] {\displaystyle \qquad \operatorname {d} _{a,b}(i,j)=\min {\begin{cases}0&{\text{if }}i=j=0\\\operatorname {d} _{a,b}(i-1,j)+1&{\text{if }}i>0\\\operatorname {d} _{a,b}(i,j-1)+1&{\text{if }}j>0\\\operatorname {d} _{a,b}(i-1,j-1)+1_{(a[i]\neq b[j])}&{\text{if }}i,j>0\\\operatorname {d} _{a,b}(i-2,j-2)+1&{\text{if }}i,j>1{\text{ and }}a[i]=b[j-1]{\text{ and }}a[i-1]=b[j]\\\end{cases}}} ここで 1 ( a [ i ] ≠ b [ j ] ) {\textstyle 1_{(a[i]\neq b[j])}} は、 a [ i ] = b [ j ] {\textstyle a[i]=b[j]} のとき 0 {\textstyle 0} になり、それ以外の場合に 1 {\textstyle 1} となる指示関数である。これらの場合分けはそれぞれ、次に示す部分文字列末尾の編集操作（あるいは編集操作しないこと）に対応している： 0 {\textstyle 0} ：2つの長さ 0 {\textstyle 0} の文字列を一致させるのに必要な編集回数は 0 {\textstyle 0} d a , b ⁡ ( i − 1 , j ) + 1 {\textstyle \operatorname {d} _{a,b}(i-1,j)+1} ： a [ 1 , i ] {\textstyle a[1,i]} が、 a [ 1 , i − 1 ] {\textstyle a[1,i-1]} に1回の挿入をすることで得られたと見なして編集回数を 1 {\textstyle 1} だけ増やす d a , b ⁡ ( i , j − 1 ) + 1 {\displaystyle \operatorname {d} _{a,b}(i,j-1)+1} ： b [ 1 , j ] {\textstyle b[1,j]} が、 b [ 1 , j − 1 ] {\textstyle b[1,j-1]} に1回の挿入をすることで得られたと見なして編集回数を 1 {\textstyle 1} だけ増やす d a , b ⁡ ( i − 1 , j − 1 ) + 1 ( a [ i ] ≠ b [ j ] ) {\textstyle \operatorname {d} _{a,b}(i-1,j-1)+1_{(a[i]\neq b[j])}} ： a [ i ] {\textstyle a[i]} と b [ j ] {\textstyle b[j]} が一致する場合には、 a [ 1 , i ] {\textstyle a[1,i]} と b [ 1 , j ] {\textstyle b[1,j]} の間の距離は a [ 1 , i − 1 ] {\textstyle a[1,i-1]} と b [ 1 , j − 1 ] {\textstyle b[1,j-1]} の間の距離に等しいので編集回数を変更しない。 a [ i ] {\textstyle a[i]} と b [ j ] {\textstyle b[j]} が一致しない場合には、 a [ i ] {\textstyle a[i]} を b [ j ] {\textstyle b[j]} に、あるいは b [ j ] {\textstyle b[j]} を a [ i ] {\textstyle a[i]} に置換したと見なして編集回数を 1 {\textstyle 1} だけ増やす d a , b ⁡ ( i − 2 , j − 2 ) + 1 {\textstyle \operatorname {d} _{a,b}(i-2,j-2)+1} ： a [ i ] = b [ j − 1 ] {\textstyle a[i]=b[j-1]} かつ a [ i − 1 ] = b [ j ] {\textstyle a[i-1]=b[j]} のとき、 a [ i − 1 ] {\textstyle a[i-1]} と a [ i ] {\textstyle a[i]} の交換、あるいは a [ i − 1 ] {\textstyle a[i-1]} と a [ i ] {\textstyle a[i]} の交換をしたと見なして編集回数を 1 {\textstyle 1} 回だけ増やす a {\textstyle a} と b {\textstyle b} の間の制限ダメラウ・レーベンシュタイン距離は、文字列全体の関数値 d a , b ⁡ ( | a | , | b | ) {\textstyle \operatorname {d} _{a,b}(|a|,|b|)} で与えられる。ここで | a | {\textstyle |a|} および | b | {\textstyle |b|} はそれぞれ文字列 a {\textstyle a} および b {\textstyle b} の長さ（文字数）である。 ここでは2つのアルゴリズムを示す。1つ目は、制限ダメラウ・レーベンシュタイン距離を求めるアルゴリズムである。これは2つ目のアルゴリズムに比べて単純である。2つ目のアルゴリズムは、非制限ダメラウ・レーベンシュタイン距離を求めるもので、レーベンシュタイン距離に隣接交換を追加して計算する。隣接交換を追加すると非常に複雑になる。 制限ダメラウ・レーベンシュタイン距離は、レーベンシュタイン距離を計算するワグナー・フィッシャー動的計画法アルゴリズムを単純に拡張することで計算できる。その擬似コードは： algorithm OSA-distance is input: strings a[1..length(a)], b[1..length(b)] output: distance, integer let d[0..length(a), 0..length(b)] // d は整数二次元配列で次元は length(a)+1, length(b)+1 // ここでは d は添字が 0 から始まる配列であり、a と b は添字が 1 から始まる配列であることに注意する for i := 0 to length(a) inclusive do d[i, 0] := i for j := 0 to length(b) inclusive do d[0, j] := j for i := 1 to length(a) inclusive do for j := 1 to length(b) inclusive do if a[i] = b[j] then cost := 0 else cost := 1 d[i, j] := minimum(d[i-1, j] + 1, // 削除 d[i, j-1] + 1, // 挿入 d[i-1, j-1] + cost) // 置換 if i > 1 and j > 1 and a[i] = b[j-1] and a[i-1] = b[j] then // 隣接文字交換：ここから d[i, j] := minimum(d[i, j], d[i-2, j-2] + cost) // 隣接文字交換：ここまで return d[length(a), length(b)] レーベンシュタイン距離を求めるアルゴリズムとの違いは、隣接文字交換にあたる計算が含まれる点である。 次は、真のダメラウ・レーベンシュタイン距離を求めるアルゴリズムである。このアルゴリズムでは、アルファベットに含まれる全文字 Σ の個数 | Σ | {\displaystyle |\Sigma |} が必要になる。成分が [0, |Σ|) に含まれる配列を使うからである。擬似コードは： algorithm DL-distance is input: strings a[1..length(a)], b[1..length(b)] output: distance, integer da := |Σ|個の整数からなる配列 for i := 1 to |Σ| inclusive do da[i] := 0 let d[−1..length(a), −1..length(b)] // d は次元が length(a)+2, length(b)+2 の二次元整数配列 // d の添字は -1 から始まり、a, b, および da の添字は 1 から始まることに注意 maxdist := length(a) + length(b) d[−1, −1] := maxdist for i := 0 to length(a) inclusive do d[i, −1] := maxdist d[i, 0] := i for j := 0 to length(b) inclusive do d[−1, j] := maxdist d[0, j] := j for i := 1 to length(a) inclusive do db := 0 for j := 1 to length(b) inclusive do k := da[b[j]] ℓ := db if a[i] = b[j] then cost := 0 db := j else cost := 1 d[i, j] := minimum(d[i−1, j−1] + cost, //置換 d[i, j−1] + 1, //挿入 d[i−1, j ] + 1, //削除 d[k−1, ℓ−1] + (i−k−1) + 1 + (j-ℓ−1)) //交換 da[a[i]] := i return d[length(a), length(b)] 非制限ダメラウ・レーベンシュタイン距離を求める正しいアルゴリズムを作るには次のことに注意する：1度交換された文字がそのあと2度と編集されないような編集操作の最適配列が存在する（交換のコスト W T {\displaystyle W_{T}} が挿入のコスト W I {\displaystyle W_{I}} と削除のコスト W D {\displaystyle W_{D}} の平均以上つまり 2 W T ≥ W I + W D {\displaystyle 2W_{T}\geq W_{I}+W_{D}} の場合に成立する）。したがって、1つの部分文字列を2回以上編集する2つの対称な方法： 隣接文字を交換して任意の個数の文字をその間に挿入する 文字列を削除し、その削除によって新たに隣り合うことになった隣接文字を交換する のいずれかだけを考えればよい。このアイディアを素直に実行するアルゴリズムは、文字列の長さ M {\displaystyle M} および N {\displaystyle N} に対して O ( M N ⋅ max ( M , N ) ) {\displaystyle O(MN\cdot \max(M,N))} の計算複雑性を持つ。ローランスとワグナーのアイディアを使ったアルゴリズムでは、最悪の場合でも O ( M N ) {\displaystyle O(MN)} に改善される。 Bitapアルゴリズムを、隣接交換を処理するように変更できる。この例については参考文献[1]の情報収集の節を見よ。 ダメラウ・レーベンシュタイン距離は、自然言語処理において重要である。自然言語では文字列が短く、誤り（綴り間違い）の個数が2を超えることは少ない。このような場合、制限編集距離と非制限編集距離の値が異なることは稀である。オーメンとロークは、一般化交換を導入して制限編集距離を拡張した。しかし、制限編集距離は一般には三角不等式を満足せず計量ではないので、メトリックツリーに使えないことに注意すべきである。 DNAでは挿入、削除、置換、および交換が頻繁に生じ、しかもどれも同程度の時間スケールで起こるので、ダメラウ・レーベンシュタイン距離は2つのDNA鎖の間の変化を計る計量として使うことができる。DNA、タンパク質、および他のバイオインフォマティクスなど、要素の整列に関連する課題では、ダメラウ・レーベンシュタイン距離に深く関係するニードルマン・ブンシュ・アルゴリズムやスミス・ウォーターマン・アルゴリズムなどを使うのが一般的である。 アメリカ合衆国政府は統合スクリーニング・リストAPIで、あいまい語検索のためにダメラウ・レーベンシュタイン距離を利用している。 Ispell はダメラウ・レーベンシュタイン距離が1の単語を訂正候補として提示する