Enumerable
って?
繰り返しを行うためのMix-in
Array
とか、Hash
はこれをMix-inしている
繰り返しを処理するときは、まずはこのリファレンスを読んで、使えそうなものは無いかな? と調べる習慣をつけると良いとおもいます
Enumerable
を扱う場合、初級者は#each
の中でぐたぐたコードを書いてしまうことが多いのですが、
#each
以外のメソッドを上手く使うことでもっとスッキリしたコードがかけますよ!
配列を処理した結果を他の配列に入れたい場合は、#map
を使おう
instance method Enumerable#collect (Ruby 2.4.0)
悪い例
# 配列の値を10倍した配列を取得する
result = []
[1,2,3].each do |i|
result << i*10
end
良い例
# 配列の値を10倍した配列を取得する
result = [1,2,3].map do |i|
i*10
end
状況に応じて配列に値を抽出するならば、#select
を使おう
instance method Enumerable#find_all (Ruby 2.4.0)
-
select
はblockの結果が真である要素を全て含む配列を返す - 逆に
reject
はblockの結果が偽である要素を全て含む配列を返す
悪い例
# 偶数を抽出する
result = []
[1,2,3].each do |i|
result << i if i % 2 == 0
end
良い例
# 偶数を抽出する
result = [1,2,3].select do |i|
i % 2 == 0
end
配列を使って処理した結果が欲しい場合は、#inject
を使おう
instance method Enumerable#inject (Ruby 2.4.0)
たとえば、配列の合計が欲しいときとか
-
#inject
は畳み込み演算を行うメソッド - 引数に初期値を入れる
- blockの戻り値が次の第二引数に、引き渡される
悪い例
# 配列の値を合計する
sum = 0
[1,2,3].each do |i|
sum += i
end
良い例
# 配列の値を合計する
[1,2,3].inject(0) do |i, sum|
sum + i
end
#inject
を使うためだけに、blockの戻り値を明示的にするくらいなら、#each_with_object
を使おう
instance method Enumerable#each_with_object (Ruby 2.4.0)
- injectは、各要素が力を合わせて一つのオブジェクトを作る
- each_with_objectは、ターゲットとなるオブジェクトに対して、各要素を作用させる
悪い例
# 配列からhashを作る
[[:a,1], [:b,2], [:c,3]].inject({}) do |hash, (k,v)|
hash[k] = v
hash
end
良い例
# 配列からhashを作る
# injectと、blockの引数の順が逆なので注意!
[[:a,1], [:b,2], [:c,3]].each_with_object({}) do |(k,v), hash|
hash[k] = v
end
パクリ元 : injectとeach_with_objectって何が違うのさ? - Qiita
indexが欲しい場合は、 #with_index
を使おう!
instance method Enumerator#with_index (Ruby 2.4.0)
-
#map
などの繰り返し処理の中で、indexが欲しいときとか -
#each_with_index
は今後放置されるとMatzが言っているらしい
悪い例
# 値とindexの積の配列を作る
result = []
[10, 10, 10].each_with_index do |v, i|
result << v*i
end
良い例
# 値とindexの積の配列を作る
[10, 10, 10].map.with_index do |v, i|
v*i
end
ちなみに、#with_index
は引数で何番目のindexから始めるか指定できるので便利
(ヘッダ情報を飛ばしたいときとか)
各要素に単一のメソッドを実行するだけの場合は&
記法を使おう
&:メソッド名
を繰り返しに渡すことで、そのメソッドが各要素に実行される
悪い例
# 奇数を抽出
[1,2,3].select do |v|
v.odd?
end
良い例
# 奇数を抽出
[1,2,3].select(&:odd?)
メソッドの引数に要素を渡したい場合にも使える
悪い例
# 全ての値をputsする
[1,2,3].each do |v|
puts v
end
良い例
# 全ての値をputsする
[1,2,3].each(&method(:puts))
くわしくはこちらを
処理元が配列or非配列かはっきりしない場合、配列にしてから#flatten
しよう
if文が不要になる
悪い例
# 合計値を返す関数
# ただし、引数に配列ではなく数値が来る場合がある。その場合はその値をそのまま返す
def sum(v)
if v.is_a?(Enumerable)
v.inject(&:+)
else
v
end
end
sum(100) # => 100
sum([100,200]) # => 100
良い例
# 合計値を返す関数
# ただし、引数に配列ではなく数値が来る場合がある。その場合はその値をそのまま返す
def sum(v)
[v].flatten.inject(&:+)
end
sum(100) # => 100
sum([100,200]) # => 300
オブジェクト指向的には、そもそもこんな風にv
のクラスに依存した実装にするのはあまり良くないかも...