Slide 1

Slide 1 text

文字数の話 Unicodeの楽しい話

Slide 2

Slide 2 text

自己紹介 … 名前: しゅん … Twitter: @shun_shobon / GitHub: @shun-shobo1 … 学校: 長野高専 電子情報工学科 5– … 得意: Web Frontend / Web Frontend Ops / A11y なI … 研究: ホログラフィ・ヒューマンインタフェースなI … 趣味: PCゲーム・自作キーボー … 一言: コンピュータと人との関わり方を模索しています

Slide 3

Slide 3 text

突然ですがクイズです

Slide 4

Slide 4 text

このJSのコードは何を出力する?

Slide 5

Slide 5 text

答え

Slide 6

Slide 6 text

このJSのコードは何を出力する?

Slide 7

Slide 7 text

答え どう見ても11文字しかないのに出力は12......

Slide 8

Slide 8 text

今回はこの挙動を理解しよう! というコンセプトです

Slide 9

Slide 9 text

Unicodeの仕組み

Slide 10

Slide 10 text

Unicodeの目的 Unicodeは全ての文字にユニークなIDを降ること目的としている. このユニークなIDのことをUnicodeでは Code Point(コードポイント) と呼ぶ. チ → U+30C1 ゃ → U+3083 𠮟 → U+20B9F ! → U+FF01

Slide 11

Slide 11 text

Code Pointの符号化方法 このCode Pointを相手に送る際には,特定の方式によってバイナリにすることで送信される. これにはいくつかの方式がある. ˜ UTF-32 … どのCode Pointも4Byteで表r ˜ UTF-16 ... 小さなCode Pointは2Byte,大きいのは4Bytƒ ˜ UTF-8 ... なるべく小さなByteになるように1〜4Byteで表す ※ものすごい雑な解説なので詳しくは調べてください

Slide 12

Slide 12 text

符号化の例① 試しに「チ(U+30C1)」をそれぞれの形式で符号化すると... ※UTF-32・UTF-16はビッグエンディアンでの場合 g UTF-32 … 0x00, 0x00, 0x30, 0xCb g UTF-16 ... 0x30, 0xCb g UTF-8 ... 0xE3, 0x83, 0x81

Slide 13

Slide 13 text

符号化の例② 試しに「𠮟(U+20B9F)」をそれぞれの形式で符号化すると... I UTF-32 … 0x00, 0x02, 0x0B, 0x9V I UTF-16 ... 0xD8, 0x42, 0xDF, 0x9V I UTF-8 ... 0xF0, 0xA0, 0xAE, 0x9F ※UTF-32・UTF-16はビッグエンディアンでの場合

Slide 14

Slide 14 text

先程の挙動の解説

Slide 15

Slide 15 text

JavaScriptでの内部表現はUTF-16 JavaScriptでは仕様として文字列データの内部表現をUTF-16と定めている. このようなコードを実行した際,メモリ上に保存されるデータはCode Pointがそのまま保存 されるわけではなく,それをUTF-16で符号化した「0xD8, 0x42, 0xDF, 0x9F」が保存され る. → つまり1要素2byteの配列で管理される

Slide 16

Slide 16 text

.lengthの挙動 .lengthはこの2byte配列の要素数を数えているだけ 4byteで表現される「𠮟」があるため,11文字だけど要素数が12なので.lengthは12を返す.

Slide 17

Slide 17 text

解決策

Slide 18

Slide 18 text

StringのIteratorを使う Stringがネイティブに実装しているIteratorの処理はCode Point単位で処理される. IteratorベースのSpread演算子を使えばCode Point単位に文字列を分割できる!

Slide 19

Slide 19 text

よし!これで文字数のカウ ントはバッチリだ!

Slide 20

Slide 20 text

...本当に?

Slide 21

Slide 21 text

1Code Point = 1文字 ではない

Slide 22

Slide 22 text

異体字セレクタという存在 Unicodeには漢字や絵文字のバリエーションを表す異体字セレクタというものがある. 例えば「葛」と「葛󠄀」の違いや,「 」と「 」の違いなど. つまりCode Pointの数と直感的な文字数が一致しない場合がある. これらは基本となる文字にCode Pointを定義して,その文字の後に異体字セレクタという別 のCode Pointをつけることによって表現される.

Slide 23

Slide 23 text

結合文字という存在 意味的にはこの2つは等しいため,検索などでこの2つを異なる扱いをしてしまうと,直感に 反する可能性がある. Unicodeには結合文字という,複数のCode Pointを使って1文字を表現することがある. 例えば「が」は,「U+304C」と「U+304B, U+3099」の2通りの表し方がある.

Slide 24

Slide 24 text

絵文字の合字 合字とは「f」を2回重ねたときに「ff」のように2つがくっついた状態で表示されること. 一部の絵文字はこの合字を利用して複雑な絵文字を表現している場合がある. それぞれの絵文字の間にZero Point Joiner(U+200D)という不可視の文字を入れることに よってその絵文字は合成されているということを表す. ※Zero Point Joinerを使わないで合成される場合もあり(一部の国旗の絵文字など)

Slide 25

Slide 25 text

こんなのいちいち判別するプ ログラムなんて書けるか!

Slide 26

Slide 26 text

書けます

Slide 27

Slide 27 text

書記素クラスタと JSの便利なAPI

Slide 28

Slide 28 text

自然な区切りを表す書記素クラスタ これまで見てきたように,Code Point単位で見ても異体字セレクタや結合文字,合字などの 概念によってUnicodeにおける「1文字」というのはかなり表現するのが難しい. それに「1文字」という表現は非常に曖昧で,Byte単位なのか,Code Point単位なのか, 「いわゆる直感的な1文字」なのかが分かりづらい. そこで,「いわゆる直感的な1文字」をUnicodeでは書記素クラスタと呼んでいる. 書記素クラスタでの分割アルゴリズムはUnicodeの仕様として標準化されており,この仕様 に従えば誰でも直感的な1文字で文字列を分割することができる(とはいえアルゴリズムは非 常に複雑).

Slide 29

Slide 29 text

JSの便利なAPI,Intl.Segmenter() 書記素クラスタへの分割をJavaScriptでやる場合,ECMAScript標準APIである Intl.Segmenter()が使える. これを使うと文字列をロケールに応じて書記素,単語,文に分割することができる.

Slide 30

Slide 30 text

これでようやく正しく 「文字数」を 数えることができる

Slide 31

Slide 31 text

まとめ

Slide 32

Slide 32 text

まとめ n Unicodeにおける文字一つ一つに割り当てられるユニークなIDを Code Point と言g n JSでは文字列の内部表現がUTF-16で統一されていh n .lengthはCode Point単位の処理ではないので,直感と異なる値を返す事があh n Code Point単位で得たい場合はIteratorを使g n Unicodeでは「1Code Point = 直感的な1文字」とは限らな6 n 異体字セレク™ n 結合文— n 絵文字の合— n et† n 直感的な1文字のことをUnicodeでは 書記素クラスタ と呼Æ n JSでは Intl.Segmenter() を使うと簡単に書記素クラスタ単位に分割できる