Ruby で文字列の先頭・末尾にある空白文字を取り除くには,String#strip を使うよね?
ふつうのスペース(U+0020)はもちろん,タブや CR, LF,ヌル文字(U+0000)といったものも取り除いてくれる:
str = " \t foo bar\r\n"
p str.strip
# => "foo bar"
しかし,削除対象となるのは ASCII の範囲の空白文字だけだ。
全角スペース(U+3000)は削除してくれない。
Ruby 4.0 の String#strip
Ruby 4.0.0-preview3 のリリースノート によれば,Ruby 4.0 では String#strip に「削除すべき文字」が指定できるようになった。
個人的にはあまり嬉しくない。
Unicode には多様な空白文字が定義されている。それらをすべて対象にしたいことが多いのではないだろうか? それを具体的に列挙しなければならないなら便利でない。
仮に,「デフォルトの空白文字に全角スペースだけを追加」でよい場合でも
str.strip(" \t\r\n\f\v\0\u3000")
などと書かねばならない。
こんなコードを一目見て「正しく書けている」と分かるだろうか。
まして,NO-BREAK SPACE(U+00A0)とか EN SPACE(U+2002)といったものまで含めたいとすれば?
※なお,本記事で文字列のエンコーディングは UTF-8 を前提とする。
正規表現を使う
Ruby の正規表現で,Unicode で空白文字とされる文字を全部含む文字クラスは,POSIX ブラケットと呼ばれる表記を使って [[:space:]] と書ける。
([ ] が二重になっているが,外側の [ ] は文字クラスのブラケットで,内側の [ ] は POSIX ブラケットである)
もちろん,何を空白文字とみなすかは要件に依るだろう。[[:space:]] は最大限広い範囲といっていいかもしれない(\p{Zs} よりも広い)。
これを使うと,「文字列の先頭・末尾のあらゆる空白文字を削除する」処理は
str.gsub(/\A[[:space:]]+|[[:space:]]+\z/, "")
のように書ける。
(?u) と \s を組み合わせて
str.gsub(/(?u)\A\s+|\s+\z/, "")
と書くこともできる。
これらがいったいどんな空白文字を削除するのかや,(?u) とは何ぞや,ということは以下の記事を参照されたい。
Ruby の正規表現で空白文字は (?u:\s) と書ける #Onigmo - Qiita
ベンチマークテスト
Ruby 4.0 における String#strip の改変の背景には,「正規表現でやるよりも高速に」という動機があったようだ。
だから,先頭・末尾の空白の削除の実行時間が処理全体のなかで無視できない場合や,ライブラリーなどで極限まで速度を求めたいときは String#strip に「全部の空白文字」を渡すのがよいのかもしれない。
strip と gsub でどのくらい速度が違うのか調べようと思ったが,ここまで書いてやる気を失ってしまい,「全部の空白文字」を調べて列挙するのがめんどくさくなった。
どなたかやってください。
代わりに,gsub 版を Ruby 2.4.8 と 4.0.0-preview3 で比べたベンチマークテストの結果をお見せする。
まず benchmark_driver gem をインストールしておく。
そして,以下の YAML ファイルを用意:
prelude: |
str = " abcdefghijklmnopqrstuvwxyz\n"
benchmark:
- str.gsub(/(?u)\A\s+|\s+\z/, "")
そして,コマンドラインで以下のように:
benchmark-driver gsubstrip.yaml --rbenv "3.4.8;4.0.0-preview3"
(Ruby のバージョン管理に rbenv を使っている前提)
結果は以下のとおり:
4.0.0-preview3: 1476106.4 i/s
3.4.8: 603196.3 i/s - 2.45x slower
えっ,爆速化してね?
結果を見たとき目を疑ったが,いろいろ実験してみると,上の例は Ruby 3.4.8 と 4.0.0-preview3 で差が大きくなるケースであることが分かった。
処理対象の文字列によってはこれほどの差は出ないようだ。ほとんど差がない場合もある。
(逆にもっと差が出る例を作ることもできる)
文字列の中間にも空白文字があったり,非 ASCII 文字が入っていたりすると差が縮むようだ。