Rubyで配列に対して空かも知れない配列を追加する時は、事前にempty?でチェックした方が早いが、まとめて追加してから一気にflattenした方が圧倒的に早いが、そんなことするよりconcatした方が早い。
タイトルで完結シリーズ。(ご指摘頂いたArray#concatについて追記しました)
ある配列に対して、「空かもしれない配列」を追加していく処理が必要になった。
fruits1 = [:apple, :orange] fruits2 = [] fruits3 = [:grape] fruits_array = [fruits1, fruits2, fruits3]
こんな配列から、"[ :apple, :orange, :grape ]"こんな結果が欲しいときについて。普通はfruits_array.flattenで終わるけど。
配列に対して配列を"<<"で追加すると、配列には配列が格納される。(わかりにくい表現だ)
ruby-1.9.2-p0 > a1=Array.new => [] ruby-1.9.2-p0 > a2=[:apple, :orange] => [:apple, :orange] ruby-1.9.2-p0 > a1 << a2 => [[:apple, :orange]] ruby-1.9.2-p0 > a3=[:grape] => [:grape] ruby-1.9.2-p0 > a1 << a3 => [[:apple, :orange], [:grape]]
それが嫌な場合には、"+="で追加すれば良い。
ruby-1.9.2-p0 > a1=Array.new => [] ruby-1.9.2-p0 > a2=[:apple, :orange] => [:apple, :orange] ruby-1.9.2-p0 > a1 += a2 => [:apple, :orange] ruby-1.9.2-p0 > a3=[:grape] => [:grape] ruby-1.9.2-p0 > a1 += a3 => [:apple, :orange, :grape]
もし追加する対象に空配列"[]"があった場合、empty?で判断してから追加するのが良いのか、問答無用で+=を呼んでも大差ないのか、という疑問が浮かんだので、実際に試してみた。
10000個、ランダムに空配列or空じゃない配列を作成して、それをフラットな配列にする関数についてベンチマーク。
ついでに、<<で追加して、最後にflattenでフラットな配列にする場合についても比較してみた。
さらに、concatで繋げていく場合についても比較してみた。(追記)
結果から:
$ ruby --version ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-linux] $ ruby ./arraytest.rb user system total real Rehearsal -------------------------------------------------- without check 0.210000 0.010000 0.220000 ( 0.225342) with check 0.090000 0.010000 0.100000 ( 0.104588) flat w/ check 0.010000 0.000000 0.010000 ( 0.003227) flat w/o check 0.000000 0.000000 0.000000 ( 0.003293) concat each 0.000000 0.000000 0.000000 ( 0.001726) ----------------------------------------- total: 0.330000sec user system total real without check 0.200000 0.030000 0.230000 ( 0.218026) with check 0.100000 0.000000 0.100000 ( 0.104849) flat w/ check 0.000000 0.000000 0.000000 ( 0.003143) flat w/o check 0.010000 0.000000 0.010000 ( 0.003293) concat each 0.000000 0.000000 0.000000 ( 0.001738) okay.
concatがブッチギリに速い。flattenはその次に速い。empty?で比較する場合は比較なしで問答無用で+=する場合に比べて倍ぐらい速い。
flattenを使う場合、empty?で比較することの効果はあまり大きくないが、確かに差は生じる。でもconcatの方がはやーい。
結論:+=じゃなくてconcat使え。
以下ベンチマークコード。
require 'benchmark' def with_check(source_array) ary = Array.new source_array.each do |s| if s.empty? == false ary += s end end return ary end def without_check(source_array) ary = Array.new source_array.each do |s| ary += s end return ary end def do_flat_with_check(source_array) ary = Array.new source_array.each do |s| if s.empty? == false ary << s end end return ary.flatten end def do_flat_with_check_dest(source_array) ary = Array.new source_array.each do |s| if s.empty? == false ary << s end end ary.flatten! return ary end def do_flat_without_check(source_array) ary = Array.new source_array.each do |s| ary << s end return ary.flatten end def do_concat_whole(source_array) ary = Array.new ary.concat(source_array) return ary end def do_concat_each(source_array) ary = Array.new source_array.each do |s| ary.concat(s) end return ary end ary_size = 10_000 # prepare src_array = Array.new ary_size.times do if (rand(2)==1) src_array << [1,2] else src_array << [] end end puts " " + Benchmark::CAPTION ret1 = ret2 = ret3 = ret4 = ret5 = ret6 = nil Benchmark.bmbm do |x| x.report("without check") { ret1 = without_check(src_array) } x.report("with check") { ret2 = with_check(src_array) } x.report("flat w/ check") { ret3 = do_flat_with_check(src_array) } x.report("flat w/o check") { ret4 = do_flat_without_check(src_array) } x.report("concat each ") { ret5 = do_concat_each(src_array) } # x.report("concat whole ") { # ret6 = do_concat_whole(src_array) # } 全体にconcatしてもフラットな配列にはならない! end if (ret1 != nil) and (ret1 == ret2) and (ret2 == ret3) and (ret3 == ret4) and (ret4 == ret5) # and (ret5 == ret6) puts "okay." end