今回は、文章中のある単語を別の単語に置き換える処理を、辞書を使って一括処理したい時の2つのやり方「str.translate()とre.sub()」を整理してみます。
今回やりたいこと
例えば、こんな文章があった時。
東京大学と大阪大学では社会人向けの「AI講座」を開いている。
また経済産業省は「AIプログラム技術」や課題解決を企業や学生らが教え合う「AI学校」を9月にも立ち上げる。
実務経験を積んだ人材が互いに鍛え合い、専門人材を育成する。
「」に囲まれたAIがついた単語・・「AI講座」や「AI学校」・「AIプログラム技術」など・・を、「AI関連」みたいな一つの単語に置き換えるのなら、正規表現をつかってこんな感じで「re.sub()」で可能です。
intext = '''東京大学と大阪大学では社会人向けの「AI講座」を開いている。 また経済産業省は「AIプログラム技術」や課題解決を企業や学生らが教え合う「AI学校」を9月にも立ち上げる。 実務経験を積んだ人材が互いに鍛え合い、専門人材を育成する。 ''' print(re.sub('「AI.+?」','「AI関連」',intext))
でも、「AI学校」は「AIスクール」、「AIプログラム技術」は「AIスキル」、「AI講座」は「AIクラス」に、それぞれ変更したい場合は、これでは無理です。
こんな時にどうしたらよいのか?・・をテーマとします。
str.translate()は使えるか?
最初に頭に浮かぶ選択肢は「str.translate()」です。
これは置換対象が1文字の場合だと、見事に機能します。
例えば、半角の「A」を全角の「A」、「i」を全角の「I」、漢字の「実」を「業」に置き換えるような用途なら、以下のようにすればできます。
def trans_single(inputtext): encode_dic = {'A':'A','i':'I','実':'業'} encode_table = str.maketrans(encode_dic) return inputtext.translate(encode_table) intext = '''東京大学と大阪大学では社会人向けの「AI講座」を開いている。 また経済産業省は「AIプログラム技術」や課題解決を企業や学生らが教え合う「AI学校」を9月にも立ち上げる。 実務経験を積んだ人材が互いに鍛え合い、専門人材を育成する。 ''' print(trans_single(intext))
この結果はこんな感じ。
東京大学と大阪大学では社会人向けの「AI講座」を開いている。
また経済産業省は「AIプログラム技術」や課題解決を企業や学生らが教え合う「AI学校」を9月にも立ち上げる。
業務経験を積んだ人材が互いに鍛え合い、専門人材を育成する。
きれいに辞書通りに置き換わってます。
これで辞書を以下のように単語レベルに変更してみます。
replacements = {'AI講座':'AIクラス','AIプログラム技術':'AIスキル','AI学校':'AIスクール'}
すると、こんなエラーをはいて終了してしまいます。
string keys in translate table must be of length 1
長さ1を超える単語の置き換えは「re.sub()」でないと無理
じゃあ、やっぱり「re.sub()」じゃないと無理・・なのですが、普通にやると、辞書通りの置き換えはできません。
でも、laambda式と組合せてやると、以下のような感じでできます。
import re def trans_word2(inputtext): replacements = {'AI講座':'AIクラス','AIプログラム技術':'AIスキル','AI学校':'AIスクール'} print('({})'.format('|'.join(map(re.escape, replacements.keys())))) return re.sub('({})'.format('|'.join(map(re.escape, replacements.keys()))), lambda m: replacements[m.group()], inputtext) intext = '''東京大学と大阪大学では社会人向けの「AI講座」を開いている。 また経済産業省は「AIプログラム技術」や課題解決を企業や学生らが教え合う「AI学校」を9月にも立ち上げる。 実務経験を積んだ人材が互いに鍛え合い、専門人材を育成する。 ''' print(trans_word2(intext))
これを実行すると。
東京大学と大阪大学では社会人向けの「AIクラス」を開いている。
また経済産業省は「AIスキル」や課題解決を企業や学生らが教え合う「AIスクール」を9月にも立ち上げる。
実務経験を積んだ人材が互いに鍛え合い、専門人材を育成する。
うまく意図通りの置き換えができてます。
ちょっと補足説明です
上記はうまく行くのですが、以下がパッと見で複雑なので、補足しときます。
re.sub('({})'.format('|'.join(map(re.escape, replacements.keys()))), lambda m: replacements[m.group()], inputtext)
まず。
'({})'.format('|'.join(map(re.escape, replacements.keys())))
の部分です。
これは辞書(例だとreplacements)を使って、正規表現の条件式を生成します。
map関数は、「map(関数, 配列)」なので、re.escape関数で辞書のキーを処理して、エスケープ処理をさせてるだけで、その結果をjoinで連結してます。
前段の例で実行すると。
(AI\講\座|AI\プ\ロ\グ\ラ\ム\技\術|AI\学\校)
こんな感じの条件が生成されます。
条件が括弧()でくくられているので、結果はgroup()にはいっていくわけです。
それを。
lambda m: replacements[m.group()],
で一つずつ取り出して、その結果(置換前文字列がはいっている)をキーにして、 replacementsの値(置換後文字列)をとりだして、順番にre.sub()に置き換えさせている・・というわけです。
まとめ
自分の感覚だと、今回のような一括置換って「知ってれば楽勝だけど、知らないと思いつくのに結構苦しむ」典型的な処理だと思ってます。
実際。
こういう処理って、忘れたころに必要になって、前にどうしてたか?を思い出せないときはすったもんだするのですね。
なので、自分の備忘もかねて(笑)書いておきます。
ではでは。