【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 では以下から。



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

絵文字 は AndroidStudio 上で扱いづらいですよね。

例えば、


"😀😍"

という2つの絵文字は、


"\uD83E\uDD78" + "\uD83D\uDC3B\u200D\u2744\uFE0F"

と書く方が編集しやすいです。(しかし分かりづらい。)

絵文字をコピーして Android Studio 上でペーストすると、

\uXXXX\uXXXX

のような「UTF-16 エスケープシーケンス」に置き換わりますよね。

(しかし置き換わらない場合もある。)

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

 

😀 文字を数える

検証するために文字列データを作ります。

絵文字も新旧バージョンのものを含めます。

15文字です。

比較的に新しい Unicode Emoji 15.1 の2つの絵文字は、今現在、まともに見たことがありません。

このデータの文字数をカウントするのは、


println(Regex("\\X").findAll(data).count())
// 15

というかんじでしょうか。

Unicode extended grapheme clusters are supported by the grapheme cluster matcher \X.

👉 2.2 Extended Grapheme Clusters and Character Classes with Strings hatena-bookmark
👉 Unicord support - Pattern  |  Android Developers hatena-bookmark

強く開発環境に影響されると思いますので、各バージョン安定版最新に更新して揃えておくべきでしょう。


android {
  // ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_17
    targetCompatibility JavaVersion.VERSION_17
  }

  kotlinOptions {
    jvmTarget = JavaVersion.VERSION_17.toString()
  }
}

 

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

人間が見ている文字は内部的には複数の要素から構成されています。

絵文字周りで開発している方にはどこかで見たようなすごく便利な表です。

文字列を渡せば簡単に確認できるようにメソッドにしておきます。

先ほどの1文字ごとに分割したあとにその内部を要素に分割して表示します。

data は、先述の15文字を使ってみました。


dump(data)

AndroidStudio のデバッグウィンドウやターミナルでも表示できない絵文字はありますが、内部的には問題なく処理できてるように見えます。

 

😀 まとめ

今回、新しくはっきり認識できたことは、

kotlin の String.length は、


その文字(列)の Char (/uXXXX) の数を表している。

ということ。コードポイントの数ではない。

Char は、コードポイントを 内部的な UTF-16サロゲートペア 分割されたあとの要素。


println("👩‍👧‍👦".length)
// 8

👉 kotlin/Char.kt at 924c28507067cbfbf78a6509ea89eabe496e34ca · JetBrains/kotlin · GitHub hatena-bookmark



👉 絵文字が意図しない白黒で表示される ➡️ - Unicode Variation Selector hatena-bookmark


【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')

でいけます。

 

👍 まとめ

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