🐾

何がいい?漢字を表す正規表現、UNIコードとの相互変換も考える

2024/04/01に公開

漢字を表す正規表現どれがいい?

漢字を抽出したい場合、どんな正規表現を用いたらいいでしょうか?

  • /[亜-熙]/
  • /[一-龠]/
  • /[\u3400-\u9FFF]/

こういったものが一般的でしょうか?
でも、UNIコードでの範囲選択も分かりづらいですし、漢字を使った範囲選択も結局の所、範囲が飛び石になってたりするので複数の範囲をいちいち選択しないといけないしで、大変だと思うんですよね。
何かいい方法が欲しいですよね。

これで決まり!漢字の正規表現

なにかいい物はないか?色々とネットを漁っていたら良さそうなのを見つけたので、紹介します。ズバリ

  • /\p{sc=Han}/u

これはかなり良いです。
漢字のカテゴリなら何でも拾います。

調べると、\p{カテゴリ名} でカテゴリ分類での抽出が可能なようで、他には、

  • /\p{sc=Hiragana}/u ひらがな抽出
  • /\p{sc=Katakana}/u カタカナ抽出

なども出来るようです。なかなか便利ですね。

漢字とUNIコードの相互変換

JavaScriptで、実際の相互変換を見て行きます。

/*---漢字からUNIコードへ変換 ---*/
console.log('漢'.charCodeAt(0).toString(16)); //結果 "6f22"

/*---UNIコードから漢字へ変換 ---*/
console.log(String.fromCharCode(0x6f22)); //結果 "漢"

恐らくcharCode を利用したものが一般的でしょう。
しかし、次のコードを見て下さい。

console.log('漢'.length); //結果 "1"
console.log('𩸽'.length); //結果 "2"

'漢'と'𩸽'で、lengthに差がありますね。
どういうことか簡単に書くと、UNIコードに新しく文字を対応させていきたいけど、番号の空きが少なくなってきたよね?じゃあ、番号2つを組み合わせて1つの文字を対応させていこうか~? って事のようです。

(因みにこの「𩸽」という字は「ホッケ」と読むそうです。焼いて美味い、ご飯のおかずにもお酒のおつまみにも良い、日本人にはおなじみの魚なんですが、漢字自体がUNIコード入りしたのは番号の空きが少なくなってからって事になるようですね。)

確かに、20個の番号の空きがあった場合、普通だったら20個の文字しか対応させられないけど、20の空きを、A軍10、B軍10に分けて、10*10=100 にした方が、たくさんの文字を対応させられますもんね。このように 空き番号を2つ割り当てるシステムを「サロゲートペア」 と呼びます。そして、この番号を2つ割り当てられた文字、charCodeによる変換だと面倒なんですよね…

/*---割り当てられた2つの番号は取得できるが… ---*/
console.log('𩸽'.charCodeAt(0).toString(16)); //結果 "d867"
console.log('𩸽'.charCodeAt(1).toString(16)); //結果 "de3d"

/*---2つの番号から文字を取得 (UNIコードではない) ---*/
console.log(String.fromCharCode(0xd867, 0xde3d)); //結果 '𩸽'

何か良い方法はないでしょうか?

おすすめはcodePpint

何か良い方法は無いか?ネットを漁っていると、codePointを利用するのが良いという事にたどり着きました。codePointについてざっとまとめると、

  • codePointは10進数
  • 各文字に1つずつ割り当てられている
  • codePointを16進数化したものがUNIコードとして扱われる

という事で、漢字とUNIコードの相互変換をcodePointを使用したコードが以下のようになります。

/*---16進数を10進数に変換する手間はあるが… ---*/
console.log('漢'.codePointAt(0).toString(16)); //UNIコード "6f22"
console.log(String.fromCodePoint(parseInt('6f22;', 16))); //文字列 "漢"

/*---codePointを使えば両方とも全く同じコードで、相互変換が可能 ! ---*/
console.log('𩸽'.codePointAt(0).toString(16)); //UNIコード "29E3D"
console.log(String.fromCodePoint(parseInt('29E3D', 16))); //文字列 "𩸽"

16進数を10進数に変換する手間はありますが、codePointを使えば、サロゲートペアが使われている漢字もそうでない漢字も、全く同じコードで漢字とUNIコードの相互変換が可能 であることが分かります。便利ですね。

おまけ codePoint内の漢字の割合?ざっくり雑感

codePointの総数がどのくらいなのか?イマイチよくわかってないのですが、とりあえず 1,114,111 (0 から 0x10FFFF ?) ということらしいので、この総数の中から漢字がどのくらい割り当てられているのか?ざっくり調べてみようと思います。
とりあえず、JavaScriptで次のコードを作り、適当に回してみました。

/*codePointよりランダム => 漢字の時抜出 ---*/
const codePointCheck = () => {
  let result = null;
  let i = 0;
	
  while (result == null) {
    i++;
    let str = Math.floor(Math.random() * 1114111);
    result = String.fromCodePoint(str).match(/\p{sc=Han}/u);

      if (result != null) {
        console.log(`漢字 ${result}`);
        console.log(`コードポイント ${str}`);
        console.log(`UNIコード U+${str.toString(16)}`);
        console.log(`length ${result[0].length}`);
        console.log(`ループ回数 ${i}`);
        break;
      }
    }
  }

10回試行結果

巡目 漢字 ※注) codePoint UNIコード length ループ回数
1 𠝦 132966 U+20766 2 30
2 𭚀 185984 U+2d680 2 19
3 𮇖 188886 U+2e1d6 2 1
4 𫞟 178079 U+2b79f 2 28
5 𡱒 138322 U+21c52 2 27
6   15225 U+3b79 1 17
7 𪔟 173343 U+2a51f 2 30
8 𱋇 201415 U+312c7 2 9
9   15416 U+3c38 1 5
10 𤘢 149026 U+24622 2 20

※注)漢字欄、スマホではほとんど表示できない かもしれません

ざっくり雑感

  • length2 の読めない漢字ばかり(笑)
  • length1 も読めないけどね(笑)
  • ループ回数は1~30 くらいに収まっている
  • 割と早く漢字カテゴリにヒットする感じ
  • とりあえず、length2 が大量に入っているイメージ
  • length2は、スマホでは表示出来るものが少ない感じ
  • もっと日本人向けの漢字が出れば…

まとめ

  • 漢字を判別する正規表現は /\p{sc=Han}/u が便利
  • 漢字とUNIコードの相互変換は codePoint が便利
  • length2系の読めない漢字は結構な数で割り当てられているイメージ

参考サイト様

https://js-next.hatenablog.com/entry/2016/04/21/013010
https://blog.jxck.io/entries/2017-03-02/unicode-in-javascript.html

Discussion