目次
map(collect)について
mapは各要素を変換して新たな配列を作成するメソッドです。collect
はmap
のエイリアス(等価のメソッド)です。
オブジェクトのプロパティや配列の要素から新たな配列を作成したい場合にmap
が活躍します。
> values = [1, 2, 3]
### 各要素を加工して新たな配列を作成
> values.map { |value| value * 2 }
=> [2, 4, 6]
map
を利用することでオブジェクトを要素にもつ配列も作成できます。具体例は以下の通りです。
> user_names = ["user_1", "user_2", "user_3"]
> users = user_names.map { |name| { name: name } }
=> [{:name=>"user_1"}, {:name=>"user_2"}, {:name=>"user_3"}]
### Arrayの要素としてオブジェクトがセットされている
> users[0][:name]
=> "user_1"
『&:』による省略記法について
map { |x| x.メソッド名 }
あるいはmap { |x| x.属性名 }
は&:
を利用した省略記法ができます。具体例は以下の通りです。
> values = [1, 2, 3]
### &:による省略記法
> values.map(&:to_s)
=> ["1", "2", "3"]
### 省略しない場合
> values.map { |value| value.to_s }
=> ["1", "2", "3"]
&:
を利用することで例えば以下のようにモデルの属性参照が簡潔に記述できます。
users = []
3.times do |i|
users << User.new(name: "user_#{i + 1}")
end
> users.map(&:name)
=> ["user_1", "user_2", "user_3"]
ハッシュに対してmap(collect)を実行する方法
Hashに対してmap
を実行する場合はブロックで利用する変数を2つ用意します。
例えばmap { |key, value| }
とすることでキー名がkey
、値がvalue
の変数にセットされます。具体例は以下の通りです。
> fruite_prices = { banana: 100, orange: 130, apple: 100 }
> fruites = fruite_prices.map { |key, value| { name: key.to_s, price: value } }
=> [{:name=>"banana", :price=>100}, {:name=>"orange", :price=>130}, {:name=>"apple", :price=>100}]
mapの戻り値はArrayなので注意
ハッシュに対してmap
を実行した場合もmap
はハッシュではなくArrayを返します。
map
の結果をハッシュに変換するにはto_h
あるいはHash[]を利用します。具体例は以下の通りです。
> fruite_prices = { banana: 100, orange: 130, apple: 100 }
### mapの結果はArray
> fruite_prices_with_tax = fruite_prices.map { |key, value| [key, (value * 1.1).to_i] }
=> [[:banana, 110], [:orange, 143], [:apple, 110]]
### to_hを利用してハッシュに変換
> fruite_prices_with_tax.to_h
=> {:banana=>110, :orange=>143, :apple=>110}
### Hash[]を利用してハッシュに変換
> Hash[fruite_prices_with_tax]
=> {:banana=>110, :orange=>143, :apple=>110}
要素の値のみを変換したい場合はtransform_valueを利用するとよい
ハッシュの各要素の値のみを変換したい場合はmap
ではなくtransform_valueを利用するとよいです。具体例は以下の通りです。
> fruite_prices = { banana: 100, orange: 130, apple: 100}
=> {:banana=>100, :orange=>130, :apple=>100}
> fruite_prices_with_tax = fruite_prices.transform_values { |value| (value * 1.1).to_i }
=> {:banana=>110, :orange=>143, :apple=>110}
特定の条件を満たした要素のみmapする場合
今回は2つの方法を紹介します。
- if文 + compact
- filter(select)してからmap
if文 + compact
map
のブロック内に条件文を追加して要素をフィルタリングする方法です。map
の結果にnil
の要素が含まれるためcompact
でnil
を除外します。具体例は以下の通りです。
users = []
3.times do |i|
users << User.new(name: "user_#{i + 1}", is_active: "#{i.even?}")
end
### is_activeがtrueなnameを取り出す
> users.map { |user| user.name if user.is_active? }.compact
=> ["user_1", "user_3"]
## 参考: compactを利用しない場合
> users.map { |user| user.name if user.is_active? }
=> ["user_1", nil, "user_3"]
filter(select)してからmap
filterを利用することで条件を満たさない要素を配列から取り除けます。select
はfilter
のエイリアス(等価のメソッド)です。
filter
の例は以下の通りです。
> values = [1, 2, 3]
> values.filter { |value| value if value.even? }
=> [2]
filter
とmap
を組み合わせることで特定の条件を満たした要素をmap
できます。具体例は以下の通りです。
users = []
3.times do |i|
users << User.new(name: "user_#{i + 1}", is_active: "#{i.even?}")
end
> users.filter { |user| user.is_active? }.map(&:name)
=> ["user_1", "user_3"]
なお、主キーのように要素が一意に定まる条件で検索をする場合はfilter
の代わりにfind
を利用するとよいです。
filter
とfind
の違いの詳細解説についてはRubyのfilter(select)とfind(detect)の違いで紹介しています。
filter_mapは『filter + map』を一括で実行するメソッド
filter_mapを利用するとfilter
とmap
を一括で実行できます。つまり、特定の条件を満たす要素にのみmapを実行するメソッドがfilter_map
です。filter_map
の具体例は以下の通りです。
> (1..10).filter_map { |i| i * 2 if i.even? }
=> [4, 8, 12, 16, 20]
『filter
+ map
』をfilter_map
で書き換えた例は以下の通りです。
users = []
3.times do |i|
users << User.new(name: "user_#{i + 1}", is_active: "#{i.even?}")
end
### filter + mapで実装した場合
> users.filter { |user| user.is_active? }.map(&:name)
=> ["user_1", "user_3"]
### filter_mapで書き換えた場合
> users.filter_map { |user| user.name if user.is_active? }
flat_map(collect_concat)は『map + flatten』を一括で実行するメソッド
flat_mapを利用するとmap
とflatten
を一括で実行できます。flatten
とは多次元配列を1次元配列にするメソッドです。
collect_concat
はflat_map
のエイリアス(等価のメソッド)です。
『map
+ flatten
』をflat_map
に書き換えた例は以下の通りです。
> values = [1, 3, 5]
### map + flattenで実装した場合
> values.map { |value| (value..(value + 1)).to_a }.flatten
=> [1, 2, 3, 4, 5, 6]
### flat_mapで書き換えた場合
> values.flat_map { |value| (value..(value + 1)).to_a }
=> [1, 2, 3, 4, 5, 6]
### 参考: mapのみを利用した場合の結果
> values.map { |value| (value..(value + 1)).to_a }
=> [[1, 2], [3, 4], [5, 6]]
flat_mapのflattenはflatten(1)相当なので注意
flatten
が多次元配列を1次元配列に変換するのに対し、flat_map
はn次元配列を(n-1)次元配列にします。つまり、flat_map
による次元削除はflatten(1)
(配列を1次元減らす)に相当するので注意が必要です。
多次元配列を確実に1次元に変換したい場合はflat_mapではなくflattenを利用するとよいです。
以下にflat_map
とflatten
の次元削除の例を紹介します。
### mapを利用して3次元配列になる例
> values = [1, 2, 3]
> values.map { |value| [[value]] }
=> [[[1]], [[2]], [[3]]]
### flat_mapだと2次元配列になる
> values.flat_map { |value| [[value]] }
=> [[1], [2], [3]]
### flat_mapのflattenはflatten(1)と等価
> values.map { |value| [[value]] }.flatten(1)
=> [[1], [2], [3]]
### flattenだと1次元配列になる
> values.map { |value| [[value]] }.flatten
=> [1, 2, 3]
さいごに
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!