ひろこま Hack Log

プログラミングや機械学習などの知識を記録・共有します

Pythonでハイフンやマイナスなど類似文字の正規化

f:id:twx:20190502170113p:plain
類似文字の文字コード

類似文字問題

ハイフンとダッシュのように、別の文字ではあるが、見た目が全く同じ文字というものが存在しています。以下がその例です。

# ハイフンに見える文字と、その文字コード
- 0x2d0x20110x20120x20130xff0d0x25000x30fc 

# 波線に見える文字と、その文字コード0x301c0xff5e

# 中点に見える文字と、その文字コード
· 0xb70x22c5

見た目では区別が付きませんね。

これは結構困ります。キーボードで全角マイナスを打つときに、WindowsとMacでは別の文字が入力されるとさえ言われています。 https://www.ilovex.co.jp/Division/ITD/archives/2008/02/windowsmac.html

このような「見た目が同じでも実は別の文字」は、自然言語処理のあらゆる場面でも問題となってきます。たとえば、正規表現で電話番号をマッチさせたい時、上述の全てのハイフンを考慮しなくてはなりません。 これは大変面倒なので、事前に正規化することで特定のハイフンだけを考慮すれば良いというようにしましょう。

Pythonで正規化するコードを書いてみました。 なお、似た文字のリストはこちらを参考にさせていただきました。

Unicodeの似た文字を整理してみた - y-kawazの日記

def normalize_wavedash(text, show_conversion_rule=False):
    convert_from = [
        u'\u007e',
        u'\u223c',
        u'\u223e',
        u'\u301c',
        u'\u3030',
        u'\uff5e'
    ]
    convert_to = u'\u301c'
    for char in convert_from:
        text = text.replace(char, convert_to)
    if not show_conversion_rule:
        return text
    print('rule of wavedash normalization')
    for char in convert_from:
        print(char, hex(ord(char)), '->', convert_to, hex(ord(convert_to)))
    return text

def normalize_hyphen(text, show_conversion_rule=False):
    convert_from = [
        u'\u002d',
        u'\u00ad',
        u'\u2010',
        u'\u2011',
        u'\u2012',
        u'\u2013',
        u'\u2043',
        u'\ufe63',
        u'\u2212',
        u'\u207b',
        u'\u208b',
        u'\uff0d',
        u'\u2500',
        u'\u2501',
        u'\u30fc'
    ]
    convert_to = u'\u30fc'
    for char in convert_from:
        text = text.replace(char, convert_to)
    if not show_conversion_rule:
        return text
    print('rule of hyphen normalization')
    for char in convert_from:
        print(char, hex(ord(char)), '->', convert_to, hex(ord(convert_to)))
    return text

def normalize_3dots(text, show_conversion_rule=False):
    convert_from = [
        u'\u2026',
        u'\u22ef'
    ]
    convert_to = u'\u2026'
    for char in convert_from:
        text = text.replace(char, convert_to)
    if not show_conversion_rule:
        return text
    print('rule of 3 dots normalization')
    for char in convert_from:
        print(char, hex(ord(char)), '->', convert_to, hex(ord(convert_to)))
    return text

def normalize_dot(text, show_conversion_rule=False):
    convert_from = [
        u'\u00b7',
        u'\u2022',
        u'\u2219',
        u'\u22c5',
        u'\u30fb',
        u'\uff65'
    ]
    convert_to = u'\u30fb'
    for char in convert_from:
        text = text.replace(char, convert_to)
    if not show_conversion_rule:
        return text
    print('rule of dot normalization')
    for char in convert_from:
        print(char, hex(ord(char)), '->', convert_to, hex(ord(convert_to)))
    return text

if __name__ == '__main__':
    print(normalize_wavedash('', show_conversion_rule=True))
    print(normalize_hyphen('', show_conversion_rule=True))
    print(normalize_3dots('', show_conversion_rule=True))
    print(normalize_dot('', show_conversion_rule=True))

    text = 'やっほ〰 4月1日~4月2日 15:00∼16:00開催'
    print('変換前:', text)
    print('変換後:', normalize_wavedash(text))
    print()

    text = 'やっほ‒ 4月1日-4月2日 15:00─16:00開催'
    print('変換前:', text)
    print('変換後:', normalize_hyphen(text))
    print()

    text = '日時…4月1日-4月2日 時間⋯15:00─16:00'
    print('変換前:', text)
    print('変換後:', normalize_3dots(text))
    print()

    text = '日時・4月1日-4月2日 時間・15:00─16:00'
    print('変換前:', text)
    print('変換後:', normalize_dot(text))
    print()

上のコードを実行してみた結果。

rule of wavedash normalization
~ 0x7e -> 〜 0x301c
∼ 0x223c -> 〜 0x301c
∾ 0x223e -> 〜 0x301c
〜 0x301c -> 〜 0x301c
〰 0x3030 -> 〜 0x301c
~ 0xff5e -> 〜 0x301c

rule of hyphen normalization
- 0x2d -> ー 0x30fc
­ 0xad -> ー 0x30fc
‐ 0x2010 -> ー 0x30fc
‑ 0x2011 -> ー 0x30fc
‒ 0x2012 -> ー 0x30fc
– 0x2013 -> ー 0x30fc
⁃ 0x2043 -> ー 0x30fc
﹣ 0xfe63 -> ー 0x30fc
− 0x2212 -> ー 0x30fc
⁻ 0x207b -> ー 0x30fc
₋ 0x208b -> ー 0x30fc
- 0xff0d -> ー 0x30fc
─ 0x2500 -> ー 0x30fc
━ 0x2501 -> ー 0x30fc
ー 0x30fc -> ー 0x30fc

rule of 3 dots normalization
… 0x2026 -> … 0x2026
⋯ 0x22ef -> … 0x2026

rule of dot normalization
· 0xb7 -> ・ 0x30fb
• 0x2022 -> ・ 0x30fb
∙ 0x2219 -> ・ 0x30fb
⋅ 0x22c5 -> ・ 0x30fb
・ 0x30fb -> ・ 0x30fb
・ 0xff65 -> ・ 0x30fb

変換前: やっほ〰 4月1日~4月2日 15:00∼16:00開催
変換後: やっほ〜 4月1日〜4月2日 15:00〜16:00開催

変換前: やっほ‒ 4月1日-4月2日 15:00─16:00開催
変換後: やっほー 4月1日ー4月2日 15:00ー16:00開催

変換前: 日時…4月1日-4月2日 時間⋯15:00─16:00
変換後: 日時…4月1日-4月2日 時間…15:00─16:00

変換前: 日時・4月1日-4月2日 時間・15:00─16:00
変換後: 日時・4月1日-4月2日 時間・15:00─16:00

以上、今回はPythonでハイフンやマイナスなど類似文字を正規化してみました。 良い記事だと思っていただいた方は、SNSでのシェア、ブログからのリンク、「読者になる」ボタンのクリック、「★」ボタンのクリック、よろしくお願いします!

Koma Hirokazu 's Hacklog ―― Copyright © 2018 Koma Hirokazu