Slide 1

Slide 1 text

文字ときどきRuby NSEG 2024-01-27 とみたまさひろ 1

Slide 2

Slide 2 text

自己紹介 • とみたまさひろ • • https://twitter.com/tmtms https://blog.tmtms.net 2

Slide 3

Slide 3 text

これは同じ文字? 直 直 3

Slide 4

Slide 4 text

フォントが違うだけで同じ文字 直 U+76F4 日本語フォント 直 U+76F4 中国語フォント 4

Slide 5

Slide 5 text

これの違いと同じ 直 直 5

Slide 6

Slide 6 text

コンピュータで扱う文字は文字ごとに番号(コードポイント) が振られていてプログラムから見たときは同じコードポイ ントであれば同じ文字 6

Slide 7

Slide 7 text

Rubyでコードポイントを知る > '直'.ord.to_s(16) "76f4" > 'ほげ'.chars.map{_1.ord.to_s(16)} ["307b", "3052"] > 'ほげ'.unpack('U*').map{_1.to_s(16)} ["307b", "3052"] 7

Slide 8

Slide 8 text

これは同じ文字? 令 令 8

Slide 9

Slide 9 text

違う文字 令 U+4EE4 CJK統合漢字 令 U+F9A8 CJK互換漢字 9

Slide 10

Slide 10 text

正規化すれば同じ文字 String#unicode_normalize > '令'=='令' false > '令'=='令'.unicode_normalize true 10

Slide 11

Slide 11 text

正規化でこんなことも 使いようによっては便利 '0'.unicode_normalize(:nfkc) => '0' '①'.unicode_normalize(:nfkc) => '1' 'ア'.unicode_normalize(:nfkc) => 'ア' 'パ'.unicode_normalize(:nfkc) => 'パ' '㌖'.unicode_normalize(:nfkc) => 'キロメートル' 11

Slide 12

Slide 12 text

これは同じ文字? 令 � 12

Slide 13

Slide 13 text

異体字 令 U+4EE4 � U+4EE4 U+E0102 13

Slide 14

Slide 14 text

基底文字に異体字セレクタを追加することで プレーンテキストでも文字の見た目を 指定することができる 14

Slide 15

Slide 15 text

異体字セレクタ � U+4EE4 U+E0102 ←これ U+E0100〜U+E01EF が異体字セレクタ 対応システムと対応フォントが必要 15

Slide 16

Slide 16 text

異体字セレクタ 異体字セレクタセレクタが便利 https://747.github.io/vsselector/#!/ja/908a 16

Slide 17

Slide 17 text

異体字セレクタ unicode_normalize では消えないので U+E0100〜U+E01EF を消す "\u4ee4\u{e0102}".gsub(/[\u{e0100}-\u{e01ef}]/, '') 17

Slide 18

Slide 18 text

「髙」と「﨑」 > '高' == '髙' false > '崎' == '﨑' false 18

Slide 19

Slide 19 text

「髙」 Unicode では「髙」は「高」の異体字ではなく別の文字 別の文字なので異体字セレクタにもない SJIS(Windows-31J)でも別の文字 でも JIS では「髙」という文字は存在しない 「高」の異体字扱い 19

Slide 20

Slide 20 text

「髙」 > '髙'.encode('Windows-31J') "\x{FBFC}" > '髙'.encode('SJIS') # SJIS は Windows-31J の別名 "\x{FBFC}" > '髙'.encode('Shift_JIS') # Shift_JIS と SJIS は異なる # `encode': U+9AD9 from UTF-8 to Shift_JIS # (Encoding::UndefinedConversionError) 20

Slide 21

Slide 21 text

「髙」 Unicode 上は別の文字なので同一文字として扱わなけれ ばいいんだけど、人名検索とかだと同一文字として扱いた いこともあるかもしれないのでむずかしい 21

Slide 22

Slide 22 text

「﨑」 CJK互換漢字 「令」と同じ だけど unicode_normalize では「崎」にならない > '﨑'.unicode_normalize "﨑" # CJK互換漢字は普通は正規化できる > '福'.unicode_normalize "福" 22

Slide 23

Slide 23 text

