Tech Racho エンジニアの「?」を「!」に。
  • 開発

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」で早速遊んでみた(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。