トップ «前の日記(2025-11-11 (Tue)) 最新 編集   RSS 1.0 FEED  

Journal InTime


2025-12-06 (Sat) [長年日記]

_ String#stripで削除される文字を指定できるようにする

この記事は、Ruby/Rails Advent Calendar 2025の6日目の記事です。

とりあえず「何か書く」と登録したもののとくにネタはなく、どうしようかなと思ったらちょうど目に留まったポストが。

Post by @KitaitiMakoto@bookwor.ms
View on Mastodon

簡単なベンチマークを試すとこんな感じ。

voyager:tmp$ cat b.rb 
require "benchmark"

TARGET = " \t\r\n\f\v\0" + "x" * 1024 + "\0 \t\r\n\f\v"

Benchmark.bmbm do |x|
  x.report("strip") do
    10000.times do
      TARGET.strip
    end
  end

  x.report("gsub") do
    10000.times do
      TARGET.gsub(/\A\s+|\s+\z/, "")
    end
  end
end

voyager:tmp$ ruby b.rb
Rehearsal -----------------------------------------
strip   0.004674   0.000938   0.005612 (  0.005618)
gsub    0.284665   0.001060   0.285725 (  0.285733)
-------------------------------- total: 0.291337sec

            user     system      total        real
strip   0.000953   0.001997   0.002950 (  0.002950)
gsub    0.288242   0.000002   0.288244 (  0.288261)
voyager:tmp$ 

ちなみに、\sがstripの削除対象からNUL文字を除いたものかどうか確認しようとドキュメントを見ていたら、たしかにそうだったが、ついでに正規表現ではなく文字列リテラルだと"\s"" "(U+0020)と解釈されるということを知った。 Rubyとは30年近い付き合いだが、時折いまだに知らない一面を見せてくれる。

実用的には速度が問題になることはあまりない気もするがgsub(/\A\s+|\s+\z/, "")はちょっと読みにくいし、strip(" \t\r\n\f\v")と書けてもよさそうなものだ(ここでstrip("\s")と書いてしまうと前述のとおりstrip(" ")になってしまう)。

まず他の言語ではどうなっているか調べてみたところ、Pythonではstripに引数を指定すると削除対象の文字を指定できることがわかった。

>>> "---abc+++".strip("-+")
'abc'

他の言語に同様の機能があるということは一定のユースケースがあるという材料にもなるし、提案する時に言及すると導入されやすい(ただし、まつもとさんがあまり好きではなさそうな言語の場合はだまっておいた方がいいかもしれない)。

次にbugs.ruby-lang.orgに同様のissueがないか見てみる(こっちを先にやるべきだった)と、3か月前に[Feature #21552] allow String.strip and similar to take a parameter similar to String.deleteというissueが作成されていたことがわかった。

ここで解散してもよかったのだがそれだとまたネタを探さないといけない。実装はまだないようだったので、以前まつもとさんと話した時に「生成AIを使ったコードをRubyに受け入れてもいいと思っているし、実際に一部のコミッタは使っているらしい」という話を聞いて、「だったらCo-Authored-Byを付けるようにした方がいいんじゃないですかね」とか話してたのを思い出して、Claude Codeによる実装を試してみることにした。

「string.cで実装されているString#stripに省略可能な引数charsを追加して、charsが渡された場合は空白文字の代りにcharsに含まれる文字を除去するようにしてください」という雑な指示で最初に出て来た実装がこれ。 だいたい期待通りの動作だが、strip("0-9")のように範囲指定をすることができない。そんな指示してないんだから当然である。

tr_setup_table()という既存のヘルパー関数(trやdeleteで使われている)を使うように指示して出て来た実装がこれ。 これで範囲指定もできるようになった。

p " \t\r\n\f\vfoo \t\r\n\f\v".strip(" \t-\r") #=> "foo"

lstrip_offset()と別にlstrip_offset_chars()を追加しているせいで呼び出し側の分岐が増えて差分が多くなっていたり、ドキュメントの表現が他と比べてやや冗長だったりしたので、手で直した上でpull request #15400を作成した。

pull request作成後になかださんの指摘を受けてのドキュメントやテストの追加や、String#delete同様に複数引数を受け付ける修正を行った。

いいと思ったら、[Feature #21552]に👍️をお願いします。

Tags: Ruby