正規表現でjisx0208の第1水準漢字であるかどうかを判定する

jisx0208では第1水準漢字、第2水準漢字が定められている

JIS基本漢字 - CyberLibrarian


pythonで文字列が第1水準漢字のみで構成されているかどうかを判定する場合、どのようにすればいいだろうか


このような正規表現を見かけるがこれはうまく働かない。

# coding=utf-8

import re
regexp = re.compile(ur'^[亜-腕]+$')

たしかにjisx0208の第1水準漢字でjisコード値がもっとも小さいのは0x3021の「亜」であり、もっとも大きいのは0x4f53の「腕」であるが、unicodeのコード値は値も並び順も異なる。

第1水準漢字をunicodeのコードポイントの昇順で並べてみると、「亜」よりも小さい値の漢字もあるし「腕」よりも大きい値の漢字もある。

「些」は「亜」よりもコード値が小さい

# 些 \u4e9b
# 亜 \u4e9c
# 亡 \u4ea1

「腫」は「腕」よりもコード値が大きい

# 腔 \u8154
# 腕 \u8155
# 腫 \u816b

よって先ほどの正規表現では「些」や「腫」がひっかからない。

# coding=utf-8

import re
regexp = re.compile(ur'^[亜-腕]+$')
assert regexp.search(u"些") is not None  # assert error
assert regexp.search(u"亜") is not None
assert regexp.search(u"亡") is not None

assert regexp.search(u"腔") is not None
assert regexp.search(u"腕") is not None
assert regexp.search(u"腫") is not None  # assert error


では、第1水準漢字のなかでunicodeのcode値が一番小さい字と一番大きい値で正規表現を作ってみたらどうか

第1水準漢字は全てヒットする

# coding=utf-8

import re
regexp = re.compile(ur'^[一-龍]+$')

assert regexp.search(u"一") is not None
assert regexp.search(u"些") is not None
assert regexp.search(u"亜") is not None
assert regexp.search(u"亡") is not None
assert regexp.search(u"腔") is not None
assert regexp.search(u"腕") is not None
assert regexp.search(u"腫") is not None
assert regexp.search(u"龍") is not None

しかし第2水準漢字も一部を除いてほとんどヒットしてしまう。

assert regexp.search(u"丐") is None # assert error

ユニコードのコード値は、ここからここまでが第1水準漢字、ここから先は第2水準漢字、となるように並べられているわけではないので、このように範囲を指定することができない。


ではsjisバイト文字列で正規表現を作成したらどうか

と思って試してみたらこんなエラーになった

# coding=utf-8
import re

regexp = re.compile(ur'^[亜-腕]+$'.encode('sjis'))  # sre_constants.error: bad character range

「亜」のバイトは\x88\x9f、「腕」は\x98\x72だから?


こんな正規表現だとエラーにならないし、うまく第1水準漢字のみをヒットさせることができたっぽい。

# coding=utf-8
import re

regexp = re.compile(
    r"^(?:\x88[\x9F-\xFC]|[\x89-\x97][\x40-\xFC]|\x98[\x40-\x72])+$")