TL;DR
value側が
-
深さ1
スピードはeach_value
が最速
メソッドチェーン続けたい感じだったらflat_map
も考えられる -
深さ2以上で全部flatにしたいとき
values.flatten
が早いけどeach_value + flatten
とあまり速さ変わらないので好み。
実行環境 Ruby 2.7.1
パターン
sample = { a: [1, 2, 3], b: [2, 3, 4], c: [3, 4, 5] }
1. each
ary = []
sample.each { |_k, v| ary.concat(v) }
ary
# => [1, 2, 3, 2, 3, 4, 3, 4, 5]
2. each_value
ary = []
sample.each_value { |v| ary.push(*v) } # 上の例もconcatじゃなくてpushでも大丈夫
ary
# => [1, 2, 3, 2, 3, 4, 3, 4, 5]
3. values + flatten
sample.values.flatten
# => [1, 2, 3, 2, 3, 4, 3, 4, 5]
4. flat_map
# ruby 2.7だったら
sample.flat_map { _2 }
# => [1, 2, 3, 2, 3, 4, 3, 4, 5]
# _2が使えないなら
sample.flat_map { |_, v| v }
# => [1, 2, 3, 2, 3, 4, 3, 4, 5]
# flat_mapのレシーバーのvalueが何なのかココで示したいときはあえて書くのもありかなと思う
sample.flat_map { |_, number_ary| number_ary }
# => [1, 2, 3, 2, 3, 4, 3, 4, 5]
テスト
テスト1
深さ1のvalueをflatなArrayで取り出したいとき
結果は以下のとおりです。(1秒間に何回ループを回せるかという数字なので、デカイほうが早いです)
Comparison:
each_value: 2859228.9 i/s
each concat: 2745728.9 i/s - same-ish: difference falls within error
flat_map(_,v): 1986117.7 i/s - 1.44x (± 0.00) slower
flat_map(_2): 1971975.8 i/s - 1.45x (± 0.00) slower
values flatten: 918971.6 i/s - 3.11x (± 0.00) slower
テストコード
require 'benchmark/ips'
sample = { a: [1, 2, 3], b: [2, 3, 4], c: [3, 4, 5] }
def method_1(h)
ary = []
h.each { |_k, v| ary.concat(v) }
ary
end
def method_2(h)
ary = []
h.each_value { |v| ary.push(*v) }
ary
end
def method_3(h)
h.values.flatten
end
def method_4(h)
h.flat_map { _2 }
end
def method_5(h)
h.flat_map { |_, v| v }
end
Benchmark.ips do |x|
x.config(:time => 20, :warmup => 2)
x.report("each concat") { method_1(sample) }
x.report("each_value ") { method_2(sample) }
x.report("values flatten") { method_3(sample) }
x.report("flat_map(_2)") { method_4(sample) }
x.report("flat_map(_,v)") { method_5(sample) }
x.compare!
end
テスト2
深さ2の値で全部flatにして取り出したいとき
Comparison:
values flatten: 800647.1 i/s
each_value.flatten: 759526.1 i/s - 1.05x (± 0.00) slower
flat_map flatten: 676500.5 i/s - 1.18x (± 0.00) slower
each_value in flatten: 468859.1 i/s - 1.71x (± 0.00) slower
テストコード
require 'benchmark/ips'
sample = { a: [1, 2, [3]], b: [2, 3, [4]], c: [3, 4, [5]] }
def method_1(h)
ary = []
h.each_value { |v| ary.push(*v.flatten) }
ary
end
def method_2(h)
ary = []
h.each_value { |v| ary.push(*v) }
ary.flatten
end
def method_3(h)
h.values.flatten
end
def method_4(h)
h.flat_map { _2 }.flatten
end
Benchmark.ips do |x|
x.config(:time => 20, :warmup => 2)
x.report("each_value in flatten") { method_1(sample) }
x.report("each_value.flatten") { method_2(sample) }
x.report("values flatten") { method_3(sample) }
x.report("flat_map flatten") { method_4(sample) }
x.compare!
end
結論
深さ1のとき
each_valueでやるとのが一番早い。(eachを使うのとあまり変わらないが、可読性考えても普通にeach_valueか)
flat_mapだと返り値が欲しい値になっててメソッドチェーン続けやすいが遅め
values.flattenはかなり遅くなる
深さ2以上で全部flatにしたいとき
values.flatten
が早いけど each_value + flatten
と同じようなもん。
実行環境 Ruby 2.7.1