Help us understand the problem. What is going on with this article?

配列で使えるメソッドの復習

More than 3 years have passed since last update.

はじめに

Rubyの勉強を始めて早3ヶ月。
これだけはRubyを使う上で覚えておけ、という配列関連のメソッドを教えていただいたので、簡単にまとめてみました。
イメージしやすいよう、下記のようなテーブルとレコードを使います。

ID name price description
1 Rubyの本 1000 Rubyの本です。
2 JavaScriptの本 1000 JavaScriptの本です。
3 PHPの本 1000 PHPの本です。
4 HTMLの本 2000 HTMLの本です。
5 CSSの本 2000 CSSの本です。

Enumerable#map (collect)

要素の数だけ繰り返しブロックを実行する。
collectmapの別名で、同様の動きをする。

items = Item.all
item_ids = items.map { |item| item.id }
# => [1, 2, 3, 4, 5]

上記のように各要素に対してメソッドを適用する場合は&を使って以下のようにも書くことができる。

item_ids = items.map(&:id)

Enumerable#select

ブロックを各要素に対し実行し、真となった要素を返す。

item_price_array = Item.all.map(&:price)
# => [1000, 1000, 1000, 2000, 2000]
only_price_1000 = item_price_array.select { |price| price == 1000 }
# => [1000, 1000, 1000]

上記では、mapを使用して1回値段のみの配列にしてしまったが、下記のようにすればprice = 1000だけ配列で取得できる。
(ちょっと例が悪かったため、whereで解決できてしまいますが。)

only_price_1000_items = Item.all.select { |item| item.price == 1000 }
# => [#<Item id: 1, name: "Rubyの本", price: 1000, description: "Rubyの本です。", created_at: "2016-10-02 05:32:52", updated_at: "2016-10-02 05:32:52">, #<Item id: 2, name: "JavaScriptの本", pricdescription: "JavaScriptの本です。", created_at: "2016-10-02 07:42:16", updated_at: "2016-10-02 07:42:16">, #<Item id: 3, name: "PHPの本", price: 1000, description: "PHPの本です。", created_at: 09:24:55", updated_at: "2016-10-02 09:24:55">]

なお、selectとは逆で、偽を返す場合はrejectを使用する。

not_price_1000_items = Item.all.reject { |item| item.price == 1000 }
# => [#<Item id: 4, name: "HTMLの本", price: 2000, description: "HTMLの本です。", created_at: "2016-10-02 09:25:12", updated_at: "2016-10-02 09:25:12">, #<Item id: 5, name: "CSSの本", price: 2000tion: "CSSの本です。", created_at: "2016-10-02 09:25:59", updated_at: "2016-10-02 09:25:59">]

Enumerable#reduce (inject)

injectreduceの別名で、同様の動きをする。
畳み込み演算を行うメソッドをし、その結果を返す。

item_price_array = Item.all.map(&:price)
# => [1000, 1000, 1000, 2000, 2000]
item_price_array.reduce(0) { |result, price|
  result + price
}
# => 7000

まず、reduce(0)にある0は、次に記載するresultの初期値になる。
ブロック内に記載のあるresult(1つ目のブロック引数)はブロックの戻り値が入る。
price(2つ目のブロック引数)は配列の各要素になる。

# 1週目
result = 0 (初期値), price = 1000
# 2週目
result = 1000, price = 1000
# 3週目
result = 2000, price = 1000
# 4週目
result = 3000, price = 2000
# 5週目
result = 5000, price = 2000
# 戻り値
=> 7000

なお、簡単な演算であれば下記のようにシンボルで記述することもできる。

item_price_array.reduce(:+)
=> 7000

