のんびりやの日記

のんびり、たまに更新。

Pythonでユーザ辞書を作って文字列を変換しよう

スポンサードリンク

カニ食べたい。どうも、だーやまんです。

これは、SLP KBIT Advent Calendar 2019 - Adventarの15日目の記事です。

始めに

Text To Speech APIを利用する際、API側で読み仮名が正しく認識されないことがある。造語などに関しては、いうまでもなく。そのような時、どのようにして送信した文字列を思い通りに読んでもらうか、という処理をPythonで行った場合を紹介する。

とりあえず

以下の通りに読んで欲しい文字列があるとする。ここでは、鉤括弧や句読点等は読み飛ばされるものとする。

文字列: 僕の禁断の過負荷 『却本作り』!!
読み: ぼくのはじまりのまいなす、ぶっくめーかー

( めだかボックス第11巻 p25 )
この場合、正しくない読み仮名と造語が入り混じった文になっている。まずはこれらの辞書を作成する

u_dict = { '禁断':'はじまり', '過負荷':'まいなす', '却本作り':'ブックメーカー' }

あとは、元の文字列に対して、辞書の登録数だけ一致する部分を変換するだけである。辞書に対して、 items() を使うことでキーと値のペアのタプルのリストを取得できる。あとは置換してやるだけである。

read_text = ' 僕の禁断の過負荷 『却本作り』!!'

for word, read in u_dict.items():
    read_text = read_text.replace(word, read)

print(read_text)

結果

僕のはじまりのまいなす 『ぶっくめーかー』!!

これで思い通りに変換ができた。これがとりあえずの実装になる。

問題点

以上の実装で、以下のような辞書と文字列の時、どうなるだろうか。

u_dict = {'蜂':'びー', 'びー':'ハエ'}

read_text = 'ぶんぶんぶん、蜂がとぶ。びーは蠅である。'

for文の1巡目で文中の「蜂」が「びー」に変換され、2巡目で「びー」が「ハエ」に変換される。その結果、

ぶんぶんぶん、ハエがとぶ。ハエは蠅である。

になる。
変換された読み仮名がさらに変換されるのは問題である。自分の思わぬ文字列に変換される恐れがあるし、文字列爆発による攻撃も可能になる。
文字列爆発の例

u_dict = { 'a':'bbb', 'b':'ccc'}

read_text = 'aaa'

変換結果

ccccccccccccccccccccccccccc

悪意のある文字列を登録された場合、文字列は指数関数的に伸ばすことが可能になるので、メモリへの攻撃が可能になる。

解決策

str型の組み込み関数、 format() を用いる。一度、文字列の変換該当部分を{}と変換先の文字列の引数番号にする。そうすることで二重の変換を防ぐことができる。コードを見てみよう。

u_dict = {'蜂':'びー', 'びー':'ハエ'}

read_text = 'ぶんぶんぶん、蜂がとぶ。びーは蠅である。'
print(read_text)

read_list = [] # あとでまとめて変換するときの読み仮名リスト
for i, one_dic in enumerate(u_dict.items()): # one_dicは単語と読みのタプル。添字はそれぞれ0と1。
    read_text = read_text.replace(one_dic[0], '{'+str(i)+'}')
    read_list.append(one_dic[1]) # 変換が発生した順に読みがなリストに追加
print(read_text)

read_text = read_text.format(*read_list) #読み仮名リストを引数にとる
print(read_text)

実行結果

ぶんぶんぶん、蜂がとぶ。びーは蠅である。
ぶんぶんぶん、{0}がとぶ。{1}は蠅である。
ぶんぶんぶん、びーがとぶ。ハエは蠅である。

いい感じに変換されました。

終わりに

辞書の登録順に変換されてしまうので、優先度等の重みつけはできていない。辞書型を使うのではなく、新しくクラスを定義することで可能になるかな〜。私が運用している喋太郎でも、今回紹介した方法を用いている。誰かの参考になれば幸い。

 
 ブログバナーです  
 ご自由にお使いください

リンク先は
http://www.dayaman.work
でお願いします
プライバシーポリシー お問い合わせ