LoginSignup
0
0

More than 3 years have passed since last update.

Hashのvalue側がarrayで全部取り出したいときの速度比較

Posted at

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0