Use #flat_map { ... } instead of #map { ... }.flatten by britishtea

#ruby's #flat_map is much faster than combining #map and #flatten.

require 'benchmark'

arr = 1_000.times.map { (1..100).to_a }

Benchmark.bm 10 do |x|
  x.report('#flatten') { arr.map { |x| x << 1001 }.flatten(1) }
  x.report('#flat_map') { arr.flat_map { |x| x << 1001 } }
end

The results

# $ ruby flat_map.rb
#                 user     system      total        real
# #flatten     0.010000   0.000000   0.010000 (  0.024426)
# #flat_map    0.000000   0.000000   0.000000 (  0.001466)

# $ ruby flat_map.rb
#                 user     system      total        real
# #flatten     0.010000   0.000000   0.010000 (  0.031431)
# #flat_map    0.010000   0.000000   0.010000 (  0.001546)

Similar posts

Comments

cvb commented 5 months ago

45ae4438d56d8492f842d2d395ca8abe?size=52

you should try to compare with flatten!, it should be faster

kritik commented 5 months ago

5ffff3031466900e097521ca9cd8a4b3?size=52
Benchmark.bm 10 do |x|
  x.report('#flatten') { arr.map { |x| x << 1001 }.flatten(1) }
  x.report('#flatten') { arr.map { |x| x << 1001 }.flatten!(1) }
  x.report('#flat_map') { arr.flat_map { |x| x << 1001 } }
end

                 user     system      total        real
#flatten     0.050000   0.000000   0.050000 (  0.141270)
#flatten     0.050000   0.000000   0.050000 (  0.047996)
#flat_map    0.000000   0.000000   0.000000 (  0.002696)
 => [  0.050000   0.000000   0.050000 (  0.141270)
,   0.050000   0.000000   0.050000 (  0.047996)
,   0.000000   0.000000   0.000000 (  0.002696)
]

Seems that flat_map still to be faster

britishtea commented 5 months ago

5cfd98f56feb0c4ad9770242a0d29654?size=52

These are all valid points.

Doesn't [[1,2],3].flat_map.to_a return [[1, 2], 3] because #flat_map without a block returns an Enumerator, though?

Note that my benchmarks used #flatten(1) instead of simply #flatten, to ensure equal output.

[[[1,2],3],4].flat_map { |x| x << 1 } # => [[1, 2], 3, 1, 8]
[[[1,2],3],4].map { |x| x << 1 }.flatten(1) # => [[1, 2], 3, 1, 8]

I updated my benchmarks, keeping your comments in mind. I included a map-reduce test, based on your comment.

require 'benchmark'

def arr
  Array.new(1_000) { (1..1000).to_a }
end

Benchmark.bm 11 do |x|
  x.report('#flatten') { arr.map { |x| x << 1001 }.flatten(1) }
  x.report('#flatten!') { arr.map { |x| x << 1001 }.flatten!(1) }
  x.report('#flat_map') { arr.flat_map { |x| x << 1001 } }
  x.report('#reduce') { arr.map { |x| x << 1001 }.reduce { |ary, i| ary.concat i } }
end

# $ ruby flat_map.rb 
#                   user     system      total        real
# #flatten      0.290000   0.020000   0.310000 (  0.346795)
# #flatten!     0.270000   0.010000   0.280000 (  0.330005)
# #flat_map     0.100000   0.010000   0.110000 (  0.146458)
# #reduce       0.100000   0.020000   0.120000 (  0.153047)

# $ ruby flat_map.rb 
#                   user     system      total        real
# #flatten      0.290000   0.020000   0.310000 (  0.390538)
# #flatten!     0.290000   0.010000   0.300000 (  0.501599)
# #flat_map     0.110000   0.010000   0.120000 (  0.206092)
# #reduce       0.110000   0.020000   0.130000 (  0.215094)

releu commented 5 months ago

757fb0d5ec7560b6f25f5bd98eadc020?size=52

Cool. Thanks!

britishtea
cvb
kritik
makaroni4
releu