概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: Ruby 2.6 adds String#split with block | BigBinary Blog
- 原文公開日: 2018/07/17
- 著者: Taha Husain
- サイト: BigBinary Blog
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`がブロックを取れる(翻訳)
- [ruby]
“#splitにブロックを渡すと、#splitが呼び出された文字列を返しますが、配列は作成されません。” split 〜 join を無くすための機能追加なのかな