ピンインの変換 その2

前回書いたのは、内部的に保存されている(と仮定した)声調番号を後ろに付けたフォーマット(以下、保存用フォーマット)のピンインから、いわゆる表示用フォーマットに変換するロジックの話でした。一応、それとは逆のパターンも考えてみたいと思います。

どんなときに使うか

少し前に、Google DocumentやEvernoteのOCR機能を用いた自動読み取りの可能性について調べました。結果的には、少なくとも声調符号付きのピンインを読み込むには実用的ではないという結論に至った(高解像度で再トライというのはまだ残っていますが)というのは、以前の記事に書いたとおりです。

ただ、その部分がクリアされた場合、もしくはネットから拾ってきたデータで表示用フォーマットとなっているものを保存するときに、表示用から内部保存用に変換してやる必要があります。もちろん、変換せずにそのまま保存するというのも1つの考え方かも知れませんが、いろいろ解析したいなと思ったときに、すべてのデータが同じフォーマットに保存されている方が有用なのは議論を待たないと思います。

こっちの変換の方が簡単だった?

ということで、ソースを書いてみました。いわゆるコンストラクタ(__init__)の部分は、前回の記事と同じなので、変換ロジックの部分だけです。

    def pinyin_converter_s(self, pinyind):
        #Convert Pinyin from display format (yāng) tostored format (yang1)
        non_alpha = re.findall(r'[^a-z]+', pinyind)
        if len(non_alpha) > 0:
            for i  in range(len(self.tones)):
                if non_alpha[0] in self.tones[i]:
                    pinyind = re.sub(non_alpha[0], self.tones[i][0], pinyind) + \
                                      str(self.tones[i].index(non_alpha[0]))
                    if 'ü' in pinyind:
                        pinyind = re.sub('ü', 'v', pinyind)
        return pinyind

やる前は、こっちの方が難しいと思ったのですが、実際にコードにしてみるとこっちの方が簡単でした。前提として、コンストラクタの中に母音に使うアルファベットと声調記号を乗せた場合の一覧をself.tonesというリスト(正確にはタプル)で持たせてあります。

まずは正規表現を使って、声調記号の乗っていない純粋なアルファベット以外の文字列を検索しています。re.findall()なので戻り値はリストになりますが、変なデータが入り込まない限りは1つだけ、つまり声調記号の載った文字だけが選ばれるはずです。なお、r'[^a-z]+と最後にプラス(+)が付いていますが、これも実験の結果です。声調記号付きのアルファベットは、\xで始まるコード2つの組み合わせ、つまり\xyy\xzzというフォーマットになっているのですが、正規表現の中で+をつけないで実行するとこれらが分割されてしまいました。

実験した結果も出しておきましょう。Functionの名前は同じですが、正規表現のところはr'[^a-z]となっていて、プラス記号は外した状態で実行しています。

>>> ppp.pinyin_converter_s('xiù')
'xi\xc3\xb9'

最後のuの上に第4声の声調記号が載っていて、戻り値は変換されて見づらいですが、何も変換されませんでした。理由を探るためにデバッグしてみました。

-> if len(non_alpha) > 0:
(Pdb) p non_alpha
['\xc3', '\xb9']

ここだけ抜き出されてもという感じですが(笑)、re.findall()した戻り値が入る変数non_alphaの中身を見ると、本来1つの文字(ù)が \xc3 と \xb9 の2つに分解されてしまっているのが分かると思います。

あとは声調番号を付加する部分ですが、self.tonesの要素番号は、声調番号と一致するようになっているので、index()を使ってその文字が何番目の要素に入っているかを取得してやればOKです。

最後に、ü を v に変換するロジックを追加してやれば、「ほぼ」完成です。

いくつかサンプルを使って試してみましょう。

>>> reload(pinyin)
<module 'pinyin' from '/Users/ken/Documents/workspace/NLTK Learning/scripts/pinyin.py'>
>>> from pinyin import *
>>> ppp = Pinyin()
>>> ppp.pinyin_converter_s('lǚ')
'lv3'
>>> ppp.pinyin_converter_s('xiě')
'xie3'

大丈夫そうですね。

でもer化には未対応なのです

ここまで作ってer化したピンインに未対応である事に気がつきました。er化というのは、普通話というか北京でよく現れる現象で、最後が巻き舌になるというやつです。例えば、「一点」(ちょっと)という単語が「一点儿」になるという具合です。これをピンイン化した場合に、ちょっとした問題があります。漢字の上では儿(er)という字が加わっただけなので「yidiǎn + er」としたいところですが、実際には「 yidiǎnr」という具合になり、eが省略されてしまうのです。

もともと、今回作ったロジックを呼ぶ前にピンインを漢字ごとに分割するという作業が必要なのですが、このer化について対応しないといけないですね。

(たぶんつづく)