Rubyの魅力。
「シンプルにかける」「フレームワーク優秀すぎる」
とか色々ありますよね。
ただ、個人的にはループ処理用のメソッドの多さも魅力の一つだと思っています。
初めてRubyを学んだ時、
「え、こんなにループ処理用のメソッドあるん?大体なんでもできるやん?」
と思いました。
せっかくなので、色々なメソッドを使ってみるのも良いかもですね。
ただメソッドが多すぎて、
「こんな時はどのメソッド使ったら良いの?」
というのがわかりにくいという難点もあります。
そこでこの記事では、「Ruby3.3公式リファレンスマニュアル:Enumerableモジュール」で紹介されている全60メソッドを目的別に分類して紹介していきます。
1. 繰り返し操作したい
1-1. インデックス付きで各要素に対してループ処理したい【each_with_index】
「ループ処理する際にインデックスをつけたいねん」
そんな時はeach_with_index
。
eachは各要素をループ処理するだけですが、each_with_indexでは通し番号をつけることができます。
['近本', '中野', '森下', '大山'].each_with_index {|name, index| puts "#{index}番#{name}"}
# => 0番近本
# => 1番中野
# => 2番森下
# => 3番大山
ただし、0以外の通し番号をつけたい場合は、each.with_index
を使用するか、each_with_index
内のブロックでindex + 1
する必要があります。
['近本', '中野', '森下', '大山'].each.with_index(1) {|name, index| puts "#{index}番#{name}"}
['近本', '中野', '森下', '大山'].each_with_index {|name, index| puts "#{index + 1}番#{name}"}
# => 1番近本
# => 2番中野
# => 3番森下
# => 4番大山
1-2. 各要素を指定したオブジェクトとともに操作したい【each_with_object】
「ループ処理の時、なんか特定のオブジェクトを一緒に渡したいねん」
そんな時に活躍するのがeach_with_object
です。
与えられたオブジェクトと要素をブロックに渡してループ処理を行い、最初に与えられたオブジェクトを返却します。
例えば、ハッシュを渡した上で配列をループ処理すると、以下のように配列をハッシュに変換することができます。
players = ['近本', '中野', '森下', '大山']
result = players.each_with_object({}) do |name, obj|
obj[name] = "#{players.index(name) + 1}番"
end
puts result
# => {"近本"=>"1番", "中野"=>"2番", "森下"=>"3番", "大山"=>"4番"}
1-3. 逆順に繰り返したい【reverse_each】
「each
を逆順に繰り返したいねん、でもわざわざsort
するのはめんどくさい」
要素を逆順に繰り返したい場合はreverse_each
を使うことができます。
sort
メソッドで並び替える必要がないので、意外と便利です。
['近本', '中野', '森下', '大山'].reverse_each do |name|
puts name
end
# => 大山
# => 森下
# => 中野
# => 近本
1-4. n個ずつ要素をまとめて処理したい【each_cons】
「先頭から指定個数グループ化したいねん」
そんな時はeach_cons
。
each_cons
は連続するn個の要素をグループ化して処理します。
each_cons
が要素を一つずつずらしながらグループ化するのが特徴です。
(1..6).each_cons(3) { |group| p group }
# => [1, 2, 3]
# => [2, 3, 4]
# => [3, 4, 5]
# => [4, 5, 6]
1-5. 指定したサイズごとに要素を分割して処理したい【each_slice】
「先頭から指定個数で要素を分割したいねん」
そんな時はeach_slice
。
each_slice
は要素を指定サイズごとに分割して処理します。
each_cons
は要素を一つずつずらしながらグループ化しますが、each_slice
は分割するだけです。
例えば要素数10の配列にeach_slice(4)
を指定すると、4・4・2で分割されます。
(1..10).each_slice(4) { |slice| p slice }
# => [1, 2, 3, 4]
# => [5, 6, 7, 8]
# => [9, 10]
1-6. 要素を展開せずにそのまま取得したい【each_entry】
「普通にeachでループすると、キーと値でバラバラになるの嫌やねんなあ」
そんな時はeach_slice
。
each_entry
はeach
と似ていますが、各要素をそのまま取得するためのメソッドです。
特に、通常のeach
では展開される要素をそのまま扱いたい場合に役立ちます。
hash
オブジェクトの場合、通常のeach
ではキーと値が分解されますが、each_entry
ではそのまま取得できます。
{ a: 1, b: 2 }.each_entry do |entry|
p entry
end
# => [:a, 1]
# => [:b, 2]
1-7. 繰り返しを続けたい【cycle】
「何回かループ処理自体継続して欲しいなあ」
そんな時はcycle
。
cycle
はループ処理自体を指定回数繰り返します。
[1, 2, 3].cycle(2) { |x| puts x }
# => 1
# => 2
# => 3
# => 1
# => 2
# => 3
2. 要素の選択・フィルタリングをしたい
2-1. 条件に一致する要素を1つ取得したい【find・detect】
「条件に合う要素見つけたいなあ」
そんな時はfind
・detect
。
find
・detect
メソッドは、最初に条件を満たす要素を取得します。
とりあえず、最初の1件を取得したい時によく使います。
[1, 2, 3, 4].find { |n| n > 2 }
# => 3
2-2. 条件に一致する要素を全部取得したい【select・filter・find_all】
「いやいや、1件じゃなくて全部取得したいねん」
そんな時はselect
・filter
・find_all
があります。
これらのメソッドは条件を満たす全ての要素を取得します。
[1, 2, 3, 4].select { |n| n.even? }
# => [2, 4]
2-3. 条件に一致する要素を変換して全部取得したい【filter_map】
「いやいや、取得するだけじゃなくて加工もしたいねん」
そんな時はfilter_map
です。
filter_map
は条件を満たす要素を変換した結果を取得します。
# 偶数を取得してさらに2倍にする
(1..10).filter_map { |n| n * 2 if n.even? }
# => [4, 8, 12, 16, 20]
2-4. 条件に一致しない要素を取得したい【reject】
「逆や、条件に一致しない要素が欲しいねん」
そんな時はreject
。
文字通り、条件を満たさない要素を取得します。
# 偶数ではない=奇数を取得
(1..10).reject { |n| n.even? }
# => [1, 3, 5, 7, 9]
2-5. 正規表現やクラス判定を活用して、条件に一致する複数の要素を取得したい【grep】
「絞り込む際に正規表現使いたいねん」
そんな時はgrep
。
grep
を使うと、条件に一致する要素を配列で返します。
主に正規表現やクラス判定に便利です。
result = ['apple', 'banana', 'cherry'].grep(/a/)
# => ["apple", "banana"]
result = [1, 'apple', :symbol].grep(String)
# => ["apple"]
ブロックを渡すことで、取得した要素を変換することも可能です。
result = ['apple', 'banana', 'cherry'].grep(/a/) { |word| word.upcase }
# => ["APPLE", "BANANA"]
2-6. 正規表現やクラス判定を活用して、条件に一致しない複数の要素を取得したい【grep_v】
「grepの逆のことしたいけど、いちいち正規表現で逆の条件を書くのめんどくさいねん」
そんな時はgrep_v
があります。
grep
とは逆で、条件に一致しない要素を配列で返します。
result = ['apple', 'banana', 'cherry'].grep_v(/a/)
# => ["cherry"]
条件に一致するものを除外する際に便利です。
2-7. 条件に一致する最初の要素のindexを取得したい【find_index】
条件に一致する最初の要素のindexを取得したい気分の時もあるかもしれませんね。
そんな時はfind_index
が大活躍です。
find_index
は条件を満たす最初の要素のインデックスを取得します。
# 25より大きい最初の要素のindexを取得
[10, 20, 30, 40].find_index { |n| n > 25 }
# => 2
2-8. 最初の1個を取得したい【first】
「とりあえず先頭の要素が欲しいねん」
そんな時は直感的にfirst
で配列や範囲の最初の要素を取得しましょう。
[10, 20, 30, 40].first
# => 10
ちなみに、引数に数値を渡すことで、指定した数だけ先頭から取得できます。
take
と同じですね。
(1..10).first(5)
# => [1, 2, 3, 4, 5]
2-9. 最初のn個を取得したい【take】
「たまにはfirst
以外でカッコよく先頭から要素取得したいねん」
そんな時はtake
。
先頭から指定した数の要素を取得します。
(1..10).take(3)
# => [1, 2, 3]
2-10. 最初のn個を除外したい【drop】
「逆や、先頭からいくつかだけ除外したいねん」
そんな時はdrop
を使って先頭から指定した数の要素を除外しましょう。
(1..10).drop(3)
# => [4, 5, 6, 7, 8, 9, 10]
2-11. 条件を満たす間だけ要素を取得したい【take_while】
「条件を満たす間だけ要素を全部取得したいねん。range
効かせたいねん」
そんな時はtake_while
。
条件が真の間だけ要素を取得します。
(1..10).take_while { |n| n < 5 }
# => [1, 2, 3, 4]
2-12. 条件を満たす間だけ要素を除外したい【drop_while】
「take_while
があるならきっとdrop_while
もあるんよね?」
もちろんあります。
drop_while
はtake_while
の逆で、条件が真の間だけ要素を除外します
(1..10).drop_while { |n| n < 5 }
# => [5, 6, 7, 8, 9, 10]
2-13. nilを除外したい【compact】
「nil
さんはいらんなあ」
そんな時はcompact
。
配列からnil
を除外できます。
[1, nil, 2, nil, 3].compact
# => [1, 2, 3]
2-14. 重複を除外したい【uniq】
「同じ要素は取り除きたいなあ」
そんな時はuniq
。
配列の要素から重複を取り除いてくれます。
[1, 2, 2, 3, 3, 3].uniq
# => [1, 2, 3]
3. 変換
3-1. 各要素を変換したい【map・collect】
「要素をそれぞれ加工したいなあ」
そんな時はmap
・collect
。
各要素を変換して新しい配列を返します。
(1..5).map { |n| n * 2 }
# => [2, 4, 6, 8, 10]
3-2. 各要素を変換してフラットな状態にしたい【flat_map・collect_concat】
「要素も加工したいし、多重配列もフラットにしたいなあ」
そんな時はfkat_map
・collect_concat
。
各要素を変換してフラットな配列を返します。
[[1, 2], [3, 4]].flat_map { |arr| arr.map { |n| n * 2 } }
# => [2, 4, 6, 8]
3-3. 配列に変換したい【to_a・entries】
「ハッシュやrange
オブジェクトを配列に変換したいなあ」
Enumerable
オブジェクトを配列に変換するにはto_a
・entries
メソッドを使用します。
(1..5).to_a
# => [1, 2, 3, 4, 5]
(1..5).entries
# => [1, 2, 3, 4, 5]
ハッシュのキーや値を配列に変換することも可能です。
{ a: 1, b: 2 }.to_a
# => [[:a, 1], [:b, 2]]
3-4. ハッシュに変換したい【to_h】
「キーと値でペアになってるからハッシュにしたいなあ」
配列やキー・値のペアを持つEnumerable
オブジェクトをハッシュに変換するにはto_h
メソッドを使用します。
[[:a, 1], [:b, 2]].to_h
# => {:a=>1, :b=>2}
4. 真偽確認
4-1. 指定した要素が含まれているか確認したい【include?・member?】
「この値、要素に含まれてるんかなあ?」
そんな確認をしたい時はinclude?
・member?
。
特定の要素が含まれているか確認することができます。
[1, 2, 3].include?(2)
# => true
[1, 2, 3].include?(4)
# => false
4-2. 全要素が条件を満たすか確認したい【all?】
「全部の要素、この条件満たしているんかなあ?」
そんな確認をしたいときはall?
。
すべての要素が条件を満たしているか確認することができます。
[2, 4, 6].all? { |n| n.even? }
# => true
[2, 4, 6, 7].all? { |n| n.even? }
# => false
なお、空のコレクションに対しては常にtrue
を返却するので注意が必要です。
[].all? { |n| n.even? }
# => true
4-3. 少なくとも一つの要素が条件を満たすか確認したい【any?】
「全部じゃなくてもええねん、一つでも条件満たしていたらええねん」
そんな確認をしたい時はany?
。
少なくとも1つの要素が条件を満たしているか確認できます。
[1, 2, 3].any? { |n| n.even? }
# => true
[1, 3, 5].any? { |n| n.even? }
# => false
なお、空のコレクションに対しては常にfalse
を返却します。
[].any? { |n| n.even? }
# => false
4-4. 条件を満たす要素が一つもないことを確認したい【none?】
「all?
の逆をしたいねん」
そんな時はnone?
。
すべての要素が条件を満たさない場合にtrue
を返します。
[1, 2, 3].none? { |n| n > 3 }
# => true
[1, 2, 5].none? { |n| n > 3 }
# => false
なお、all?
と同じく空のコレクションに対しては常にtrue
を返却するので注意が必要です。
[].none? { |n| n > 3 }
# => true
4-5. 条件を満たす要素が一つだけあることを確認したい【one?】
「1個だけやで、1個だけ条件を満たして欲しいんや」
そんな時はone?。
条件を満たす要素が1つだけの場合にtrue
を返します。
複数ある場合はちゃんとfalse
を返すのがany?
との違いです。
[1, 2, 3].one? { |n| n.odd? }
# => false
[1, 2, 4].one? { |n| n.odd? }
# => true
なお、空のコレクションに対しては常にfalse
を返却します。
[].one? { |n| n.odd? }
5. 要素のグループ化・分割
5-1. 評価結果に基づいてグループ化したい【group_by】
「特定の条件でグループ分けしたいなあ」
そんな時はgroup_by
。
評価結果をキーとして要素をグループ化することができます。
[1, 2, 3, 4, 5].group_by { |n| n.even? }
# => {false=>[1, 3, 5], true=>[2, 4]}
5-2. 評価結果が変化するごとにグループ化したい【chunk】
「要素を評価して、結果が変わるたびにグルーピングしたいなあ」
そんな時はchunk
。
要素を評価し、その結果が変わるたびにグループを作成できます。
[1, 2, 2, 3, 3, 4].chunk { |n| n.even? }.to_a
# => [[false, [1]], [true, [2, 2]], [false, [3, 3]], [true, [4]]]
5-3. 条件を満たす間、隣接する要素を同じグループにまとめたい【chunk_while】
「条件を満たしている間だけ、前後の要素とまとめたいなあ」
そんな時はchunk_while
。
指定した条件がtrue
である間、隣接する要素を同じグループにまとめてくれます。
[1, 2, 3, 1, 2, 1].chunk_while { |a, b| a < b }.to_a
# => [[1, 2, 3], [1, 2], [1]]
5-4. 条件が変化するタイミングで切り分けたい【slice_when】
「条件が変化するタイミングで要素を切り分けたいなあ」
そんな時はslice_when
。
条件が変わるタイミングで要素を分割してくれます。
[1, 2, 3, 1, 2, 1].slice_when { |a, b| a > b }.to_a
# => [[1, 2, 3], [1, 2], [1]]
5-5. 条件が真になる直後で分割したい【slice_after】
「条件が真になったらその直後で分割したいなあ」
そんな時はslice_after
。
条件がtrue
になる要素の直後で分割します。
[1, 2, 3, 4, 5].slice_after { |n| n.even? }.to_a
# => [[1, 2], [3, 4], [5]]
5-6. 条件が真になる直前で分割したい【slice_before】
「逆に条件が真になる直前で分割したいなあ」
そんな時はslice_before
。
条件がtrue
になる要素の直前で分割します。
[1, 2, 3, 4, 5].slice_before { |n| n.odd? }.to_a
# => [[1], [2, 3], [4, 5]]
5-7. 条件の一致有無で分割したい【partition】
「条件を満たすかどうかで分割したいねんなあ」
そんな時はpartition
。
指定した条件に一致する要素と一致しない要素を2つのグループに分けます。
group_by
とは違って、partition
はあくまでも条件に一致するかどうかの2パターンでしか分類できません。
[1, 2, 3, 4, 5].partition { |n| n.even? }
# => [[2, 4], [1, 3, 5]]
6. 並び替え
6-1. 要素を並び替えたい【sort】
「要素を順番に並び替えたいねん」
そんな時はsort
を使いましょう。
要素を昇順に並び替えることができます。
[5, 3, 1, 4, 2].sort
# => [1, 2, 3, 4, 5]
「いやいや、降順で頼みますわ」
という場合は、ブロックを渡します。
[5, 3, 1, 4, 2].sort { |a, b| b <=> a }
# => [5, 4, 3, 2, 1]
6-2. 指定した基準で並び替えたい【sort_by】
「単純に並び替えるんじゃなくて、指定した基準で並び替えたいねん」
そんな時はsort_by
です。
sort_by
を使うと、指定した基準で並び替えることができます。
ブロックで比較基準を指定します。
players = [
{ name: '近本', age: 30 },
{ name: '中野', age: 28 },
{ name: '森下', age: 24 },
{ name: '大山', age: 30 }
]
players.sort_by { |player| player[:age] }
# => [{:name=>"森下", :age=>24}, {:name=>"中野", :age=>28}, {:name=>"近本", :age=>30}, {:name=>"大山", :age=>30}]
7. 集計・計算
7-1. 要素の合計を計算したい【sum】
「要素内を合計したいなあ」
そんな時はsum
。
要素の合計値を計算できます。
(1..5).sum
# => 15
ブロック内には式を渡すこともできるので、各要素を2倍して合計するみたいなことも可能です。
(1..5).sum { |n| n * 2 }
# => 30
7-2. 要素の個数をカウントしたい【count】
「要素自体、何個あるか数えたいなあ」
そんな時は、count
で要素の個数をカウントすることができます。
[1, 2, 3, 4, 5].count
# => 5
また引数やブロックに条件を渡してあげると、その条件に合った要素の個数も取得してくれて便利です。
[1, 2, 3, 4, 5].count { |n| n.even? }
# => 2
7-3. 要素の出現回数をカウントしたい【tally】
「りんご、バナナ、みかん、それぞれ何個出てきたん?」
そんな要素の出現回数をカウントする時に便利なのがtally
メソッドです。
['apple', 'apple', 'banana', 'orange'].tally #=> {"apple"=>2, "banana"=>1, "orange"=>1}
ハッシュを使うことで結果を加算していくこともできます。
h = {}
[:apple, :banana, :orange].tally(h)
[:apple, :banana, :lemon].tally(h)
p h # => {:apple=>2, :banana=>2, :orange=>1, :lemon=>1}
7-4. 最大値を取得したい【max】
「シンプルに一番大きい値を知りたいねん」
そんな最大値を取得してくれるのがmax
メソッドです。
[1, 5, 3, 4, 2].max
# => 5
ただし、該当する要素が複数存在する場合、どの要素を返すかは不定なことに注意しましょう。
7-5. 特定の条件下で最大値を取得したい【max_by】
「特定の条件に絞って、最大値を取得したいねん」
そんなmax
に条件を追加したい場合はmax_by
。
指定した基準での最大値を取得できます。
players = [
{ name: '近本', age: 30 },
{ name: '中野', age: 28 },
{ name: '森下', age: 24 }
]
players.max_by { |player| player[:age] }
# => {:name=>"近本", :age=>30}
ただし、max
と同じく該当する要素が複数存在する場合、どの要素を返すかは不定なことに注意しましょう。
7-6. 最小値を取得したい【min】
「最大値じゃなくて最小値を取得したいねん」
そんな時はmax
の逆なのでmin
です。
要素の最小値を取得できます。
[1, 5, 3, 4, 2].min
# => 1
7-7. 特定の条件下で最小値を取得したい【min_by】
「ということは、特定の条件に絞って、最小値を取得するのはmin_by
?」
おっしゃる通りです。
min_by
は指定した基準での最小値を取得できます。
players = [
{ name: '近本', age: 30 },
{ name: '中野', age: 28 },
{ name: '森下', age: 24 }
]
players.min_by { |player| player[:age] }
# => {:name=>"森下", :age=>24}
7-8. 最小値と最大値を両方取得したい【minmax】
「最小値と最大値、どっちも取得する欲張りなことしたいねん」
minmax
なら最小値と最大値を同時に取得できます。
[1, 5, 3, 4, 2].minmax
# => [1, 5]
7-9. 特定の条件下で最小値と最大値を両方取得したい【minmax_by】
「今までの流れからすると、minmax
があればminmax_by
もあるんやろか?」
ありますね。
minmax_by
を使うと、指定した基準で最小値と最大値を同時に取得できます。
players = [
{ name: '近本', age: 30 },
{ name: '中野', age: 28 },
{ name: '森下', age: 24 }
]
players.minmax_by { |player| player[:age] }
# => [{:name=>"森下", :age=>24}, {:name=>"近本", :age=>30}]
7-10. 要素を累積して計算したい【inject・reduce】
「それぞれの要素について初期値を渡して累積計算したいなあ」
そんな時は、inject
やreduce
を使うと、要素を累積的に処理できます。
(1..5).inject(100) { |sum, n| sum + n }
# => 115
初期値を省略すると、最初の要素が初期値として使用されます。
(1..5).reduce(:+)
# => 15
8. その他の汎用操作
8-1. 複数のEnumerableを連結したい【chain】
「複数のEnumerableさん達を合体させたいなあ」
そんな時はchain
。
文字通り、複数のEnumerableを連結し、1つのEnumerableとして扱います。
連結した結果は遅延評価されるため、大量のデータを扱う際にも便利です。
enum1 = [1, 2, 3]
enum2 = [4, 5, 6]
enum3 = [7, 8, 9]
result = enum1.chain(enum2, enum3).to_a
# => [1, 2, 3, 4, 5, 6, 7, 8, 9]
8-2. 遅延評価でEnumerableを処理したい【lazy】
「この配列、要素多すぎてめっちゃ重たいやん。パフォーマンス改善したいわ」
そんな時はlazy
。
lazy
を使うと、遅延評価のEnumerableを生成できます。
大きなデータや無限リストの操作に適しています。
lazy_enum = (1..Float::INFINITY).lazy
result = lazy_enum.select { |n| n % 2 == 0 }.first(5)
# => [2, 4, 6, 8, 10]
この例では、無限リストから最初の5個の偶数を効率よく取得しています。
8-3. 行列変換したい【zip】
「対応する要素ごとに行列変換したいけど、そんな高度なことできるんかなあ」
Rubyは行列変換にも対応しています。
zip
を使うと、複数のEnumerable
を対応する要素ごとにまとめて配列を生成します。
行列変換や関連付けに役立ちます。
names = ['近本', '中野', '森下', '大山']
ages = [30, 28, 24, 30]
result = names.zip(ages)
# => [["近本", 30], ["中野", 28], ["森下", 24], ["大山", 30]]
ブロックを渡すことで、各要素を直接処理することも可能です。
names.zip(ages) { |name, age| puts "#{name}は#{age}歳です。" }
# => 近本は30歳です。
# => 中野は28歳です。
# => 森下は24歳です。
# => 大山は30歳です。
9. まとめ
Enumerable
全60メソッドを紹介しました。
たくさんありましたね。
全部覚えるのは難しいかもしれません。
でも、こんなことできるメソッドあるんだなとぼんやり知っているだけでもメソッドの幅が広がると思います!