Skip to content

文字列をイテレーションする

公開日:

東京都渋谷区GMO Yoursで開催された『第138回 PHP勉強会@東京』でライトニングトーク(5分)として発表しました。

Download PDF

スライドテキスト

Page 1

文字列をイテレーションする

Iterate over "elements" of a string.

第138回 PHP勉強会@東京2019年5月29日 #phpstudy

Page 2

お前誰よ

  • うさみけんた (@tadsan) / Zonu.EXE
  • GitHub/Packagistでは id: zonuexe
  • ピクシブ株式会社 pixiv運営本部
  • Emacs Lisper, PHPer
  • Emacs PHP Modeのメンテナ引き継ぎました
  • 好きなリスプはEmacs Lispです
  • Qiitaに記事を書いたり変なコメントしてるよ

Page 3

宣伝

Page 4

Page 5

Page 6

先に

Page 7

今回話す内容は平成最後のLT大会で発表した内容に由来

Page 8

Page 9

今回話す実装は既にライブラリ化しました

Page 10

Page 11

おさらい

Page 12

イテレータとは何か

Page 13

任意の処理を繰り返しの構造に落とし込める

Page 14

繰り返し?

Page 15

みんなだいすきforeach

Page 16

どうするの?

Page 17

Iteratorインターフェイス

Page 18

Page 19

Page 20

Page 21

ジェネレータ

Page 22

関数でイテレータをつくれる

Page 23

時間がないので割愛

Page 24

このあとさんざん出ます

Page 25

さて本題

Page 26

文字列をイテレーションする

Iterate over "elements" of a string.

第138回 PHP勉強会@東京2019年5月29日 #phpstudy

Page 27

問題です

Page 28

入力した文字列を1文字ごとに改行区切りで出力せよ

Page 29

簡単じゃん

Page 30

Page 31

Page 32

簡単でしょ?

😄

Page 33

Page 34

本当に?

🤔

Page 35

Page 36

(どう表示されるかは環境による)

Page 37

…………

🙃

Page 38

どうしてこんなことがおこるのか

Page 39

前提1:PHPのstringは「文字」を知らない

Page 40

stringはただのバイト列

Page 41

$s = 'abc';echo $s[0];

これは先頭1文字ではなく1バイトを取り出す

Page 42

逆に言うとstringにはどんなバイナリも格納できる

Page 43

例:

<?php$s = file_get_contents('flower.png');header('Content-Type: image/png');echo $s;

Page 44

mb関数があるのでは?

Page 45

Page 46

mb関数はencodingを常に指定しないと(環境依存せず)確実に処理できない

🤔

Page 47

PHP5.6以降ではdefault_charsetのデフォルト値が"UTF-8"に、mbstring.internal_encodingが非推奨になったのは嬉しい

Page 48

それはencodingを明示的に指定しなくても安全に処理できることを意味しない

🙃

Page 49

前提2:UTF-8は可変長バイト

Page 50

ひとつの符号位置を表すために

1〜4バイト

Page 51

まだ日本語文字を「2バイト文字」と呼んだりしませんね?

Page 52

Unicodeより前の時代

Page 53

Shift_JISではガ (2バイト)

ガ (1バイト+1バイト)

Page 54

この頃は半角カナで情報量が半分になることもあった

Page 55

UTF-8ではガ (3バイト)

ガ (3バイト+3バイト)

Page 56

Unicode(UTF-8)で表現すると半角カナは単純にバイト数が倍になる

Page 57

たいへんですね

Page 58

その上でUnicode時代の今日何を「文字」と呼ぶかを考える

Page 59

1. バイトごと2. コードポイントごと3. 書記素クラスタごと

Page 60

バイトごと

Page 61

Page 62

これがまさにバイトごとのイテレーション

Page 63

これをジェネレータに変換

Page 64

Page 65

Page 66

ライブラリとしては「foreachできるオブジェクト」(=ジェネレータ)として提供

Page 67

そうすることで汎用的に利用できるようになる

Page 68

Page 69

例:数を集計する

Page 70

Page 71

コードポイントごと

Page 72

コードポイント(符号位置)

Page 73

Page 74

PHP(コア言語)はUnicodeを知らないが、PCRE関数preg_match()はUnicodeを知ってる ( /u 修飾子)

Page 75

Page 76

書記素クラスタごと

Page 77

書記素とは?

Page 78

人間の目に1文字に見える単位

Page 79

あ漢ガ

Page 80

どれも単独の書記素

Page 81

ところでUnicodeには「ガ」の複数の表現方法が存在する

Page 82

ガ (U+30AC)

ガ (U+30AB U+3099)

ガ (U+FF76 U+FF9E)

Page 83

結合したガカ + ゛(結合文字)

ガ (U+30AC)

半角カ + ゙

ガ (U+30AB U+3099)

ガ (U+FF76 U+FF9E)

Page 84

Unicode正規化

Page 85

余談: ファイル名に濁点のついた文字をGitで管理するとMacとそのほかのファイルシステムで諸々の厄介な事態が起こるので禁忌。これはAppleのファイルシステムが伝統的にファイル名をUnicode正規化することが問題

Page 86

(本筋から逸れるので各自ggr)

Page 87

書記素クラスタのターンはまだまだ終らないぜ

Page 88

絵文字が開いてしまったパンドラの匣

Page 89

Case1: 国旗

Page 90

絵文字には国旗が充実

Page 91

Page 92

……と思うじゃん?

Page 93

実態

Page 94

Page 95

国旗専用文字の[J]と[P]を並べて配置するとに化ける

Page 96

Case2: 家族

Page 97

Page 98

この絵文字のバイト数

strlen('

');

Page 99

この絵文字のバイト数

strlen('

');// => 25

Page 100

この絵文字のUTF-8文字数

mb_strlen('

', 'UTF-8');

Page 101

この絵文字のUTF-8文字数

mb_strlen('

', 'UTF-8');// => 7

Page 102

Page 103

👨|

👩|

👦|

👦

Page 104

Zero Width Joiner

Page 105

さて

Page 106

そんな文字でも一安心

Page 107

Page 108

ICU(intl)に依存

(常に正しく計算するにはシステムに最新のUnicode定義が実装されたICUが必要)

Page 109

今回の話題はただのトリビアか?

Page 110

No

Page 111

Webアプリとしては「文字数」を制限しなければいけない場合が多々ある

Page 112

サービス都合の制限ストレージ(DB)都合制限

Page 113

自分がどういう理由で文字数を制限しようとしているのか考えよう

Page 114

例:MySQLのインデックス長のデフォルト設定が767バイトだからutf8mb4のカラムの文字数をVARCHAR(191)以下にしたい

Page 115

例:長いユーザー名は画面に収まりきらないから30文字以下にしたい

Page 116

たいていはmb_strlen($s, 'UTF-8')

で片がつく

Page 117

「文字数」と「表示幅」は

全然違う概念なので厄介

(なので、今回紹介したeach_grapheme()を使って計算してもうまくいかない)

Page 118

オチはないけど各位よく考えて「文字」単位とつきあっていきましょう

Page 119

ちなみにTwitterの140文字制限がDBの制約ではないことは明白