目次
injectについて
injectとはリストのたたみこみ演算をするEnumableモジュールのメソッドです。
inject
はEnumableモジュールに属するArrayやHashなどに適用でき、各要素に対して処理を実行して結果を算出する機能があります。
reduce
はinject
のエイリアス(等価のメソッド)です。
injectの構文
以下の4通りに分類してinject
の構文を紹介します。
- 『シンボルを利用しない』かつ『初期値がない』
- 『シンボルを利用しない』かつ『初期値がある』
- 『シンボルを利用する』かつ『初期値がない』
- 『シンボルを利用する』かつ『初期値がある』
『シンボルを利用しない』かつ『初期値がない』
### {} を利用する場合
オブジェクト.inject { |result, item| block }
### do...endを利用する場合
オブジェクト.inject do |result, item|
block
end
オブジェクト
の要素がitem
に渡されてblock
に記述された演算をします。演算結果はresult
に保存されます。
演算が終わるとオブジェクトの次の要素がitem
に渡されて再度block
の演算とresult
への結果の保存が行われます。(result
には各item
の演算結果が積まれていくイメージです。)
この処理がオブジェクトのすべての要素に対して行われます。
たとえば、1から10までの合計値を求める場合は以下のようになります。
# (1..10).inject { |sum, i| sum + i } でもOK
result = (1..10).inject do |sum, i|
sum + i
end
### 結果出力
result
=> 55
each
を利用した場合は以下のようになります。
sum = 0
(1..10).each { |i| sum += i }
### 結果出力
sum
=> 55
『シンボルを利用しない』かつ『初期値がある』
### {} を利用する場合
オブジェクト.inject(初期値) { |result, item| block }
### do...endを利用する場合
オブジェクト.inject(初期値) do |result, item|
block
end
たとえば、初期値100で、1から10までの合計値を求める場合は以下のようになります。
# (1..10).inject(100) { |sum, i| sum + i } でもOK
result = (1..10).inject(100) do |sum, i|
sum + i
end
### 結果出力
result
=> 155
each
を利用した場合は以下のようになります。
sum = 100
(1..10).each { |i| sum += i }
### 結果出力
sum
=> 155
『シンボルを利用する』かつ『初期値がない』
各要素に対して行う演算はシンボルのみで表現できる場合もあります。
オブジェクト.inject(シンボル)
たとえば、1から10までの合計値を求める場合は以下のようになります。
result = (1..10).inject(:+)
### 結果出力
result
=> 55
『シンボルを利用する』かつ『初期値がある』
オブジェクト.inject(初期値, シンボル)
たとえば、初期値100で、1から10までの合計値を求める場合は以下のようになります。
result = (1..10).inject(100, :+)
### 結果出力
result
=> 155
injectを利用した具体例
配列を結合する
あえてinject
を利用せずとも配列の結合はできますが、inject
の参考実装の1つとして紹介をします。
array1 = ['a', 'b']
array2 = ['c', 'd']
array3 = ['e', 'f']
# concatは破壊的変更を加えるため、array1の内容を変更させないために初期値([])を用意した
[array1, array2, array3].inject([], :concat)
=> ["a", "b", "c", "d", "e", "f"]
[array1, array2, array3].inject(:+)
=> ["a", "b", "c", "d", "e", "f"]
Rubyにおける配列操作の詳細解説は【Ruby】複数の配列を1つの配列に結合するメソッドまとめで紹介しています。
すべての結果が成功(true)か判定する
inject
と&&
(論理積)と組み合わせることでall?と似たようなことを表現できます。
たとえば、配列の要素がすべて偶数ならtrue
、そうでなければfalse
を返す例について考えます。
all?
を使った実装は以下の通りです。
[2, 4, 5].all? { |num| num.even? }
=> false
[2, 4, 6].all? { |num| num.even? }
=> true
inject
を利用すると以下のように表現できます。
def all_even?(array)
array.map do |num|
num.even?
end.inject(true) do |result, evaluate|
result && evaluate
end
end
### 結果出力
all_even?([2, 4, 5])
=> false
all_even?([2, 4, 6])
=> true
実務においては、一括更新をする際にすべての処理が成功したか判定したい時に便利です。
具体的な疑似コードは以下になります。サンプルコードはプロを目指す人のための例外処理(再)入門を参考にしています。
def extend_deadline_for_all_stakeholders(project, new_deadline)
project.developers.map do |developer|
# developerごとにupdateを実行(成功したらtrue, 失敗したらfalse)
developer.update(deadline: new_deadline)
end.inject(true) do |result, evaluate|
# evaluate(updateの結果)が1つでもfalseならresultはfalseになる
result && evaluate
end
end
なお、&=
(自己代入の積集合バージョン。つまりx &= y
はx = x & y
を表現している)を利用しても『複数の処理がすべてtrue
ならtrue
、1つでもfalse
ならばfalse
』の実装は可能です。
def all_even?(array)
all_even = true
array.map do |num|
all_even &= num.even?
end
all_even
end
### 結果出力
all_even?([2, 4, 5])
=> false
all_even?([2, 4, 6])
=> true
さいごに
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!