RのUnicode正規化関数(パッケージ)を作ってみた

自然言語を処理する場合,表記の揺れが問題になることがあります.
例えば半角カタカナと全角カタカナ,全角記号と半角記号の違いによる表記の揺れに関してはUnicode正規化で解決できます.

RにはUnicode正規化の組み込み関数やパッケージが提供されていないのでどうしても他の言語に頼ることになってしまいます.
いちいち他の言語で前処理するのって不便ですよね?RでUnicode正規化できたら便利ですよね??役に立ちますよね????
苦肉の策としてUnicode正規化用のWeb APIを作ってみたなんてエントリーを書きましたが,Web APIってのは微妙ですよね.

ということで作ってみました.
https://github.com/abicky/RUnicode
※Mac OS 16.6と64bit版Ubuntu 10.04でのみ動作確認済みです
※Windowsで使うにはどうするかよくわかりません…

デモ

> library(RUnicode)
> ?unormalize           # しょぼいドキュメントの確認
> unormalize("アイウエオ")   # 半角カタカナを全角カタカナへ(NFKC)
[1] "アイウエオ"
> ga <- "が"
> cat("\\x", paste(charToRaw(ga), collapse = "\\x"), sep = "", fill = TRUE)
\xe3\x81\x8c
> # 濁点を分解 (NFD)
> cat("\\x", paste(charToRaw(unormalize(ga, "NFD")), collapse = "\\x"), sep = "", fill = TRUE)
\xe3\x81\x8b\xe3\x82\x99

これでRでのテキスト処理もグッとやりやすくなりますねっ!ねっ!!ねっ!!!!

RUnicodeのインストール

GitHubから RUnicode_0.1.0.tar.gz をダウンロードして

$ sudo R CMD INSTALL RUnicode_0.1.0.tar.gz

※ICU(後述)がインストールされていなければなりません

ICUのインストール

RUnicodeを使うためにはICU (International Components for Unicode) のインストールが必要です.

Macな人

$ wget http://download.icu-project.org/files/icu4c/4.6.1/icu4c-4_6_1-src.tgz
$ tar xvf icu4c-4_6_1-src.tgz
$ cd icu/source
$ ./runConfigureICU MacOSX
$ make
$ sudo make install

Ubuntuな人

$ sudo aptitude install lib32icu42

その他

Macな人を参考にどうぞ(runConfigureICUを実行する際に指定するプラットフォーム名が変わるだけのはず)

雑談

今回初めてC/C++でRの拡張を書いてみたんですが,Rcppを使ってみてハマりにハマりました.
コメントに書いてありますが,Rに結果を渡そうとすると正規化できなくなるんです.
結果を返すところでコケるんじゃなくて,正規化そのものができなくなるんです.意味不明です.

#include <unicode/normlzr.h>
#include <Rcpp.h>
#include <iostream>

RcppExport SEXP normalize(SEXP str)
{
  Rcpp::CharacterVector strVec(str);
  const int n = strVec.length();
  Rcpp::CharacterVector retVec(n); 

  for (int i = 0; i < n; i++) {
    std::cout << strVec[i] << std::endl;
    icu::UnicodeString src(strVec[i], "utf-8");

    icu::UnicodeString dst;
    UErrorCode status;
    icu::Normalizer::normalize(src, UNORM_NFKC, 0, dst, status);

    if (U_FAILURE(status))
      std::cerr << u_errorName(status) << std::endl;

    // 以降4行(コメント・空白行除く)をコメントアウトすると正規化できなくなる
    int32_t len = dst.extract(0, dst.length(), NULL, "utf-8");
    char buf[len + 1];
    dst.extract(0, dst.length(), buf, "utf-8");

    // 次の行があると正規化できなくなる
    retVec[i] = buf;
  }

  return retVec;
}

一旦ベクトルに正規化結果を格納してそれをSEXPに変換して値を返そうとしてもダメで,文字列配列に格納してSEXPに変換してもダメで,関数を間に挟んでもダメでした.

ちなみにこれをRから使うためには次のような Makevars というファイルを設置して

PKG_CXXFLAGS=$(shell Rscript -e "Rcpp:::CxxFlags()")
PKG_LIBS=$(shell Rscript -e "Rcpp:::LdFlags()") -licuuc

次のように共有ライブラリとしてコンパイルするだけです.

$ R CMD SHLIB unormalize.cpp

あとは dyn.load で読み込んで .Call で関数を呼び出します.

解決方法が全くわからなかったので Rcpp を使わない方法を調べてなんとか所望の動作が得られたといった感じです.
あと,Rcpp を使わない場合でも #include とすると LENGTH が使えなくなったりと,まだ理解できていない部分が多いです. 今回は時間がなかったので細かいことは勉強していませんが,そのうちしっかり勉強したいところですね.

あと,ICUのライセンス(ICU License - ICU 1.8.1 and later)がよくわからないんですが,ICUのコピーライトもどっかに載せないといけないんですかね…?静的リンクしてるわけでもコピーしてるわけでもないですけど.