Page 1
文字列をイテレーションする
Iterate over "elements" of a string.
第138回 PHP勉強会@東京2019年5月29日 #phpstudy
公開日:
by USAMI Kenta@tadsan
に東京都渋谷区のGMO Yoursで開催された『第138回 PHP勉強会@東京』でライトニングトーク(5分)として発表しました。
Iterate over "elements" of a string.
第138回 PHP勉強会@東京2019年5月29日 #phpstudy
宣伝
先に
今回話す内容は平成最後のLT大会で発表した内容に由来
今回話す実装は既にライブラリ化しました
おさらい
イテレータとは何か
任意の処理を繰り返しの構造に落とし込める
繰り返し?
みんなだいすきforeach
どうするの?
Iteratorインターフェイス
ジェネレータ
関数でイテレータをつくれる
時間がないので割愛
このあとさんざん出ます
さて本題
Iterate over "elements" of a string.
第138回 PHP勉強会@東京2019年5月29日 #phpstudy
問題です
入力した文字列を1文字ごとに改行区切りで出力せよ
簡単じゃん
簡単でしょ?
本当に?
(どう表示されるかは環境による)
…………
どうしてこんなことがおこるのか
前提1:PHPのstringは「文字」を知らない
stringはただのバイト列
$s = 'abc';echo $s[0];
これは先頭1文字ではなく1バイトを取り出す
逆に言うとstringにはどんなバイナリも格納できる
<?php$s = file_get_contents('flower.png');header('Content-Type: image/png');echo $s;
mb関数があるのでは?
mb関数はencodingを常に指定しないと(環境依存せず)確実に処理できない
PHP5.6以降ではdefault_charsetのデフォルト値が"UTF-8"に、mbstring.internal_encodingが非推奨になったのは嬉しい
それはencodingを明示的に指定しなくても安全に処理できることを意味しない
前提2:UTF-8は可変長バイト
ひとつの符号位置を表すために
まだ日本語文字を「2バイト文字」と呼んだりしませんね?
Unicodeより前の時代
Shift_JISではガ (2バイト)
ガ (1バイト+1バイト)
この頃は半角カナで情報量が半分になることもあった
UTF-8ではガ (3バイト)
ガ (3バイト+3バイト)
Unicode(UTF-8)で表現すると半角カナは単純にバイト数が倍になる
たいへんですね
その上でUnicode時代の今日何を「文字」と呼ぶかを考える
1. バイトごと2. コードポイントごと3. 書記素クラスタごと
バイトごと
これがまさにバイトごとのイテレーション
これをジェネレータに変換
ライブラリとしては「foreachできるオブジェクト」(=ジェネレータ)として提供
そうすることで汎用的に利用できるようになる
例:数を集計する
コードポイントごと
コードポイント(符号位置)
PHP(コア言語)はUnicodeを知らないが、PCRE関数preg_match()はUnicodeを知ってる ( /u 修飾子)
書記素クラスタごと
書記素とは?
人間の目に1文字に見える単位
あ漢ガ
どれも単独の書記素
ところでUnicodeには「ガ」の複数の表現方法が存在する
ガ (U+30AC)
ガ (U+30AB U+3099)
ガ (U+FF76 U+FF9E)
結合したガカ + ゛(結合文字)
半角カ + ゙
Unicode正規化
余談: ファイル名に濁点のついた文字をGitで管理するとMacとそのほかのファイルシステムで諸々の厄介な事態が起こるので禁忌。これはAppleのファイルシステムが伝統的にファイル名をUnicode正規化することが問題
(本筋から逸れるので各自ggr)
書記素クラスタのターンはまだまだ終らないぜ
絵文字が開いてしまったパンドラの匣
Case1: 国旗
絵文字には国旗が充実
……と思うじゃん?
実態
国旗専用文字の[J]と[P]を並べて配置するとに化ける
Case2: 家族
この絵文字のバイト数
strlen('
');
この絵文字のバイト数
strlen('
');// => 25
この絵文字のUTF-8文字数
mb_strlen('
', 'UTF-8');
この絵文字のUTF-8文字数
mb_strlen('
', 'UTF-8');// => 7
👨|
👩|
👦|
👦
Zero Width Joiner
さて
そんな文字でも一安心
(常に正しく計算するにはシステムに最新のUnicode定義が実装されたICUが必要)
今回の話題はただのトリビアか?
No
Webアプリとしては「文字数」を制限しなければいけない場合が多々ある
サービス都合の制限ストレージ(DB)都合制限
自分がどういう理由で文字数を制限しようとしているのか考えよう
例:MySQLのインデックス長のデフォルト設定が767バイトだからutf8mb4のカラムの文字数をVARCHAR(191)以下にしたい
例:長いユーザー名は画面に収まりきらないから30文字以下にしたい
たいていはmb_strlen($s, 'UTF-8')
で片がつく
(なので、今回紹介したeach_grapheme()を使って計算してもうまくいかない)
オチはないけど各位よく考えて「文字」単位とつきあっていきましょう
ちなみにTwitterの140文字制限がDBの制約ではないことは明白
