ユニコードで文字数を数える方法
ASCII.jp / 2024年9月22日 10時0分
ユニコードでは複数の絵文字を結合させて 別の絵文字を表現することもある
Unicodeが一般的になって、日本語を含めて、さまざまな言語の文字を自由に使えるようになったが、「文字」を取り出す、あるいは数えるのが面倒になったのも確かだ。というのも、1つの文字が必ずしも1つのコードポイントで表現されるとは限らないからだ。
たとえば絵文字では、複数の絵文字をゼロ幅接合子(Zero Width Joiner:ZWJ、U+200D)で結合することで、別の絵文字を表現することがある。たとえば、「🐦 鳥(bird)」(U+1F426)と「🔥 火」(U+1F525)をゼロ幅接合子でつなげたものは、「🐦🔥フェニックス」(Unicode Emoji 15.1で定義)の絵文字になる。
コードだと「U+1F426」「U+200D」「U+1F525」なのだが、表示上は1つの文字に見える。なお、こうした組み合わせは、ユニコード仕様書で定められ、誰かが勝手に作っているわけではない。
漢字の場合には、異字体がある。こちらはコードポイントの後ろに「異字体セレクタ」が付く。簡単に言えば、ユニコードは32bitのコードポイントを使うが、人間が認識する文字である「書記素クラスタ」(grapheme cluster)は、複数のコードポイントから構成されることがある。文字列から、書記素クラスタを認識して境界を決定する処理を「テキスト・セグメンテーション」という。
これは人間が見たときに「1文字」に見えるようなコードポイントのつながりを認識して、切れ目を見つけるのが「テキスト・セグメンテーション」である。ユニコードの処理では、分割された1文字(人の目に見える1文字)を「書記素」(grapheme)と表現することがある。
テキスト・セグメンテーションに関しては、ユニコードでは「Unicode Standard Annex #29 (UAX#29) Unicode Text Segmentation」(https://unicode.org/reports/tr29/)に定義がある。
PowerShellなどで、文字列を正しく分割するには、.NETの「StringInfoクラス(System.Globalization)」(https://learn.microsoft.com/ja-jp/dotnet/api/system.globalization.stringinfo?view=net-8.0)を使うのが簡単だ。
まずは、文字(書記素)の先頭位置を求める。PowerShellでは、内部は、リトルエンディアンのUTF16(これをWindowsではUnicodeと呼ぶ)でエンコードされている。このため、一部のコードポイントは、サロゲートペアを使って16ビット文字コード2つで表現されている。
StringInfoクラスの「ParseCombiningCharacters」メソッド(https://learn.microsoft.com/ja-jp/dotnet/api/system.globalization.stringinfo.parsecombiningcharacters?view=net-8.0#system-globalization-stringinfo-parsecombiningcharacters(system-string))は、文字列を受け取って、その書記素の先頭部分の位置を返すものだ。これを使うことで、文字の「境界」を得られる。
具体的には、PowerShellのコマンドラインで以下のようにする。
[System.Globalization.StringInfo]::ParseCombiningCharacters(<文字列>)
このメソッドは、書記素の先頭の位置を返す。たとえば、「葛󠄀-葛」という文字列は、コードポイントとしてみると、以下の図のようになっている。
この文字列に対して、ParseCombiningCharactersを実行すると、「0、3、4」という位置が返る。これは、文字の先頭が0文字目、3文字目、4文字目にあるということだ。入力が面倒そうだが、正直にキーを打つ必要はなく、先頭部分は、「“[stringinfo”+Tabキー」で、後半は「“]::p”+Tabキー」で開きカッコまで補完できる。
なお、いわゆる文字数(書記素クラスタの数)は、ParseCombiningCharactersがいくつ整数を出力しているかを数えるだけでいいので、
([System.Globalization.StringInfo]::ParseCombiningCharacters($x)).Length
で求めることができる(コマンドラインならMeasure-Objectコマンドを使うこともできる)。
文字を取り出す
この文字先頭位置から「書記素クラスタ」(文字)を取り出すには、GetNextTextElementメソッド(https://learn.microsoft.com/ja-jp/dotnet/api/system.globalization.stringinfo.getnexttextelement?view=net-7.0)を使う。このメソッドは、コードポイントが複数あっても、1つの書記素クラスタ分を文字列として出力してくれる。
具体的には、ParseCombiningCharactersの出力(文字の先頭位置)を、2つ目の引数に入れて文字(書記素クラスタ。セグメント)を取り出す。ParseCombiningCharactersの出力する位置を変数に入れおく。
$p=[System.Globalization.StringInfo]::ParseCombiningCharacters($x)
次にこれを使って、文字を順次取り出す。
$p | foreach-object { [System.Globalization.StringInfo]::GetNextTextElement($x,$_)
とすればよい。
ついでに16進数でダンプさせるなら、
$p | %{ $t=[System.Globalization.StringInfo]::GetNextTextElement($myKatu,$_); "$_ $t`t$(([int[]][char[]]$t)|%{$_.ToString('X4')})" }
などとする。
サンプルとしてもう少し複雑な文字列を使ってみる。
これは、家族4人の絵文字と異字体セレクタ付きの文字、サロゲートペアになる文字、それ以外の漢字の4つをハイフンでつなげたもので、$xに代入してある。まずは、$pに文字列内の先頭位置を格納しておく。このとき$p.lengthが書記素クラスタの数(文字数)となる。
$p=[System.Globalization.StringInfo]::ParseCombiningCharacters($x) $p.length $p
たとえば、先頭から3文字目(葛󠄀)までを取り出したいなら、
($p[0..2] | %{[System.Globalization.StringInfo]::GetNextTextElement($x,$_)}) -join ""
とする。前半の$p[0..2]は、3つ目の書記素クラスタの開始位置までの開始位置を取り出すもの(配列のインデックスなので0から始まることに注意)。後半の部分は、GetNextTextElementを使って、書記素に対応する文字列を取り出し、全体を-joinで結合して1つの文字列としている。
同様に後半4文字目から最後までを取り出したいなら。
($p[3..999] | %{[System.Globalization.StringInfo]::GetNextTextElement($x,$_)}) -join ""
とする。「[3..999]」は、書記素開始位置配列の4文字目から最後までを取り出すもの。範囲演算子では、対象配列の最大インデックスが分からないとき、それよりも大きなインデックス値を指定(ここでは999)を指定しておけば、エラーにならず、最大インデックスを指定したのと同等になる。もちろん「$p[3..($p.Length-1)]」などのように正しく計算してもいいが、最大インデックスを超えない大きな数を使うほうが簡単だ。
このようにすることで、文字列の前半部分(Left関数)、後半部分(Right関数)のようにユニコード文字列を正しい位置で分割することができる。
ユニコードになって、さまざまな文字を扱えるようになった反面、単純な文字列処理が不可能になり、今回のように、StringInfoクラスなどを使ってテキスト・セグメンテーションをして、その上で、分割などの処理をする必要がある。
なお、ユニコードの正しいテキスト・セグメンテーション(前述のUAX#29)には、.NET 5.0以降の対応なので、処理には、PowerShell(pwsh.exe)のほうを利用する。
この記事に関連するニュース
-
Intel製CPUに脆弱性 最大深刻度は「High」/家族間のゲーム共有を手軽に行える新機能「Steamファミリー」登場
ITmedia PC USER / 2024年9月15日 6時5分
-
「木」の下に「✕」…100年以上昔の戸籍でみつけた難読漢字の正体は? おそらく現在は使われていない“幻の異体字”
まいどなニュース / 2024年9月13日 19時50分
-
Excelをノーコードで自動化しよう! パワークエリの教科書 第18回 既存の列からデータを「抽出」する処理
マイナビニュース / 2024年9月9日 11時0分
-
PowerShellで面倒なオブジェクトはPSCustomObjectに変換するのが早道
ASCII.jp / 2024年9月8日 10時0分
-
Sixel形式の画像表示が可能に「Windows Terminal」プレビュー v1.22
マイナビニュース / 2024年8月28日 13時34分
ランキング
-
1バトルもアツい新作『英雄伝説 界の軌跡』を試遊!「日本ファルコム」ブースは巨大「みっしぃ」やノベルティなどが魅力【TGS2024】
インサイド / 2024年9月26日 20時20分
-
2「どういうお笑い?」 ヨシモト∞ホールが誤表示を謝罪 “斜め上をいくエラー”に「声出たwww」「どうしてこうなった」
ねとらぼ / 2024年9月26日 17時44分
-
3『パワフルプロ野球2024-2025』7月31日までに入団発表された選手を追加する第4回アップデート配信―「栄冠ナイン」追加バランス調整の予告も
Game*Spark / 2024年9月26日 21時30分
-
4『モンハンワイルズ』に「イャンクック」が登場決定!「クック先生」の愛称で親しまれる人気モンスターが久しぶりの復活
インサイド / 2024年9月26日 23時35分
-
5「変わってなかったらロボット」 元“ギャルのカリスマ”安西ひろこ、容姿巡る「たたき」にピシャリ “45歳の近影”が奇跡そのものだった「本当の美人」
ねとらぼ / 2024年9月25日 20時15分
記事ミッション中・・・
記事にリアクションする
記事ミッション中・・・
記事にリアクションする
エラーが発生しました
ページを再読み込みして
ください