ログイン中のQiita Team
ログイン中のチームがありません

Qiita Team にログイン
コミュニティ
OrganizationイベントアドベントカレンダーQiitadon (β)
サービス
Qiita JobsQiita ZineQiita Blog
Python
自然言語処理
NLP
データ分析
83
どのような問題がありますか?

この記事は最終更新日から1年以上が経過しています。

投稿日

更新日

Organization

【Python】一行で全角と半角を相互変換する(英字+数字+記号)

テキスト分析などやっていると遭遇する全角/半角の変換処理をライブラリに頼らずにやります。
英字、英数字、記号の変換となります半角カナなどには対応していません。
Python 3.6.7 (Jupyter Notebook経由)で確認しています。

googleで探した限りでは、そうとうシンプルな部類と思います。

更新.1 指摘を頂き、変換部分を辞書内包表記に後半の全角・半角データ生成部分をジェネレータ内包表記に変更しました(2019/04/19)
更新.2 指摘を頂き、str.maketrans()の利用方法の別バージョンの追記と説明の更新を行いました(2019/04/20)

※※重要※※
テキストの各行を変換するなど、自然言語処理などで大量の変換処理を行う場合は下の一行の例ではなく、記事後半の変換テーブルを予め作成しておく方法の方が計算コストの面で良いと思われます。

少し形を変えてしまいましたが、こちらの記事を参考にさせていただいています。
http://eneprog.blogspot.com/2018/09/pythonunicodedata.html

全角 -> 半角

# 元の文字列
text = "!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`>?@abcdefghijklmnopqrstuvwxyz{|}~"

# 変換
text.translate(str.maketrans({chr(0xFF01 + i): chr(0x21 + i) for i in range(94)}))

# 結果
# '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`>?@abcdefghijklmnopqrstuvwxyz{|}~'

半角 -> 全角

# 元の文字列("と\の記号は「\"」「\\」としてエスケープしています。
text = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"

# 変換
text.translate(str.maketrans({chr(0x0021 + i): chr(0xFF01 + i) for i in range(94)}))

# 結果
# '!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'

文字列一括変換を行う関数translateに、変換前の文字をキー、返還後の文字をバリューとした辞書から作成した変換テーブルを渡して変換しています。

利用している関数

translate()

変換テーブルとして与えられた情報を元に、文字の置き換えを実施します
変換テーブルはstr.maketrans()に辞書を与えて作成します
https://docs.python.org/ja/3/library/stdtypes.html#str.translate

text = "abcdefg"

trans_table = str.maketrans({"a":"A", "d":"D"})
text.translate(trans_table)

# 結果
# 'AbcDefg'

str.maketrans()

translate()に与える変換テーブルを作成するための静的関数です
https://docs.python.org/ja/3/library/stdtypes.html#str.maketrans

(なぜtranslate()と統合しないのだろう)
作成済みの変換テーブルを使いまわせば、毎回生成するコストを削減できます。
特に大量のコーパスに変換をかける際には重要になりそうです。

次のように、辞書の代わりに変換元と変換先の二つの文字列を与えて変換テーブルを作成することもできます。一行にこだわらなければこちらの方が分かりやすいですね。
(shiracamusさん、ありがとうございます!)

変換テーブルを予め作成する例

ZEN = "".join(chr(0xff01 + i) for i in range(94))
HAN = "".join(chr(0x21 + i) for i in range(94))

ZEN2HAN = str.maketrans(ZEN, HAN)
HAN2ZEN = str.maketrans(HAN, ZEN)

# 全角から半角
print(ZEN.translate(ZEN2HAN))
# 結果
# !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

# 半角から全角
print(HAN.translate(HAN2ZEN))
# 結果
# !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrst

chr()

入力されたUnicodeコードに対応する文字列を返します
https://docs.python.org/ja/3/library/functions.html?highlight=chr#chr


chr(0x0021)

# '!'

変換辞書用データについて

半角データ

Unicode表に従い、「!」(0x0021)から94文字を半角データとして使用します
https://ja.wikipedia.org/wiki/Unicode%E4%B8%80%E8%A6%A7_0000-0FFF

"".join(chr(0x21 + c) for c in range(94))
# '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'

全角データ

Unicode表に従い、「!」(0xFF01)から94文字を半角データとして使用します
https://ja.wikipedia.org/wiki/Unicode%E4%B8%80%E8%A6%A7_F000-FFFF

"".join(chr(0xFF01 + c) for c in range(94))
# '!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'

内包表記

リスト内包表記

for 文の結果を用いてリストを作成する処理を一行にまとめたもの
https://qiita.com/y__sama/items/a2c458de97c4aa5a98e7

# リスト内包表記の例
[x * 2 for x in range(5)]

# 結果
# [0, 2, 4, 6, 8]

