【Python】絵文字を含む Unicode 文字列の文字数をカウントする方法と文字ごとの構成要素


print(len("👩‍👧‍👦"))
# 5

絵文字1文字の長さが「5」。

コードポイントの個数ですね!

 

😀 文字を数える

データを15文字で作ります。

この文字をカウントするのは、re でどう書くのか分からなかったので、ライブラリを使いました。

👉 regex 2022.10.31 on PyPI - Libraries.io hatena-bookmark

Matching a single grapheme \X
The grapheme matcher is supported. It conforms to the Unicode specification at http://www.unicode.org/reports/tr29/.

👉 mrabarnett/mrab-regex hatena-bookmark


print(len(regex.findall(r'\X', data)))
# 15

正しくカウントできました!

 

😀 文字の構成(要素)を確認する

作ったデータを使って、各文字に含まれるコードポイントを確認するスクリプトを書いて実行してみます。


dump(data)

【Python】絵文字を含む Unicode 文字列の文字数をカウントする方法と文字ごとの構成要素

うまくそれぞれの絵文字の構成要素が分かるようになりました。

 

😀 まとめ

Python ではコードポイントをそのまま \UXXXXXXXX の形式で書けるので便利。

Kotlin では以下から。



【Python】Unicode / UTF-16 エスケープシーケンス文字列 から 文字 に変換する方法

みんな大好き絵文字「👍」で。


👉 GitHub Emoji Unicode Full Emoji List - shortcode | code point | escape-sequence hatena-bookmark

 

👍 Unicode エスケープシーケンス


s = '\\U0001f44d'  # r'\U0001f44d'

print(s)
# \U0001f44d

print(s.encode().decode('unicode-escape'))
# 👍

print(eval(f'"{s}"').encode().decode('unicode-escape'))
# ð

「Unicode エスケープシーケンス」から「文字」に変換するには、


'\\U0001f44d'.encode().decode('unicode-escape')

でいけます。

 

👍 UTF-16 エスケープシーケンス

同様に、


s = '\\ud83d\\udc4d'  # r'\ud83d\udc4d'

print(s)
# \ud83d\udc4d

print(s.encode('utf-16', 'surrogatepass').decode('utf-16'))
# \ud83d\udc4d

print(eval(f'"{s}"').encode('utf-16', 'surrogatepass').decode('utf-16'))
# 👍

「UTF-16 エスケープシーケンス」から「文字」に変換するには、


eval('"\\ud83d\\udc4d"').encode('utf-16', 'surrogatepass').decode('utf-16')

でいけます。

 

👍 まとめ

ややこしいです。他にいい方法ないかな。



【Unicode】UTF-16 サロゲートぺア と コードポイント の変換

UTF-16 は、Unicode の文字をコードポイントと呼ばれる整数値で表現するエンコーディング方式です。UTF-16 では、一部のUnicode 文字は16ビットでは表現できないため、サロゲートペアと呼ばれる特殊な方法で符号化されます。

サロゲートペアは、2つの16ビットの値(上位サロゲートと下位サロゲート)を組み合わせて1つの Unicode 文字を表現します。上位サロゲートは 0xD800-0xDBFF の範囲にあり、下位サロゲートは 0xDC00-0xDFFF の範囲にあります。

Wikipedia で調べると数式があります。

【Unicode】UTF-16 サロゲートぺア と コードポイント の変換
👉 Unicode - Wikipedia hatena-bookmark


code-point <-> surrogate-pair

これに合わせて Python コードにしてみました。


def cp_to_sp(cp):
    cp -= 0x10000
    hsg = cp // 0x400 + 0xD800
    lsg = cp % 0x400 + 0xDC00
    return hsg, lsg


def sp_to_cp(hsg, lsg):
    cp = 0x10000 + (hsg - 0xD800) * 0x400 + (lsg - 0xDC00)
    return cp

この関数を、みんな大好き絵文字「👍」で試してみます。


👉 GitHub Emoji Unicode Full Emoji List - shortcode | code point | escape-sequence hatena-bookmark

表にあるデータから、コードポイントとサロゲートペアのそれぞれの値を使います。


cp = 0x1f44d
hsg, lsg = cp_to_sp(cp)
print(hex(hsg), hex(lsg))
# 0xd83d 0xdc4d

hsg = 0xd83d
lsg = 0xdc4d
cp = sp_to_cp(hsg, lsg)
print(hex(cp))
# 0x1f44d

うまくいってます。

 

👍 まとめ

Unicode まわりは CJK のおかげでか、ややこしいことになってます。

UTF-16などのエンコード処理を扱う場合、コード内では「コードポイント」を中心にを進めると幸せになることがよくあります。


👉 Python vs Kotlin Unicode Escape Sequence (エスケープシーケンス) の記述 hatena-bookmark