LoginSignup
32

More than 5 years have passed since last update.

【初級者向け】rubyで効率のよいコードの書き方例いろいろ【Enumerable編】

Last updated at Posted at 2017-05-20

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)

悪い例

# 値と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のクラスに依存した実装にするのはあまり良くないかも...

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32