Goでの文字列の正規化 (Text normalization in Go)
Goでの文字列の正規化 #
Text normalization in Go by By Marcel van Lohuizen
はじめに #
先の記事では、Goでの文字列、バイト、文字について説明していました。
私は go.text レポジトリ(訳注:現在は golang.org/x/text パッケージ群、以下原文で go.text の部分は置き換える。)で多言語文字列処理向けの様々なパッケージの開発に関わってきました。
これらのパッケージのいくつかは別のブログポストに譲って、この記事では go.text/unicode/norm (訳注:現在は golang.org/x/text/unicode/norm)に焦点を当てたいと思います。
このパッケージは、先の文字列に関する記事、そして本記事のタイトルとなっている、文字列の正規化を扱います。
正規化は生のバイト列よりも高水準での抽象化を扱います。
正規化についてのすべてを知りたければ、Unicode標準の付録15を読むのが良いでしょう。 より読みやすい記事としては、対応するWikipediaのページ(訳注:日本語版)があります。 ここでは、正規化がどのようにGoに関わっているかに焦点を当てます。
正規化とは何か #
同じ文字列を表現するときにいくつかの方法があることがしばしばあります。たとえば、é(eのアキュート)は文字列内で単一のルーン("\u00e9")または ’e’のあとにアキュート・アクセントが続いたもの(“e\u0301”)として表現されます。Unicode標準によれば、この2つの表現はどちらも「正準等価」 であり、同等に扱われるべきです。
これら2つの表現の等価性を調べるために1バイトごとに比較していたのでは正しい結果は導けません。Unicodeでは2つの表現が正準に等価で、 同じ正規形に正規化される場合に、そのバイト表現が同じになるような正規形のセットを定義しています。
またUnicodeでは同じ文字を表すけれども見た目が異なる表現を同等とみなす「互換等価」を定義しています。 たとえば上付き文字の数字 ‘⁹’ と通常の数字 ‘9’ はこの形式では等価です。
これら2つ等価な形式に対して、Unicodeでは合成と分解を定義しています。 合成は、結合して1つのルーンにできる複数のルーンをその1つのルーンにい置きv換えることです。 分解は、ルーンを要素に切り離すことを指します。すべてNFから始まる次の表は、Unicodeコンソーシアムが各形式を識別する際に使っているものです。v
| 合成 | 分解 | |
|---|---|---|
| 正準等価 | NFC | NFD |
| 互換等価 | NFKC | NFKD |
Goの正規化に対するアプローチ #
文字列に関する記事で言及したように、Goでは文字列内の文字が正規化されていることを保証していません。
しかしながら、golang.org/x/text パッケージがそれを補填してくれます。
たとえば collate パッケージは、Go言語特有の方法で文字列を順場に並べるパッケージで、
これは正規化されていない文字列でも正しく動作します。
golang.org/x/text 内のパッケージは必ずしも入力が正規化されている必要はありませんが、一般的に一貫した結果を得るためには
正規化が必要でしょう。
正規化のコストはタダではないですが速いです。特に、照合や検索の場合、または文字列がNFDかNFCのいずれかで、バイトを並び替えなくても分解するだけで NFDになる場合は顕著です。実際に、99.98%のウェブ上のHTMLページのコンテンツはNFC形式です。(マークアップは含めていません。 含めた場合はその割合はより大きくなります。)ほぼ間違いなくたいていのNFCは(メモリの確保を必要とする)並び替えの必要なく分解するだけでNFDになります。 また、並び替えが必要な場合の検出も効率的なので、それが必要なまれな区画に対してだけ並び替えを行うことで時間を節約することが出来ます。
...