Java/C++のLevenshtein distance

レーベンシュタイン距離を実装するには,Wikipediaを見れば十分です.

レーベンシュタイン距離 - Wikipedia

ここのサンプルに忠実に,例えばJavaで実装してみると,次のようになります.
以下,日本語コードはUTF-8だけに統一します.

import java.io.*;
import java.util.*;

class LevenshteinDistance
{
  int edit(String px, String py) throws UnsupportedEncodingException
  {
    int len1=px.length(),len2=py.length();
    int[][] row = new int[len1+1][len2+1];
    int i,j;
    int result;

    for(i=0;i<len1+1;i++) row[i][0] = i;
    for(i=0;i<len2+1;i++) row[0][i] = i;
    for(i=1;i<=len1;++i)
    {
      for(j=1;j<=len2;++j)
      {
        row[i][j] = Math.min(Math.min(
           (Integer)(row[i-1][j-1])
           + ((px.substring(i-1,i).equals(py.substring(j-1,j)))?0:1) , // replace
                      (Integer)(row[i][j-1]) + 1),     // delete
                      (Integer)(row[i-1][j]) + 1);  // insert
      }
    }
    result=(Integer)(row[len1][len2]);

    return result;
  }

  public static void main(String[] args) throws UnsupportedEncodingException
  {
    LevenshteinDistance ld = new LevenshteinDistance() ;
    System.out.println(ld.edit("あい","あいう"));
  }
}

このプログラムをedit0.javaというファイル名で保存して実行すると...

> javac -encoding UTF-8 edit0.java ; java -Dfile.encoding=UTF8 LevenshteinDistance
1

要するに「う」だけ違うので,距離は1となります.

同じプログラムをC++で作ると...

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
class LevenshteinDistance
{
public:
  static int edit(const string &px, const string &py)
  {
    int len1=px.size(),len2=py.size();
    vector<vector<int> > row(len1+1, vector<int>(len2+1));
    int i,j;
    int result;
    for(i=0;i<len1+1;i++) row[i][0]=i;
    for(i=0;i<len2+1;i++) row[0][i]=i;
    for(i=1;i<=len1;++i)
    {
      for(j=1;j<=len2;++j)
      {
        row[i][j] = min(min(
                      row[i-1][j-1] + ((px[i-1]==py[j-1])?0:1) , // replace
                      row[i][j-1] + 1),     // delete
                       row[i-1][j] + 1  );  // insert
      }
    }
    result=row[i-1][j-1];
    return result;
  }
};

int main()
{
  cout<<LevenshteinDistance::edit("あい","あいう")<<endl;
  return(0);
}

結果は,

> c++ -o LevenshteinDistance edit0.cc ; ./LevenshteinDistance
3

当然,UTF-8の全角一文字分違うので,3バイト分ということで距離が3になってしまいます.
c++にもw_charとか多言語を意識した予約がありますが...今のところ,互換性等,信用できない...

というわけで,自前で.

