🩰

【どちらを使うべき?】Ruby2.7から登場したfilter_mapと、map/filterの違いを丁寧に解説

2023/05/31に公開

はじめに

ruby2.7から filter_mapメソッド が利用できるようになりました。
Rubocopでもruby2.7以上で Performance/MapCompact が適用されています。
本記事では、 filter_mapmap / filter の違いを徹底解説します。

TL;DR - 表でまとめると

本記事では「(配列の)要素」「メソッド」「ブロック」「(ブロックの)結果」「返り値」という単語を以下のように定義し、使用します。

[要素1, 要素2, 要素3,,,].メソッド { ブロック }
# ブロックが最後に評価した値を「(ブロックの)結果」と呼ぶ
=> 返り値

filter / map / filter_map の違いを表でまとめると

filter map filter_map
返り値 結果が真のみの要素の配列 結果の配列 ※結果の真偽は問わない 結果が真のみの結果の配列

具体例

filter: 結果が真のみの要素の配列を返す

[1, 2, 3, 4, 5].filter { |i| i.even? }
=> [2, 4] # 要素が偶数なのは2と4だけ
[1, 2, 3, 4, 5].filter do |i|
  double = i*2
  double.even?
end
=> [1, 2, 3, 4, 5] # 要素の2倍が偶数なのは 1~5 のすべて

map: 結果の配列を返す ※結果の真偽は問わない

[1 ,2, 3, 4, 5].map { |i| i*2 }
=> [2, 4, 6, 8, 10] # 要素を2倍した結果を全て返す
[1, 2, 3, 4, 5].map { |i| i.even? }
=> [false, true, false, true, false] # 要素が偶数かの評価結果を全て返す

ここで問題です

以下の返り値を説明できますか?

問題1

[1, 2, 3, 4, 5].map do |i|
  if i.even?
    i*2
  else
    '偶数じゃない'
  end
end
=> ["偶数じゃない", 4, "偶数じゃない", 8, "偶数じゃない"]

問題1の答え

問題1の答えはこちら

【要素の2,4】
2,4は偶数であるため、if文の結果がtrueになる

if節を評価した結果はそれぞれ4,8になる

そのまま返り値になる


【要素の1,3,5】
1,3,5は偶数でないため、if文の結果がfalseになり、else節に入る

else節の結果は "偶数じゃない" になる

そのまま返り値になる

問題2

[1, 2, 3, 4, 5].map do |i|
  if i.even?
    i*2
  end
end
=> [nil, 4, nil, 8, nil]

[1, 2, 3, 4, 5].map { |i| i*2 if i.even? }
=> [nil, 4, nil, 8, nil]

答えの前にRubyの前提知識

if文の返り値

if式は、条件が成立した節(あるいは else 節)の最後に評価した式の結果を返します。 else 節がなくいずれの条件も成り立たなければ nil を返します。

後置ifの返り値

右辺の条件が成立する時に、左辺の式を評価してその結果を返します。 条件が成立しなければ nil を返します。

参考:Ruby リファレンスマニュアル>制御構造

問題2の答え

問題2の答えはこちら

【要素の2,4】
2,4は偶数であるため、if文の結果がtrueになり、条件が成立する

条件が成立したので、if節を評価し、結果はそれぞれ4,8になる

そのまま返り値になる


【要素の1,3,5】
1,3,5は偶数でないため、if文の結果がfalseになり、条件が成立しない

条件が成立しなかったので、結果はnilになる

そのまま返り値になる


本題の前にもうひとつRubyの前提知識

false は nil オブジェクトとともに偽を表し、その他の全てのオブジェクトは真です。

参考:Ruby リファレンス>FalseClass

やっと本題

filter_map: 結果が真のみの結果の配列を返す

[1, 2, 3, 4, 5].filter_map { |i| i*2 }
=> [2, 4, 6, 8, 10] # 要素を2倍した結果は全て真(すなわちfalseでもnilでもない)

[1, 2, 3, 4, 5].filter_map { |i| i.even? }
=> [true, true] # 偶数でない要素の結果はfalseとなり、falseは偽なので返り値から除かれる

問題3

以下の返り値を説明してください。

[1, 2, 3, 4, 5].filter_map { |i| i*2 if i.even? }
=> [4, 8]

問題3の答え

問題3の答えはこちら

【要素の2,4】
2,4は偶数であるため、後置if文の結果がtrueになり、条件が成立する

条件が成立したので、左辺の式を評価し、結果はそれぞれ4,8になる

4と8は真なので、返り値になる


【要素の1,3,5】
1,3,5は偶数でないため、後置if文の結果がfalseになり、条件が成立しない

条件が成立しなかったので、結果はnilになる

nilは偽なので、返り値から除かれる

最終問題

以下の返り値を説明してください。

[1, 2, 3, 4, 5].filter_map do |i|
  if i.even?
    i*2
  else
    '偶数じゃない'
  end
end
=> ["偶数じゃない", 4, "偶数じゃない", 8, "偶数じゃない"]

最終問題の答え

最終問題の答えはこちら

【要素の2,4】
2,4は偶数であるため、if文の結果がtrueになる

if節を評価した結果はそれぞれ4,8になる

4と8は真なので、返り値になる


【要素の1,3,5】
1,3,5は偶数でないため、if文の結果がfalseになり、else節に入る

else節の結果は "偶数じゃない" になる

"偶数じゃない" は真なので、返り値になる

さいごに

最後まで読んでいただきありがとうございました!
宜しければぜひフォローお願いします!

https://twitter.com/itsmedachan

https://www.instagram.com/itsmedachan/

Rubyってたのしいな💕

Discussion