Characters

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になります。 また、並び替えが必要な場合の検出も効率的なので、それが必要なまれな区画に対してだけ並び替えを行うことで時間を節約することが出来ます。

...

Goにおける文字列、バイト、ルーンと文字 (Strings, bytes, runes and characters)

Goにおける文字列、バイト、ルーンと文字 #

Strings, bytes, runes and characters in Go By Rob Pike

はじめに #

1つ前の記事 では、その実装の背後にある機能を解説する例とともに、Goにおいてスライスがどのように動作するかを説明しました。 その知識を前提として、この記事ではGoにおける文字列について話します。 まず最初に、文字列はブログの記事にしては簡単すぎるように見えるかもしれませんが、上手に使うには文字列の動作を理解するだけでなく、 バイト、文字、ルーンの違いについても理解し、UnicodeとUTF-8の違いについても理解し、文字列と文字列リテラルの違いについても理解し、 その他多くの細かな違いについて理解する必要があります。

この話題を議論するときの1つのアプローチとして、FAQである「Goの文字列のn番目のインデックスにアクセスした時に、 なぜn番目の文字を取得できないのか」という質問の回答を考えてみましょう。この記事で説明していきますが、この質問には 現代社会でテキストがどのように動作しているかを多くの観点から考えるきっかけとなります。

Goにかぎらず、これらの問題について考える最高の導入は、Joel Spolskyの有名なブログポスト、The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) です。 彼がその記事の中で挙げた点を、この記事でも繰り返し言及します。

stringとは何か #

基礎からはじめましょう。

Goでは、文字列は実際には読み取り専用のバイトのスライスでした。バイトのスライスがなにかについて不確かな場合、あるいはそれがどう動作するか 不確かな場合は、前のブログポスト を読んで下さい。この記事ではすでに前のブログポストを読んでいることを前提とします。

stringは 任意の バイトを保持できることをはっきりと述べておくことは重要です。 stringはUnicode文字もUTF-8文字も、その他の事前定義の形式の文字を持つ必要はありません。 stringの中身を考える限りにおいては、それはバイトのスライスについて考えることと同義です。

(すぐあとで説明しますが)あるバイト値を定数で持つように \xNN という記法を使って文字列リテラルを定義しました。 (もちろん、16進数でのバイト値の範囲は両端含んで 00 から FF です)

    const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98"

文字列を表示する #

サンプルの文字列内のいくつかのバイトは正しいASCII文字やUTF-8の値ではないので、直接表示するとおかしな出力になります。 単順に表示すると

...