初期値に配列を渡すこともできて、フィボナッチ数列などは下記のように書ける。
(引用:ちょっとわかりにくいけど非常に便利なinjectメソッド

(0..5).reduce([1, 1]) { |fib, i| fib << fib[i] + fib[i+1] }
# => [1, 1, 2, 3, 5, 8, 13, 21]

Enumerable#index_by

特定の値をキーとしたハッシュを生成する。

items_index_by_name = Item.all.index_by { |item| item.name }
# => => {"Rubyの本"=>#<Item id: 1, name: "Rubyの本", price: 1000, description: "Rubyの本です。", created_at: "2016-10-02 05:32:52", updated_at: "2016-10-02 05:32:52">, "JavaScriptの本"=>#<Item id: 2vaScriptの本", price: 1000, description: "JavaScriptの本です。", created_at: "2016-10-02 07:42:16", updated_at: "2016-10-02 07:42:16">, "PHPの本"=>#<Item id: 3, name: "PHPの本", price: 1000, de"PHPの本です。", created_at: "2016-10-02 09:24:55", updated_at: "2016-10-02 09:24:55">, "HTMLの本"=>#<Item id: 4, name: "HTMLの本", price: 2000, description: "HTMLの本です。", created_at: "20162", updated_at: "2016-10-02 09:25:12">, "CSSの本"=>#<Item id: 5, name: "CSSの本", price: 2000, description: "CSSの本です。", created_at: "2016-10-02 09:25:59", updated_at: "2016-10-02 09:25:59"

ここでも&を使用すると便利。

items_index_by_name = Item.all.index_by(&:name)

Enumerable#group_by

特定の値をキーとしてグルーピングした、新しいハッシュを生成する。

items_group_by_price = Item.all.group_by{ |item| item.price }
# => {1000=>[#<Item id: 1, name: "Rubyの本", price: 1000, description: "Rubyの本です。", created_at: "2016-10-02 05:32:52", updated_at: "2016-10-02 05:32:52">, #<Item id: 2, name: "JavaScriptの本 1000, description: "JavaScriptの本です。", created_at: "2016-10-02 07:42:16", updated_at: "2016-10-02 07:42:16">, #<Item id: 3, name: "PHPの本", price: 1000, description: "PHPの本です。", crea6-10-02 09:24:55", updated_at: "2016-10-02 09:24:55">], 
#     2000=>[#<Item id: 4, name: "HTMLの本", price: 2000, description: "HTMLの本です。", created_at: "2016-10-02 09:25:12", updated_at: "2016-1:25:12">, #<Item id: 5, name: "CSSの本", price: 2000, description: "CSSの本です。", created_at: "2016-10-02 09:25:59", updated_at: "2016-10-02 09:25:59">]}

ここでも&を使用すると便利。

items_group_by_price = Item.all.group_by(&:price)

Enumerable#partition

group_byと似たグルーピングのメソッドで、partitionがある。
これはブロック内の要素が真か偽かで新しい配列を生成する。

items_partition_with_price = Item.all.partition {|item| item.price == 1000 }
# => [
#       [#<Item id: 1, name: "Rubyの本", price: 1000, description: "Rubyの本です。", created_at: "2016-10-02 05:32:52", updated_at: "2016-10-02 05:32:52">, #<Item id: 2, name: "JavaScriptの本", pri description: "JavaScriptの本です。", created_at: "2016-10-02 07:42:16", updated_at: "2016-10-02 07:42:16">, #<Item id: 3, name: "PHPの本", price: 1000, description: "PHPの本です。", created_at2 09:24:55", updated_at: "2016-10-02 09:24:55">], 
#       [#<Item id: 4, name: "HTMLの本", price: 2000, description: "HTMLの本です。", created_at: "2016-10-02 09:25:12", updated_at: "2016-10-02 09:25:1Item id: 5, name: "CSSの本", price: 2000, description: "CSSの本です。", created_at: "2016-10-02 09:25:59", updated_at: "2016-10-02 09:25:59">]
#    ]

おわりに

簡単ではありますが、復習としてまとめました。
mapcollectのように、同一の処理なのにメソッド名が異なるのは、LispSmalltalkの影響のようです。
下記に詳しく記載されております。
map と collect、reduce と inject ―― 名前の違いに見る発想の違い

もし間違ている、もしくは他にもこんな使い方や便利なメソッドがある、といったことがありましたらぜひ教えてください!

naoki85
Web系のエンジニアです。 RubyやPHPを主に書いています。
https://scrapbox.io/naoki85/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away