「﨑」 これも「髙」と同じく変換するには個別対応が必要そう U+FA11(﨑)はU+5D0E(崎)に統 合漢字ブロックの異体字を持つが、字体 差が大きいとみなされ統合の範疇とされ ていない。 CJK互換漢字 - Wikipedia 23

Slide 24

Slide 24 text

これは同じ文字? へ ヘ 24

Slide 25

Slide 25 text

別の文字だけど日本語のバグ へ 平仮名 ヘ 片仮名 25

Slide 26

Slide 26 text

この文字数は? � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 26

Slide 27

Slide 27 text

国旗は2文字 � � U+1F1EF U+1F1F5 � � + � � 国コードを国旗用文字2文字で書くと国旗になる � � + � � = � � � � 27

Slide 28

Slide 28 text

3人家族は1文字 � � � � � � � � � � � � � � � � � � U+1F46A 「FAMILY」という絵文字 28

Slide 29

Slide 29 text

4人家族は7文字 � � � � � � � � � � � � � � � � � � � � � � U+1F468 � � � � � MAN U+200D ゼロ幅接合子 U+1F469 � � � � � � WOMAN U+200D ゼロ幅接合子 U+1F467 � � � � � � � � � GIRL U+200D ゼロ幅接合子 U+1F466 � � � � � BOY 29

Slide 30

Slide 30 text

濁点つき文字 ぱ U+3071 ぱ U+306F U+309A 「は」+合成用半濁点文字 (これは unicode_normalize(:nfc) で1文字の「ぱ」になる) 30

Slide 31

Slide 31 text

囲み文字 a⃝ U+0041 U+20DD a⃤ U+0041 U+20E4 a⃞ U+0041 U+20DE a ⃣ U+0041 U+20E3 31

Slide 32

Slide 32 text

人間の肌色と髪型 � � � � U+1F9D1(大人) � � � � + U+1F3FB(明るい肌色) � � � � + U+1F3FC(やや明るい肌色) U+200D U+1F9B0(赤毛) � � � � + U+1F3FE(やや濃い肌色) U+200D U+1F9B2(坊主頭) 32

Slide 33

Slide 33 text

文字数とは? � � � � 33

Slide 34

Slide 34 text

プログラム的に自然なのは コードポイントの数 でも人にはわかりにくい > '� �� � � � � � � � � � � � � � � � � �� � � � � � � � � � � � � � � � � � � � � �'.size 10 34

Slide 35

Slide 35 text

書記素 より 「人が1文字として見える文字」みたいな 書記素(しょきそ、英: grapheme)と は、書記言語において意味上の区別を 可能にする最小の図形単位をいう 書記素 - Wikipedia 35

Slide 36

Slide 36 text

Ruby で書記素を扱う String#grapheme_clusters > '� �� � � � � � � � � � � � � � � � � �� � � � � � � � � � � � � � � � � � � � � �'.size 10 > '� �� � � � � � � � � � � � � � � � � �� � � � � � � � � � � � � � � � � � � � � �'.grapheme_clusters ["� �", "� � � � � � � � � � � � � � � � � �", "� � � � � � � � � � � � � � � � � � � � � �"] > '� �� � � � � � � � � � � � � � � � � �� � � � � � � � � � � � � � � � � � � � � �'.grapheme_clusters.size 3 36

Slide 37

Slide 37 text

Ruby で書記素を扱う 正規表現 \X > '� �� � � � � � � � � � � � � � � � � �� � � � � � � � � � � � � � � � � � � � � �'.scan(/./) ["� �", "� �", "� � � � � � � � � � � � � � � � � �", "� � � � �", "", "� � � � � �", "", "� � � � � � � � �", "", "� � � � �"] > '� �� � � � � � � � � � � � � � � � � �� � � � � � � � � � � � � � � � � � � � � �'.scan(/\X/) ["� �", "� � � � � � � � � � � � � � � � � �", "� � � � � � � � � � � � � � � � � � � � � �"] 37

Slide 38

Slide 38 text

まとめ • ユニコードはカオス • 文字列を比較するときは正規化 • 文字数はコードポイントなのか書記素なのかを考える 38