大ちゃんの駆け出し技術ブログ

RUNTEQ受講生のかわいいといわれるアウトプットブログ

【Ruby】eachの付くArrayクラスのインスタンスメソッド

はじめに

みなさんこんにちは。大ちゃんの駆け出し技術ブログです。

先日の記事ではArrayクラスのインスタンスメソッドを中心に記事を出しましたが、記事では書かなかったeachの付くメソッドがいくつもあったので本記事ではそれを紹介します。

メソッドは英単語と同じで知っているか知らないかで変わってくるので、覚えておいて全く損はないなと思います。

each

言わずと知れたRubyの繰り返し処理で頻出するメソッド。元の配列の各要素を評価するメソッドですね。

[1, 2, 3].each do |i|
  puts i
end
#=> 1
#   2
#   3

基本的には他のeachが付くメソッドはこのeachメソッドがベースとなっています。

ちなみにこのeachメソッドを使う際は他の方法で再現可能かどうかを考えるべきだそうです。eachの処理は繰り返し処理の基本の処理であり、複雑な処理をする場合は他のメソッドで再現可能担っていることが多いそうです。

例えば、以下のようにわざわざ空の配列を作成して別の配列を加工したものを空の配列に格納するようなメソッドはmapメソッドで代用可能です。

list = (1..5).to_a # [1,2,3,4,5]の配列

array = []        # 空の配列を用意
list.each do |i|
  array << i * 2  # 要素を2倍にしたものを空の配列に格納
end

mapメソッドを使う場合

list = (1..5).to_a # [1,2,3,4,5]の配列

array = list.map { |i| i * 2 }

mapを使う方が明らかにシンプルにかけます。Arrayクラスのインスタンスメソッドは複雑な処理が非常に多く用意されていると思うので、eachを使う場合はその処理を他のメソッドで置き換えることが可能かどうかを意識した方がよさそうです。

each_index

eachの時は配列の要素を取得しましたが、こちらはインデックスの値を使用して処理を行います。

["a", "b"].each_index do |i|
    puts i
end
#=> 0, 1

正直この処理はあまり使いどころが思い浮かびませんでした。元の配列の要素を使用した評価ができないからです。

インデックスを使う場合は次に紹介するeach_with_indexの方が使う機会が多そうです。

reverse_each

eachのレシーバーの要素を逆にして評価を行うメソッドです。

a = [ "a", "b", "c" ]
a.reverse_each {|x| puts x }
#=> a, b, c

上記は下記の処理を1つのメソッドにまとめたものです。

a = [ "a", "b", "c" ]
a.reverse.each {|x| puts x }
#=> a, b, c

2つのメソッドだと効率が悪いので1つにまとめているという感じですね。コードの記述量は変わりません。

each_with_index

each_with_indexはeachとeach_indexを組み合わせたものです。ブロック内で使えるものは要素とその要素のインデックス番号です。

["a", "b"].each_with_index do |s, i|
    puts [s, i]
end
#=> ["a", 0]
# ["b", 1]

このメソッドを使う例として、評価時の要素の前後の要素のインデックス番号を取得したい時などに使用できると思います。

ary=["a", "b", "c"]
count=ary.size
ary.each_with_index{|v,i|
  prev_idx = (i-1)%count
  next_idx = (i+1)%count
}

この処理は自分のスクールの受講生さんがアウトプットしていた処理です。配列の要素数を取得して

count=ary.size

インデックス番号からマイナス1をした値を配列の要素数で割った余りが評価中の要素の手前のインデックス番号(prev_idx)、プラス1をした値を配列の要素数で割った余りが評価中の要素の後ろのインデックス番号(next_idx)になります。

ary.each_with_index{|v,i|
  prev_idx = (i-1)%count
  next_idx = (i+1)%count
}

実際に出力してみると確認できます。

ary=["a", "b", "c"]
count=ary.size
ary.each_with_index{|v,i|
  prev_idx = (i-1)%count
  next_idx = (i+1)%count
  p "#{v}を評価中"
  p "手前は#{ary[prev_idx]}"
  p "後ろは#{ary[next_idx]}"
}

#=> "aを評価中"
#"手前はc"
#"後ろはb"
#"bを評価中"
#"手前はa"
#"後ろはc"
#"cを評価中"
#"手前はb"
#"後ろはa"

each_cons

このメソッドは先に処理を見てもらった方が理解できると思います。

list = (1..5).to_a

list.each_cons(3) {|v| p v}
# => [1,2,3], [2,3,4], [3,4,5]

each_consは引数に数値を指定する必要があります。

each_cons(n) # nは自然数

この数値の数で先頭から要素を区切って、その区切った要素を配列としてブロック内で使用することができます。

{|v| p v} # vには区切った配列:1巡目であれば[1,2,3]

次の巡目ではレシーバーの配列の次の2つ目の要素を先頭に引数の数で区切った配列をブロック内で使用します。

{|v| p v} # vには区切った配列:2巡目であれば[2,3,4]

この循環処理は区切った配列の要素にレシーバーの配列の最後の要素が含まれた場合に終了します。

この処理は言葉では説明しづらいですが処理を見ればかなりわかりやすいですよね。

ちなみに、もし引数がレシーバーの要素数より大きい場合何も出力されません。つまりエラーにもなりません。

list = (1..5).to_a

list.each_cons(6) {|v| p v}
# => 何も出力されない

each_slice

こちらも先に処理を見てみましょう。

(1..10).each_slice(3) {|v| p v}
# => [1, 2, 3]
#    [4, 5, 6]
#    [7, 8, 9]
#    [10]

each_consと異なる点としては、循環する際に次の要素に手前の要素が含まれないということです。

each_cons

(1..10).each_cons(3) {|v| p v}
# => [1, 2, 3]
#    [2, 3, 4] # 前の処理の要素が含まれている

each_slice

(1..10).each_slice(3) {|v| p v}
# => [1, 2, 3]
#    [4, 5, 6] # 前の処理の要素は含まれない

そして、最後の要素数が引数の数値より少ない場合でも配列として出力されます。

(1..10).each_slice(3) {|v| p v}
# => [1, 2, 3]
・
・
#    [10] # 引数の数より少ないけど出力される

each_with_object

引数にオブジェクトを指定してそのオブジェクトとレシーバーの各要素を使用してブロック内で処理を行います。

[1, 2, 3].each_with_object [] do |i, array|
  array << i**i
  p array
end

# [1]
# [1, 4]
# [1, 4, 27]

面白い点としては渡したオブジェクトが繰り返し処理で更新されていくところです。どういうことかというと、以下のように渡したオブジェクトに対して要素を追加すると、

array << i**i # 空の配列に1が格納される

次の処理ではarrayに1の要素が格納されている状態になります。

array << i**i # [1]の配列に4が格納される

引数で渡すオブジェクトはオブジェクトであれば何でもいいのでかなり使い勝手が良さそうです。