Ruby 2.7 adds Enumerable#filter_map


Ruby 2.7 adds Enumerable#filter_map.

filter_map is a short hand 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" ...]

Its 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 speed up 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.