Ruby 2.7 adds Enumerable#filter_map.
filter_map
is a shorthand for filter
+ map
in a single call.
Before
Originally suggested 7 years ago, it was addressing a common idiom to map over an Enumerable, but only include the elements that match a particular filter
enum = 1.upto(1_000)
enum.map { |i| i + 1 if i.even? }.compact
# => [3, 5, 7, 9, 11, 13, ...]
Another common approach is selecting + mapping,
which is pretty common, for example in ActiveRecord
,
when manipulating loaded records:
records = User.limit(10)
records.select { |user| user.is_admin? }.map{|user| "#{user.id} : #{user.name}"}
# => ["1: Jane", "4: Sam" ...]
It’s also possible to achieve something similar with:
enum = 1.upto(1_000)
enum.each_with_object([]) { |i, a| a << i + 1 if i.even? }
# => [3, 5, 7, 9, 11, 13, ...]
Enumerable#filter_map
We can now use Enumerable#filter_map
in Ruby 2.7 to achieve this:
enum = 1.upto(1_000)
enum.filter_map { |i| i + 1 if i.even? }
# => [3, 5, 7, 9, 11, 13, ...]
As you see, filter_map
creates a new array
after first filtering desired results,
and then maps to get expected Array.
This comes in pretty handy for creating mapped arrays in a simpler way.
It also gives us a bit of a speedup from its previous counter-parts
N = 1_00_000
enum = 1.upto(1_000)
Benchmark.bmbm do |x|
x.report("select + map") {N.times {enum.select { |i| i.even? }.map{|i| i +1}}}
x.report("map + compact") {N.times {enum.map { |i| i + 1 if i.even? }.compact}}
x.report("filter_map") {N.times {enum.filter_map { |i| i + 1 if i.even? }}}
end
#
# Rehearsal -------------------------------------------------
# select + map 8.569651 0.051319 8.620970 ( 8.632449)
# map + compact 7.392666 0.133964 7.526630 ( 7.538013)
# filter_map 6.923772 0.022314 6.946086 ( 6.956135)
# --------------------------------------- total: 23.093686sec
#
# user system total real
# select + map 8.550637 0.033190 8.583827 ( 8.597627)
# map + compact 7.263667 0.131180 7.394847 ( 7.405570)
# filter_map 6.761388 0.018223 6.779611 ( 6.790559)
As you can see, this is faster with about 10-20% speedup.