Enumerableモジュールを機能(ほぼ)全部まとめ 〜 select, inject, take などなど

  • 18
    Like
  • 1
    Comment
More than 1 year has passed since last update.

はじめに

eachやmap(collection)だけ使っているとなかなかコードが洗練されない
そこでEnumerableモジュールを継承しているクラスArray、Hash等で使えるメソッドを例示して、
同じ処理をeachで書いた場合の例をも書いて便利さを確認していきたい

あまりにコレは使いどころがないなぁと言うメソッドは省いている(#zipとか)
こういうところで使うよと言うのがあればコメントや編集依頼をいただけると助かる
また, #lazyはまた毛色が違うので省いている。lazyの実作業での使いどころがいまいちわからない

all?

すべての要素がブロック内の条件式を満たしていればtrueを返す

使い方

[1,2,3].all? { |v| v > 0 }
=> true

[1,2,3].all? { |v| v > 1 }
=> false

eachで書いた場合

flg = true
[1,2,3].each do |v|
  if v <= 0
    flg = false
    break
  end
end
p flg
=> true

any?

すべての要素のうち一つでもブロック内の条件式を満たせばtrueを返す

使い方

[1,2,3].any? { |v| v == 2 }
=> true

eachで書いた場合

flg = false
[1,2,3].each do |v|
  if v == 2
    flg = true
    break
  end
end
p flg
=> true

collect, map

すべての要素をブロック内で評価した配列を返す
collectとmapは同じ動きをする
mapの方がよく使われる気がする

eachじゃなくてmapを使いこなせるとRubyを使ってる感が出てくるよね

使い方

# 各項を2倍にした配列を返す
[1,2,3].collect { |v| v * 2 }
=> [2, 4, 6]

[1,2,3].map { |v| v * 2 }
=> [2, 4, 6]

eachで書いた場合

result = []
[1,2,3].each do |v|
  result << v * 2
end
p result
=> [2, 4, 6]

collect_concat, flat_map

すべての要素をブロック内で評価した配列を展開して返す
collect_concatとflat_mapは同じ動きをする
map と flatten組み合わせた動きをする

下記の例のようにmap + flattenの方が可読性が高い気もする

使い方

# 各項を2倍にした配列を返す
[[1, 2], [3, 4]].flat_map { |v| v.map { |cv| cv * 2 } } 
=> [2, 4, 6, 8]

#map と flattenを組み合わせた場合
[[1, 2], [3, 4]].map { |v| v.map { |cv| cv * 2 } }.flatten
=> [2, 4, 6, 8]

eachで書いた場合

result = []
[[1, 2], [3, 4]].each do |v|
  v.each do |cv|
    result << cv * 2  
  end
end
p result
=> [2, 4, 6, 8]

count

すべての要素をブロック内で評価して真の物の数を返す
ブロックがない場合はsizeやlengthと同じ要素数を返す

使い方

[1,2,3].count
=> 3 #要素数

#値が3のもの数
[1,2,3].count(3)
=> 1 

# 文字列でも大丈夫
["1","2","3"].count("1")
=> 1 

# 奇数の数を返す
[1,2,3].count{ |v| v.odd? }
=> 2

# selectで絞り込んでから配列のサイズを数えるとこうも書ける
[1,2,3].select{ |v| v.odd? }.size

eachで書いた場合

result = 0
[1,2,3].each do |v|
  result += 1 if v.odd?
end
p result
=> 2

detect, find

すべての要素をブロック内で評価して真になった最初の要素を返し
それ以降の配列の要素を評価しない

ある条件の要素があるかどうかチェックするany?や
ある条件に合致する要素すべてを取りだすselectの方が使う場面が多い気もする

detectよりfindの方がよく使うかな

使い方

# 配列の中から3~5の値の最初の要素を返す
[1,3,7].detect { |v| (3..5).include?(v) } 
=> 3 

[1,3,7].find { |v| (3..5).include?(v) } 
=> 3 

eachで書いた場合

result = nil
[1,3,7].each do |v|
  if (3..5).include?(v)
    result = v
    break
  end
end
p result
=> 3 

drop

先頭から引数のInteger数の要素を捨てて、 残りの要素を配列で返す
引数が配列のサイズより大きい時でもエラーにならずに空の配列を返してくれるのが嬉しい

使い方

[1,2,3,4].drop(2)
=> [3.4]

# 配列のsizeより大きくてもエラーにならない
[1,2,3,4].drop(5)
=> []


eachで書いた場合

result = []
drop_num = 2
i = 0
[1,2,3,4].each do |v|
  i += 1
  next if i <= drop_num
  result << v
end
p result
=> [3.4]

# each_with_indexで配列のindex番号をつけても書ける、当然dropを使えばもっと楽
result = []
drop_num = 2
[1,2,3,4].each_with_index do |v, i|
  next if i < drop_num
  result << v
end
p result
=> [3.4]

take

先頭から引数のInteger数の要素を取り出してその配列で返す
引数が配列のサイズより大きい時でもエラーにならずに空の配列を返してくれるのが嬉しい

dropとtakeは対になっている機能なので合わせて覚えておくと良い

使い方

[1,2,3,4].take(2)
=> [1.2]

# 配列のsizeより大きくてもエラーにならない
[1,2,3,4].take(5)
=> [1, 2, 3, 4]


eachで書いた場合

result = []
take_num = 2
i = 0
[1,2,3,4].each do |v|
  i += 1
  break if i > take_num
  result << v
end
p result
=> [1.2]

# each_with_indexで配列のindex番号をつけても書ける、当然takeを使えばもっと楽
result = []
take_num = 2
[1,2,3,4].each_with_index do |v, i|
  break if i >= take_num
  result << v
end
p result
=> [1.2]

drop_while

ブロック内の条件にヒットしたらそれ以前の要素を捨てて、残りの要素を配列で返す
ソート済みの配列で、ある条件より小さいものを捨てると言う場合、rejectと違ってすべて評価しないのでよいかも

使い方

[1,2,3,4].drop_while { |v|  v < 3 }
=> [3, 4]

# こちらだと4も評価している 
[1,2,3,4].reject { |v|  v < 3 }
=> [3, 4]

eachで書いた場合

# だんだんつらくなってきた
result = []
drop_num = 2
i = 0
ary =[1,2,3,4]
ary.each do |v|
  i += 1
  next if i <= drop_num
  result = ary[(i - 1)..-1]
  break
end
p result
=> [3, 4]

take_while

ブロック内の条件にヒットしなくなったら(偽を返したら)以降の要素を捨てて、残りの要素を配列で返す
drop_whileとでソート済みの配列の場合だと、ある条件より小さいものを捨てると言う場合、selectと違ってすべて評価しないのでよいかも

使い方

[1,2,3,4].take_while { |v| v < 3 }
=> [1, 2]

# こちらだと4も評価している
[1,2,3,4].select { |v| v < 3 }
=> [1, 2]

eachで書いた場合

result = []
take_num = 2
i = 0
[1,2,3,4].each do |v|
  i += 1
  break if i > take_num
  result << v
end
p result
=> [1, 2]

each_with_index

要素とその何番目かを表す添字のインデックスをブロックに渡して処理をする
each_with_indexよりmap.with_indexを使って添字を使った配列を返すほうが使う気がする

使い方

ary = []
[1,9,5,2].each_with_index { |v, i|  ary << [v, i] }
p ary
=> [[1, 0], [9, 1], [5, 2], [2, 3]]


# map.with_indexを使うとこんな感じでかける
[1,9,5,2].map.with_index { |v, i| [v, i] }
=> [[1, 0], [9, 1], [5, 2], [2, 3]]

eachで書いた場合

result = []
i = 0
[1,9,5,2].each do |v|
  result << [v, i]
  i += 1
end
p result
=> [[1, 0], [9, 1], [5, 2], [2, 3]]

to_a, entries

すべての要素を含む配列を返す(って説明を見るけどいまいちピンとこない)
hashを [key, value] の組み合わせの配列を返すときに使ったりするかな。

Railsの場合はActiveRecordのオブジェクトをキャッシュで格納するときに使ったりするかな

User.where(state: "alive").to_a

使い方

{ a: 1, b: 2, c:3 }.entries
=> [[:a, 1], [:b, 2], [:c, 3]]


{ a: 1, b: 2, c:3 }.to_a
=> [[:a, 1], [:b, 2], [:c, 3]]

eachで書いた場合

result = []
{ a: 1, b: 2, c:3 }.each do |v|
  result << [v.first.to_s, v.last]
end
p result
=> [[:a, 1], [:b, 2], [:c, 3]]

find_all, select

各要素をブロック内で評価して真になったものだけの配列を返す。全部偽だった場合は空の配列を返す

select + mapをつなげて使うことがままあるかな

使い方

[1, 2, 3, 4].find_all { |v| v.even? }
=> [2, 4]


# こうとも書ける
[1, 2, 3, 4].select(&:even?)
=> [2, 4]

eachで書いた場合

result = []
i = 0
[1, 2, 3, 4].each do |v|
  result << v if v.even?
end
p result
=> [2, 4]

reject

各要素をブロック内で評価して偽になったものだけの配列を返す。全部真だった場合は空の配列を返す
selectの逆と考えれば良い、ブロック内でヒットした条件のものを削除したい場合に使う

使い方

[1, 2, 3, 4].reject { |v| v.even? }
=> [1, 3]


# こうとも書ける 書けるけどパフォーマンスは悪い
ary = [1,2,3,4]
ary - ary.select{ |v| v.even? }
=> [1, 3]

eachで書いた場合

result = []
i = 0
[1, 2, 3, 4].each do |v|
  result << v if v.even?
end
p result
=> [2, 4]

partition

各要素をブロック内で評価して真と偽になった2つの配列を返す
selectとrejectを実行したペアと考えればよい

使い方

[1, 2, 3, 4].partition { |v| v.even? }
=> [[2, 4], [1, 3]]

eachで書いた場合

result = [[], []]
[1, 2, 3, 4].each do |v|
  if v.even?
    result[0] << v
  else
    result[1] << v
  end
end
p result
=> [[2, 4], [1, 3]]

find_index, index(Arrayの場合)

各要素をブロック内で評価して最初に真になったものの添字(何番目か)を返す。全部偽だった場合はnil
Arrayの場合はindexメソッドが別名である。主にindexがある

使い方

[1, 2, 3, 4].find_index { |v| v.even? }
=> 1

# こうとも書ける
[1, 2, 3, 4].index { |v| v.even? }
=> 1

# Hashの場合に使う意味がたぶんないけどこう書ける
{a: 1, b: 2}.find_index { |v| v.last.even? }
=> 1

eachで書いた場合

result = nil
index = 0
[1, 2, 3, 4].each do |v|
  if v.even?
    result = index
    break
  end
  index += 1
end
p result
=> 1

first, last(Arrayの場合)

要素の最初の要素または、先頭から引数の数字個要素を取得する
Arrayの場合は後ろから取得する lastもある

空の配列の
引数なしの場合はnil、引数ありの場合はからの配列を返す動きをする
引数ありの場合はtakeメソッドを使ったほうがわかりやすい気もする

使い方

[1, 2, 3, 4].first
=> 1

# 引数があると
[1, 2, 3, 4].first(2)
=> [1, 2]

# lastの場合
[1, 2, 3, 4].last(2)
=> [3, 4]

eachで書いた場合

# 引数なしのfirstはeachは使わない
result = [1, 2, 3, 4][0]
p result
=> 1

# [1, 2, 3, 4].first(2)をeachを使って書くと
result = []
index = 0
num = 2
[1, 2, 3, 4].each do |v|
  unless index < num
    break
  end
  result << v
  index += 1

end
p result

grep

引数のpatternにマッチするすべての要素の配列を返す。
patternには===メソッドがあるものが必要。
具体的にはRegexpオブジェクトをつかって正規表現にマッチするものを抽出するのに使う

ブロックをつけると、マッチした配列の要素それぞれにブロックの要素を評価した結果を格納した配列を返す
Arrayの中身が単純なIntegerやStringじゃないと機能しないかな、他のクラスで上手く使いたかったらたぶん#===メソッドを拡張しておく必要がありそう。小難しいことするならselectメソッドを使ったほうが楽だろう

map + matchをまとめて使うイメージ

使い方

# ブロックなしの場合
["1", "2", "3"].grep(/2/)
=> ["2"]

# grepを使わないで書くとこんな感じ
["1", "2", "3"].map{ |v| v if v.match(/2/) }.compact
=> ["2"]

# ブロックありの場合 マッチした"2"を数字にしてから2倍する
["1", "2", "3"].grep(/2/){ |v| v.to_i * 2 }
=> [4]

eachで書いた場合

# マッチした"2"を数字にしてから2倍する
result = []
["1", "2", "3", "4"].each do |v|
  if v.match(/2/)
    result << v.to_i * 2
  end
end
p result
=> [4]

member?, include?

各要素が、引数と==の評価値を返す。要するに含まれていればtrue、含まれていなければfalseを返す

RailsのActiveSupportの機能にinclude?の逆のようなin?メソッドがある

使い方

[1, 2, 3].include?(2)
=> true
[1, 2, 3].member?(2)
=> true

# Railsのin?メソッドはinclude?の逆のイメージ
2.in?([1, 2, 3])
=> true

eachで書いた場合

result = false
num = 2
[1, 2, 3].each do |v|
  if v == num
    result = true
    break
  end
end
p result
=> true

inject, reduce

各ブロックの結果を順次格納した値を返す、引数は初期値となる引数を省略すると1つ目の実行結果が初期値となる
引数にSymbolオブジェクトを渡すと、各要素のSymbolで表されたメソッドを実行する :+を渡すと各要素の+メソッドを実行したっけかを返す

このメソッドは個人的に好き、一発でかけると気持ちいい

使い方

# 初期値100で各要素の和
[1, 2, 3].inject(100) { |sum, v| sum + v}
=> 106

# こうも書ける
[1, 2, 3].reduce(100) { |sum, v| sum + v}
=> 106

# 引数を省略した場合
[1, 2, 3].inject{ |sum, v| sum + v}
=> 6

# Symbolで渡してやるこうも書ける
[1, 2, 3].inject(:+)
=> 6

# サンプルのデータを作りたいときべんり
# 1〜3の値の配列をランダムにしたものとか
(1..3).inject([]){ |sum, v| sum << v}.shuffle
=> [1, 2, 3]

eachで書いた場合

sum = 0
[1, 2, 3].each do |v|
  sum += v
end
p sum
=> 6

max, max_by

各要素の最大値を返す、ブロックを渡さない場合は <=> メソッドで比較した結果を返す。
ブロックを渡す場合は、max_byの方がO(n)で実行できるらしい(この辺の説明はここを読むと良い)

使い方

[1, 2, 3].max
=> 3

# 比較条件がややこしい場合はこちら
["A", "bb", "ccc"].max_by { |a | a.length }
=> "ccc"

eachで書いた場合

ary = [1, 2, 3]
max = ary.first

ary.each do |v|
  max = v if max < v
end
p max
=> 3

min, min_by

各要素の最小値を返す、ブロックを渡さない場合は <=> メソッドで比較した結果を返す。
ブロックを渡す場合は、min_byもmax_byと同様にO(n)で実行できるらしい(この辺の説明はここを読むと良い)

使い方

[1, 2, 3].min
=> 1

# 比較条件がややこしい場合はこちら
["A", "bb", "ccc"].min_by { |a | a.length }
=> "A"

eachで書いた場合

ary = [1, 2, 3]
min = ary.first

ary.each do |v|
  min = v if min > v
end
p min
=> 1

sort, sort_by

各要素の並び替えた値を返す、ブロックを渡さない場合は <=> メソッドで比較した結果を返す。
ブロックを渡す場合は、sort_byもmax_byと同様にO(n)で実行できるらしい(この辺の説明はここを読むと良い)

使い方

[3, 2, 1].sort
=> [1, 2, 3]

# 比較条件がややこしい場合はこちら
["aaa", "b", "cccc"].sort_by { |a | a.length }
=> ["b", "aaa", "cccc"]

whileで書いた場合

ary = [3, 2, 1]
i = 0

while i < ary.length
  j = 1
  while j < ary.length - i
    if ary[i] > ary[i+j]
      ary[i], ary[i+j] = ary[i+j], ary[i]
    end
    j += 1
  end
  i += 1
end
p ary
=> [1, 2, 3]

reverse_each

逆順に各要素に対してブロックを評価する
使いどころはArray#rindexを他のEnumerableモジュールを適用しているものに実装する場合とかかな

使い方

[1, 2, 3].reverse_each { |v| p v}
3
2
1
=> [1, 2, 3]

eachで書いた場合

[1, 2, 3].reverse.each do |v|
 p v
end
3
2
1
=> [3, 2, 1]

使いどころがよくわからん

zipとか、group_byとか

refs:
http://ruby-doc.org/core-2.3.0/Enumerable.html
https://repl.it/languages/ruby