はじめに
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