Rubyによる、配列を結合し展開する処理について調べていみました。
間違っていればご指摘お願いします!
元のコード
今回は下記のコードをリファクタして、ベンチマークを計測しながら比べていこうと思います。
計測方法はこちらの記事を参考に計測していきます。
def nomal(items)
name_array = []
items.each do |item|
name_array.push([item[:id], item[:name]])
end
name_array.flatten
end
計測コード
require 'benchmark'
n = 10
items = 100.times.flat_map do |i|
[id: i, name: "山田#{i}"]
end
Benchmark.bm(10) do |r|
r.report "nomal" do
n.times{ nomal(items) }
end
r.report "each_splat" do
n.times{ each_splat(items) }
end
r.report "map_flatten" do
n.times{ map_flatten(items) }
end
r.report "flat_map" do
n.times{ flat_map(items) }
end
end
各リファクタコード
each.splat
def each_splat(items)
name_array = []
items.each do |item|
name_array.push(*[item[:id], item[:name]])
end
name_array
end
map.flatten
def map_flatten(items)
name_array = items.map do |item|
[item[:id], item[:name]]
end.flatten
end
flat_map
def flat_map(items)
name_array = items.flat_map do |item|
[item[:id], item[:name]]
end
end
ベンチマーク
n = 1
user system total real
nomal 0.000075 0.000004 0.000079 ( 0.000073)
each_splat 0.000051 0.000001 0.000052 ( 0.000052)
map_flatten 0.000056 0.000001 0.000057 ( 0.000056)
flat_map 0.000055 0.000001 0.000056 ( 0.000057)
n = 3
user system total real
nomal 0.000098 0.000002 0.000100 ( 0.000098)
each_splat 0.000058 0.000001 0.000059 ( 0.000059)
map_flatten 0.000123 0.000005 0.000128 ( 0.000136)
flat_map 0.000048 0.000005 0.000053 ( 0.000051)
n = 10
user system total real
nomal 0.000641 0.000003 0.000644 ( 0.000640)
each_splat 0.000244 0.000009 0.000253 ( 0.000252)
map_flatten 0.000436 0.000006 0.000442 ( 0.000442)
flat_map 0.000238 0.000008 0.000246 ( 0.000247)
n = 100
user system total real
nomal 0.005138 0.000109 0.005247 ( 0.005243)
each_splat 0.002081 0.000009 0.002090 ( 0.002089)
map_flatten 0.003699 0.000031 0.003730 ( 0.003730)
flat_map 0.001785 0.000001 0.001786 ( 0.001787)
n = 1000
user system total real
nomal 0.037624 0.000269 0.037893 ( 0.038009)
each_splat 0.014614 0.000028 0.014642 ( 0.014689)
map_flatten 0.030695 0.000288 0.030983 ( 0.031110)
flat_map 0.015104 0.000052 0.015156 ( 0.015213)
n = 100000
user system total real
nomal 3.133017 0.005101 3.138118 ( 3.144279)
each_splat 1.461549 0.002172 1.463721 ( 1.465885)
map_flatten 2.816710 0.003754 2.820464 ( 2.825173)
flat_map 1.506553 0.001913 1.508466 ( 1.510696)
まとめ
パフォーマンスという観点では、 each.splat型 と flat_map型 が約50%も改善されており、map.flatten型は比較的に改善されない結果となりました。
また、この記事に書かれてある通り、Rubyではeachよりもmapなどのコレクションを積極的に使うことが推奨されています。
「ループ内に処理を逐一記述する」 という手続き的発想から 「処理をEnumerableのメソッドに渡す」 というRubyらしい発想が大事らしいです。
つまり、今回のリファクタでは、 flat_map を用いて下記の通りにリファクタすることが良さそうです!
def flat_map(items)
name_array = items.flat_map do |item|
[item[:id], item[:name]]
end
end
補足
map.flattenも場合によっては、選ばれる実装方法です。flattenは、flat_mapと異なり、一階層展開するのではなく全て展開します。例えば、二次元配列から一次元配列を得るのではなく、多次元配列から一次元配列を得る場合、 flatten が選ばれます。