Rubyのgsubとブロックと$1

何年かおきにはまる気がするこういうの:

def content
  'aaabbbccc'
end

def content_gsub(regexp, repl = nil, &block)
  if repl
    content.gsub(regexp, repl)
  else
    content.gsub(regexp, &block)
  end
end

regexp = /.(.)./

content_gsub(regexp, '[\1]')       # => "[a][b][c]"
content_gsub(regexp) { "[#{$1}]" } # => "[][][]"
content_gsub(regexp) { '[\1]' }    # => "[\\1][\\1][\\1]"

たまにこういう感じでgsubしているライブラリがあって、うっかりはまった上にわりと回避策がない感じで困ったりする。

MatchDataを渡してくれるgsubがあるといいのかもなと時々思う。

def content_gsub2(regexp, repl = nil, &block)
  if repl
    content.gsub(regexp, repl)
  else
    content.gsub(regexp) { block.call($~) }
  end
end

content_gsub2(regexp) { |m| "[#{m[1]}]" } # => "[a][b][c]"

yieldするとちょっと速いらしい。(Ruby 2.3.1)

def content_gsub3(regexp, repl = nil)
  if block_given?
    content.gsub(regexp) { yield($~) }
  else
    content.gsub(regexp, repl)
  end
end

require 'benchmark/ips'

n = 1000

Benchmark.ips do |x|
  x.report('gsub') do
    n.times { content_gsub(regexp) { 'x' } }
  end
  x.report('gsub2') do
    n.times { content_gsub2(regexp) { 'x' } }
  end
  x.report('gsub3') do
    n.times { content_gsub3(regexp) { 'x' } }
  end
  x.compare!
end

いちおうこんな感じ:

Comparison:
                gsub:      199.0 i/s
               gsub3:      160.1 i/s - 1.24x slower
               gsub2:      136.3 i/s - 1.46x slower

追記