#include <iostream>
#include <vector>
using namespace std;
class LevenshteinDistance
{
public:
  static int isutf8(const string &c)
  {
    int ret;
    if((c[0]&0x80) == 0x00) ret = 1;
    else if((c[0]&0xe0) == 0xc0 && (c[1]&0xc0) == 0x80) ret = 2;
    else if((c[0]&0xf0) == 0xe0 && (c[1]&0xc0) == 0x80
                               && (c[2]&0xc0) == 0x80 ) ret = 3;
    return(ret);
  }
  static bool equal(const string &s1, const string &s2)
  {
    bool ret = false;
    if(isutf8(s1)==isutf8(s2))
    {
      int f=0;
      for(int i=0;i<isutf8(s1);++i)
      {
        if(s1[i]==s2[i]) ++f;
      }
      if(f==isutf8(s1)) ret = true;
    }
    return(ret);
  }
  static int edit(const string &px, const string &py)
  {
    int len1=0,len2=0;
    int i,j;
    int result;
    for(i=0;i<px.size();)
    {
      i += isutf8(&px[i]);
      ++len1;
    }
    for(i=0;i<py.size();)
    {
      i += isutf8(&py[i]);
      ++len2;
    }
    vector<vector<int> > row(len1+1, vector<int>(len2+1));

    for(i=0;i<len1+1;i++) row[i][0]=i;
    for(i=0;i<len2+1;i++) row[0][i]=i;
    int k=0,l=0;
    for(i=1;i<=len1;++i)
    {
      l = 0;
      for(j=1;j<=len2;++j)
      {
        row[i][j] = min(min(
                      row[i-1][j-1] + (equal(&px[k],&py[l])?0:1) , // replace
                      row[i][j-1] + 1),     // delete
                       row[i-1][j] + 1  );  // insert
        l += isutf8(&py[l]);
      }
      k += isutf8(&px[k]);
    }
    result=row[i-1][j-1];

    return result;
  }
};
int main()
{
  cout<<LevenshteinDistance::edit("あい","あいう")<<endl;
  return(0);
}

はい,実行すると

> c++ -o LevenshteinDistance edit.cc ; ./LevenshteinDistance
1

距離は1に.


せっかくなので,Javaのマルチバイト機能を信用しないバージョンを,C++から移植する形で書いてみました.

import java.io.*;
import java.util.*;

class LevenshteinDistance
{
  static int isutf8(String args) throws UnsupportedEncodingException
  {
    int ret = -1;
    byte[] c = args.getBytes("UTF-8");
    if((c[0]&0x80) == 0x00) ret = 1;
    else if((c[0]&0xe0) == 0xc0 && (c[1]&0xc0) == 0x80) ret = 2;
    else if((c[0]&0xf0) == 0xe0 && (c[1]&0xc0) == 0x80
                                && (c[2]&0xc0) == 0x80 ) ret = 3;
    return(ret);
  }
  static Boolean equal(String s1, String s2) throws UnsupportedEncodingException
  {
    Boolean ret = false;
    if(isutf8(s1)==isutf8(s2))
    {
      int f=0;
      for(int i=0;i<isutf8(s1);++i)
      {
        byte[] c1 = s1.getBytes("UTF-8");
        byte[] c2 = s2.getBytes("UTF-8");
        if(c1[i]==c2[i]) ++f;
      }
      if(f==isutf8(s1)) ret = true;
    }
    return(ret);
  }
  static int edit(String px, String py) throws UnsupportedEncodingException
  {
    Vector<Vector> row = new Vector<Vector>();
    int len1=0,len2=0;
    int i,j;
    int result;
    len1 = px.length();
    len2 = py.length();
    for(i=0; i<len1+1; ++i)
    {
      row.add(new Vector());
      for(j=0; j<len2+1; ++j)
      {
        row.get(i).add(0);
      }
    }

    for(i=0;i<len1+1;i++) row.get(i).set(0,i);
    for(i=0;i<len2+1;i++) row.get(0).set(i,i);
    for(i=1;i<=len1;++i)
    {
      for(j=1;j<=len2;++j)
      {
        row.get(i).set(j,Math.min(Math.min(
                      (Integer)(row.get(i-1).get(j-1))
          + (equal(px.substring(i-1),py.substring(j-1))?0:1) , // replace
                      (Integer)(row.get(i).get(j-1)) + 1),     // delete
                      (Integer)(row.get(i-1).get(j)) + 1  ));  // insert
      }
    }
    result=(Integer)(row.get(len1).get(len2));

    return result;
  }

  public static void main(String[] args) throws UnsupportedEncodingException
  {
    System.out.println(LevenshteinDistance.edit("あい","あいう"));
  }
}

実行しますと,

> javac -encoding UTF-8 edit.java ; java -Dfile.encoding=UTF8 LevenshteinDistance
Note: edit.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
1

一応,距離は1に.