Ruby 2.6先行チェック: `String#split`がブロックを取れる(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Ruby 2.6先行チェック: String#splitがブロックを取れる(翻訳)

本記事はRuby 2.6シリーズのひとつです。Ruby 2.6.0-preview2は最近リリースされました

Ruby 2.6より前のString#splitは、#splitされた文字列の配列を返します。

Ruby 2.6のString#splitにはブロックを渡せるようになります。このブロックは、#splitされた文字列ごとにyieldされて操作を追加します。

#splitにブロックを渡す方法を理解するため、#is_fruit?というメソッドを作成します。

def is_fruit?(value)
  %w(apple mango banana watermelon grapes guava lychee).include?(value)
end

野菜や果物の名前の文字列をカンマで区切ったものを入力として与えます。メソッドの目的は、入力された文字列から果物の名前を取り出して配列に保存することです。

String#splitだけの場合

input_str = "apple, mango, potato, banana, cabbage, watermelon, grapes"

splitted_values = input_str.split(", ")
=> ["apple", "mango", "potato", "banana", "cabbage", "watermelon", "grapes"]

fruits = splitted_values.select { |value| is_fruit?(value) }
=> ["apple", "mango", "banana", "watermelon", "grapes"]

#splitで作成された中間の配列には、果物名と野菜名がどちらも含まれています。

String#splitにブロックを渡す場合

fruits = []

input_str = "apple, mango, potato, banana, cabbage, watermelon, grapes"

input_str.split(", ") { |value| fruits << value if is_fruit?(value) }
=> "apple, mango, potato, banana, cabbage, watermelon, grapes"

fruits
=> ["apple", "mango", "banana", "watermelon", "grapes"]

#splitにブロックを渡すと、#splitが呼び出された文字列を返しますが、配列は作成されません。String#splitは、#splitされた文字列ごとにブロックをyieldします。この場合、果物の名前は別途用意した配列に追加されます。

追加情報

ベンチマーク

ブロックなしの#splitとブロックありの#splitそれぞれについて、大量のランダムな文字列を使ってベンチマークを作成しました。

require 'securerandom'

test_string = ''

100_000.times.each do
  test_string += SecureRandom.alphanumeric(10)
  test_string += ' '
end
require 'benchmark'

Benchmark.bmbm do |bench|

  bench.report('split') do
    arr = test_string.split(' ')
    str_starts_with_a = arr.select { |str| str.start_with?('a') }
  end

  bench.report('split with block') do
    str_starts_with_a = []
    test_string.split(' ') { |str| str_starts_with_a << str if str.start_with?('a') }
  end

end

結果は次のとおりです。

Rehearsal ----------------------------------------------------
split              0.023764   0.000911   0.024675 (  0.024686)
split with block   0.012892   0.000553   0.013445 (  0.013486)
------------------------------------------- total: 0.038120sec

                       user     system      total        real
split              0.024107   0.000487   0.024594 (  0.024622)
split with block   0.010613   0.000334   0.010947 (  0.010991)

benchmark/ipsライブラリを用いる別のベンチマークも回してみました。

require 'benchmark/ips'
Benchmark.ips do |bench|

  bench.report('split') do
    splitted_arr = test_string.split(' ')
    str_starts_with_a = splitted_arr.select { |str| str.start_with?('a') }
  end

  bench.report('split with block') do
    str_starts_with_a = []
    test_string.split(' ') { |str| str_starts_with_a << str if str.start_with?('a') }
  end

  bench.compare!
end

結果は次のとおりです。

Warming up --------------------------------------
               split     4.000  i/100ms
    split with block    10.000  i/100ms
Calculating -------------------------------------
               split     46.906  (± 2.1%) i/s -    236.000  in   5.033343s
    split with block    107.301  (± 1.9%) i/s -    540.000  in   5.033614s

Comparison:
    split with block:      107.3 i/s
               split:       46.9 i/s - 2.29x  slower

このベンチマークでは、ブロックありの#splitはブロックなしの#splitのおよそ倍の速度を叩き出しています。

今回の変更に関連するコミットとやりとりを以下に示します。

はてブより

Ruby 2.6先行チェック: `String#split`がブロックを取れる(翻訳)

“#splitにブロックを渡すと、#splitが呼び出された文字列を返しますが、配列は作成されません。” split 〜 join を無くすための機能追加なのかな

2018/07/31 18:11

関連記事

Rubyの新しいJIT「MJIT」で早速遊んでみた(翻訳)

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