IQ Bot カスタムロジック（Python）：置換処理をループで効率化

[こちらの記事](https://qiita.com/IQ_Bocchi/items/46269089b99c6f322687)で、IQ Botにおける除外処理のループを使った効率化について説明しています。

除外は置換の一種なので、基本的に考え方は同じなのですが、たぶんコーディング的に若干置換の方が難易度が高い気がしたのでこちらに切り出しました。

##どんなときに役立つ？　～置換編～

置換をループで処理するケースで、一番よく出会うのは「半角カナの揃え」です。
IQ Botに内蔵されているOCRエンジンのひとつであるABBYYは、半角カナの読みにやや弱い傾向があります。

半角を全角で読んでしまう（あるいはその逆）だけであれば、Bot Storeに出ている日本語の半角⇔全角変換アクションでRPA側で補正すればいいのですが、「ガ」を「カ"」(カ＋ダブルクォーテーション)のように読んでしまうケースがあり、これはまともにRPAでやるよりPythonでやった方が早いと思います。

（RPAでもPythonは書けますが）


##除外と置換の違い

除外と置換の違いは、フィールド項目のロジックで見てみると以下のとおりです。

```python:除外と置換の違い
#これが除外
field_value = field_value.replace("除外する文字列","")
#これが置換
field_value = field_value.replace("置換前の文字列","置換後の文字列")
```

基本の文法は「置換」の方で、除外は置換でいう「置換後の文字列」を`""`(＝空の文字列)にすることで除外を実現している、という仕組みです。

除外の場合は、置換後の文字列が常に`""`で固定だったので、複数にわたる要素は`"除外する文字列"`（＝置換前の文字列）だけでした。
なので、ループを回す対象のシーケンスは一次配列の構造でOKでした。

一方、置換の場合は置換の前後がともに複数にわたるので、置換前・置換後をセットでシーケンスに入れる必要があります。
二次配列の構造です。

これが難易度UPポイント。

でも安心してください。
はいてま……いえ、ちゃんと解説します。

##ズバリ、やりかた（置換の場合）

以下の構造で、置換処理のできあがりです。

```python:置換処理の考え方
replace_list = (("置換前の文字列1","置換後の文字列1"),
               ("置換前の文字列2","置換後の文字列2"),
               ("置換前の文字列3","置換後の文字列3"))

for i in replace_list:
    field_value = field_value.replace(i[0],i[1])
```

`replace_list`は、タプルの中にタプルが入っている、つまり二次配列の構造になっていますね。

forループの中の`i`には、１回目の処理のときは`("置換前の文字列1","置換後の文字列1")`、２回目の処理のときは`("置換前の文字列2","置換後の文字列2")`というふうに、子のタプルが入ります。

その`i`からインデックス0（`i[0]`／置換前）と1（`i[1]`／置換後）の要素をそれぞれ取り出して、`replace`の引数に渡しているという仕組みです。

##半角カナの揃え処理

ちなみに冒頭で例に挙げた半角カナの補正ロジックはこんなイメージです。

```python:半角カナに対する置換処理
replace_list = (('カ"','ガ'),('キ"','ギ'),('ク"','グ'),('ケ"','ゲ'),('コ"','ゴ'),
               ('サ"','ザ'),('シ"','ジ'),('ス"','ズ'),('セ"','ゼ'),('ソ"','ゾ'),
               ('タ"','ダ'),('チ"','ヂ'),('ツ"','ヅ'),('テ"','デ'),('ト"','ド'),
               ('ハ"','バ'),('ヒ"','ビ'),('フ"','ブ'),('ヘ"','べ'),('ホ"','ボ'),
               ('ハ°','パ'),('ヒ°','ピ'),('フ°','プ'),('ヘ°','ペ'),('ホ°','ポ'))

for i in replace_list:
    field_value = field_value.replace(i[0],i[1])
```

濁点は`"`(ダブルクオーテーション)、半濁点は`°`（度）と読まれることを前提に、全角カナに揃えるという処理です。

実際は、濁点や半濁点の読まれ方はもう少し揺れる場合が多いです。

OCRの結果を見つつ、通常の置換処理で、濁点や半濁点の読み取り結果を統一してあげてからこちらの処理をかけると効率がよいです。


##テーブルの場合は？（置換編）

テーブルの場合は、forループの中身をテーブル用に変えればいいので、以下のとおりで動くはず。

```python:置換処理（テーブルの場合）
replace_list = (("置換前の文字列1","置換後の文字列1"),
               ("置換前の文字列2","置換後の文字列2"),
               ("置換前の文字列3","置換後の文字列3"))

for i in replace_list:
    df['列名'] = df['列名'].str.replace(i[0],i[1])
```

でも上記はあまり美しくない……気がするのは私だけですか？

私だったらこうします。

```python:置換処理（テーブルの場合）

replace_list = (("置換前の文字列1","置換後の文字列1"),
               ("置換前の文字列2","置換後の文字列2"),
               ("置換前の文字列3","置換後の文字列3"))

def table_replace(x,y):
    for i in y:
        x = x.replace(i[0],i[1])
    return x

df['列名'] = df['列名'].apply(table_replace,y=replace_list)

```

こうしておくと、

- 置換したい文字列の組み合わせは同じだが、違う列に適用したいとき
- 列によって、置換したい文字列の組み合わせを変えたいとき

などに柔軟に対応が可能になります。

```python:置換処理（テーブルの場合）

replace_listA = (("置換前の文字列1","置換後の文字列1"),
               ("置換前の文字列2","置換後の文字列2"),
               ("置換前の文字列3","置換後の文字列3"))

replace_listB = (("置換前の文字列a","置換後の文字列a"),
               ("置換前の文字列b","置換後の文字列b"),
               ("置換前の文字列c","置換後の文字列c"))


def table_replace(x,y):
    for i in y:
        x = x.replace(i[0],i[1])
    return x

df['列名1'] = df['列名1'].apply(table_replace,y=replace_listA)
df['列名1'] = df['列名1'].apply(table_replace,y=replace_listB)
df['列名2'] = df['列名2'].apply(table_replace,y=replace_listA)
df['列名3'] = df['列名3'].apply(table_replace,y=replace_listB)

```

という要領です。

列名１には`replace_listA`と`replace_listB`による置換を両方適用し、
列名２には`replace_listA`による置換だけを適用し、
列名３には`replace_listB`による置換だけを適用した例です。

##以上！

うーんどうでしょう、難しかったですかね。

ご質問がある方は、この記事にコメントをお寄せいただくか、TwitterのDMにてご連絡ください。

※ 上記は毎日チェックできているわけではないため、回答に時間がかかる場合があります。
　 IQぼっちをリアルにご存じの方は、仕事用のメールに質問をください。