辞書内包表記

for 文の結果で辞書を作成する処理を一行にまとめたもの

# 辞書内包表記の例
{"no%d" % i: i for i in range(5)}

# 結果
# {'no0': 0, 'no1': 1, 'no2': 2, 'no3': 3, 'no4': 4}

ジェネレータ内包表記

(私の理解不足のため)正確に説明できないので次のURLなど参照。
https://qiita.com/keitakurita/items/5a31b902db6adfa45a70

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
YuukiMiyoshi
viが好きです。
fnlp
金融データ処理や自然言語処理に興味のあるメンバーがあつまって情報交換するコミュニティです

コメント

リンクをコピー
このコメントを報告

辞書内包表記にしてみました、

text = "!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`>?@abcdefghijklmnopqrstuvwxyz{|}~"

print(text.translate(str.maketrans({chr(0xFF01 + i): chr(0x21 + i) for i in range(94)})))
1
リンクをコピー
このコメントを報告

ジェネレータ内包表記も。

"".join(chr(0x21 + c) for c in range(94))
1
リンクをコピー
このコメントを報告

shiracamusさん、お恥ずかしながらリスト以外の内包表記を知りませんでした。本文を修正させていただきました。ありがとうございます。

0
(編集済み)
リンクをコピー
このコメントを報告

str.maketransは辞書以外にも、変換元と変換先の2つの文字列を指定することもできます。
https://docs.python.org/ja/3/library/stdtypes.html#str.maketrans

ZEN = "".join(chr(0xff01 + i) for i in range(94))
HAN = "".join(chr(0x21 + i) for i in range(94))
print(ZEN.translate(str.maketrans(ZEN, HAN)))
print(HAN.translate(str.maketrans(HAN, ZEN)))
実行結果
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
2
リンクをコピー
このコメントを報告

shiracamusさん
ありがとうございます!一行にこだわらなければこちらの方が分かりやすいですね。記事に追加させていただきました。

0
(編集済み)
リンクをコピー
このコメントを報告

もし、何度も変換するのであれば、maketrans結果を変数に保存しておいて再利用するといいです。

ZEN = "".join(chr(0xff01 + i) for i in range(94))
HAN = "".join(chr(0x21 + i) for i in range(94))
ZEN2HAN = str.maketrans(ZEN, HAN))
HAN2ZEN = str.maketrans(HAN, ZEN)

print(ZEN.translate(ZEN2HAN))
print(HAN.translate(HAN2ZEN))
2
リンクをコピー
このコメントを報告

shiracamusさん
ありがとうございます。実務での利用では変換テーブルの使いまわしの方が良いですね!
記事の冒頭とstr.maketrans()の例を更新させていただきました。

1
(編集済み)
リンクをコピー
このコメントを報告

リスト内包表記、辞書内包表記、集合内包表記では、すべてのデータを作ってしまい、メモリを圧迫します。
ジェネレータ内包表記は要素が必要になるまで処理をせず、for文やnext関数などで要素をひとつ取り出す処理が呼ばれると、要素を一つ作る分の処理だけして要素を返します。大量のデータ処理を行うときはジェネレータ関数やジェネレータ式にするといいです。

リスト内包表記ではメモリ不足になるだろう・・・
>>> data = [x * 2 for x in range(99999999999999999999999999999999)]
ジェネレータ内包表記なら平気
>>> data = (x * 2 for x in range(99999999999999999999999999999999))
>>> data
<generator object <genexpr> at 0x6ffffd6af10>
>>> next(data)
0
>>> next(data)
2
>>> next(data)
4
>>> for item in data:
...     print(item)
...     if item > 10:
...        break
...
6
8
10
12
3
(編集済み)
リンクをコピー
このコメントを報告

shiracamusさん
ありがとうございます。
ジェネレータについて、DBのカーソルの簡易版のようなものと把握していましたが、例を頂き使い方もイメージできました。面白いテクニックなどに出会った際には記事にさせていただきたいと思います。

0
リンクをコピー
このコメントを報告

@miyatsuki ありがとうございます。投稿時にチェックしていたつもりでしたがお恥ずかしい。適用させていただきました。

)が一つ多いので、確認いただきたいです。 by miyatsuki 2019/12/09 16:38

1
どのような問題がありますか?
あなたもコメントしてみませんか :)
ユーザー登録
すでにアカウントを持っている方はログイン
83
どのような問題がありますか?
ユーザー登録して、Qiitaをもっと便利に使ってみませんか

この機能を利用するにはログインする必要があります。ログインするとさらに下記の機能が使えます。

  1. ユーザーやタグのフォロー機能であなたにマッチした記事をお届け
  2. ストック機能で便利な情報を後から効率的に読み返せる
ユーザー登録ログイン
ストックするカテゴリー