【Ruby】inject()メソッドで繰り返し処理をスマートに実装してみよう!

今回は、inject()メソッドと呼ばれる、繰り返し処理を実装するのに便利な機能を持つメソッドの使い方を、サンプルコード付きで詳しく解説していきます。

また、類似する他のメソッドとの違いもサンプルコード付きで比較していますので、繰り返し処理をスマートに実装したい人は、ぜひ今回の内容をお役立てください。

なお、Rubyの魅力や特徴については以下の記事で詳しく解説していますので、こちらもぜひご覧くださいね。

おすすめ記事

みなさんこんにちは! 今回の記事は、 悩みを抱えた人 ・Rubyってどんなプログラミング言語なの?・Rubyを勉強すると将来役に立つかな?・Rubyのおすすめの学習方法が知りたい! というお悩みを解決する記事にな[…]

Rubyとは?その特徴や将来性、おすすめの学習方法まで網羅的にご紹介します

inject()メソッドとは?

inject()メソッドは、RubyのEnumerableモジュールで用意されているインスタンスメソッドです。Enumerableとは、「列挙可能な」などを意味する英単語で、つまりは配列ハッシュなどの繰り返し可能なデータ構造を指しています。

Rubyでは、配列(Arrayクラス)やハッシュ(Hashクラス)のような繰り返し処理可能なクラスに、Enumerableモジュールが組み込まれており、inject()メソッドを呼び出すことが可能です。

inject()メソッドの基本的な使い方

ここからは、inject()メソッドの使い方について、サンプルコードを適宜確認しながら理解していきましょう。

ブロックで処理を定義する

inject()メソッドは、具体的な処理を定義したブロックを渡すことで実行できます。

簡単な例として、配列に格納されているデータを合計した値を計算するプログラムを考えてみましょう。これを、inject()メソッドを使って以下のサンプルコードのように実装できます。

sample_data_array = [
  192,
  394,
  74,
  230,
  4892,
  3939
]

# inject()メソッドにブロックを渡す
data_sum = sample_data_array.inject { |sum, data| sum += data }

p "サンプルデータの合計は" + data_sum.to_s + "です。" #"サンプルデータの合計は9721です。"

inject()メソッドは、配列(Arrayクラス)のインスタンスメソッドなので、配列をレシーバとして呼び出します。

inject()メソッドにブロックを渡す場合、2つの引数を定義でき、1つ目の引数にはブロック内の処理の最終結果が、2つ目の引数には1回の繰り返しごとにレシーバの要素が順番に格納されます。このことを踏まえて、サンプルコードを詳しく見てみましょう。

inject()メソッドを呼び出して最初の繰り返しでは、引数sumは0、引数dataは配列sample_data_arrayの一番最初の要素、つまり192です。「sum += data」は「sum = sum + data」と同じ処理を意味しており、sumの中身が192に更新されます。

続いて2回目の繰り返しでは、引数sumは192、引数dataは次の要素である394が格納され「192 + 394」の計算結果が、引数sumに設定されます。これを配列の要素数分だけ繰り返すことにより、配列の合計値を求めることができます。

上記サンプルコードの計算処理を、forキーワードで書き換えると以下のサンプルコードになります。

sample_data_array = [
  192,
  394,
  74,
  230,
  4892,
  3939
]

data_sum = 0
for item in sample_data_array do
  data_sum = data_sum + item
end

いずれの処理も、同様の結果を得られますが、ソースコードの記述量に注目するとinject()メソッドがforキーワードを使う場合よりも少ないことが分かります。

このように、inject()メソッドは、Rubyで繰り返し処理を実装するうえでforキーワードよりもスマートに実装できるのが大きなメリットの一つです。

初期値を設定する

inject()メソッドを呼び出す際に、初期値を設定することができます。初期値を設定する場合のinject()メソッドのサンプルコードを、以下に示します。

# 初期値として追加するデータ
last_week_data = 123000;

# 今回計算するデータ
this_week_data = [
  8000,
  13000,
  6000,
  12000,
  52000,
  5000,
  2000
]

# 第一引数に初期値を渡して呼び出す
total_data = this_week_data.inject(last_week_data) { |result, data| result += data }

p "前回分を含めた合計:" + total_data.to_s # "前回分を含めた合計:221000"

初期値の設定方法は簡単で、inject()メソッドの第一引数に初期値となる値、または変数を渡すだけです。

初期値を設定した場合、初回の繰り返しにおいて引数resultには初期値が格納された状態で、ブロック内の処理が実行されます。つまり、今回のサンプルコードにおけるinject()メソッドの呼び出し時は、引数resultに123000、引数dataに8000が格納されることになります。

なお、初期値を設定しない場合は、前節「ブロックで処理を定義する」のサンプルコードのように、0が自動的に設定された状態で実行されます。

シンボルを設定する

inject()メソッドでは、演算子やメソッドをシンボルとして引数に渡すことで、ブロックを定義するよりもさらにスリムに繰り返し処理を表現できます。

配列の総和や総乗を求めるプログラムであれば、演算子だけを渡すことで簡単に実現できます。サンプルコードは、以下の通りです。

sample_range = 11..15

# シンボル「:+」を渡すことで総和が求まる
sample_sum = sample_range.inject(:+)

# シンボル「:*」を渡すことで総乗が求まる
sample_product = sample_range.inject(:*)

p "総和:" + sample_sum.to_s + "、総乗:" + sample_product.to_s # "総和:65、総乗:360360"

ここで、4行目の総和を求めるプログラムを、forキーワードで実装した場合と、inject()メソッドにブロックを渡した場合とで、ソースコードを比べてみましょう。

# inject()メソッドにシンボルを渡した場合
sample_sum = sample_range.inject(:+)

# inject()メソッドにブロックを渡した場合
sample_sum = sample_range.inject { |result, value| result += value}

# for構文を利用した場合
sample_sum = 0
for value in sample_range do
  sample_sum = sample_sum + value
end

シンボルを渡した場合、記述量が他のケースよりも少なく済むことが一目瞭然です。

他の繰り返し処理系メソッドと比較

inject()メソッド以外にも、配列などの繰り返し可能なデータ構造に対して、何らかの処理を実施するためのメソッドが存在します。それらのメソッドと比較しながら、inject()メソッドがどのような場面で利用するのに最適であるかを、説明しましょう。

reduce()メソッド

1つ目は、reduce()メソッドです。

inject()メソッドとは違う機能を持つと考えがちですが、実際はinject()メソッドと全く同じ機能を持ちます。以下のサンプルコードを実行して、挙動の違いを確認してみましょう。

sample_data = [
  12,
  24,
  36,
  48,
  50,
  39,
  18
]

sum_used_inject = sample_data.inject {|sum, val| sum += val}
sum_used_reduce = sample_data.reduce {|sum, val| sum += val}

# inject()、reduce()ともに同じ機能を持つ
p "計算結果(inject):" + sum_used_inject.to_s # "計算結果(inject):227"
p "計算結果(reduce):" + sum_used_reduce.to_s # "計算結果(reduce):227"

sum_used_inject = 0
sum_used_reduce = 0

sum_used_inject = sample_data.inject(1000, :+) 
sum_used_reduce = sample_data.reduce(1000, :+) 

# 初期値やシンボルなども同等の機能を持つ
p "計算結果(inject)(初期値有・シンボル指定):" + sum_used_inject.to_s # "計算結果(inject)(初期値有・シンボル指定):1227"
p "計算結果(reduce)(初期値有・シンボル指定):" + sum_used_reduce.to_s # "計算結果(reduce)(初期値有・シンボル指定):1227"

ブロックを渡した場合、初期値やシンボルを設定した場合のいずれも、inject()メソッドとreduce()メソッドとの間に、機能的な違いはまったく存在しないことが分かります。

each()メソッド

2つ目は、each()メソッドです。

each()メソッドは、配列などの要素ごとに何かしらの処理を施す機能を持ちます。inject()メソッドと異なるのは、以下の2点です。

  • 全ての繰り返しで共通の変数を引数として用意できないこと
  • 戻り値がレシーバ自身であること

総乗計算を行う処理を例に挙げて、2つのメソッドの挙動の違いを以下のサンプルコードで確認しましょう。

sample_data = [2, 6, 4, 7, 3, 5]

product_inject = 1
product_each = 1

# inject()メソッドで実装した場合
product_inject = sample_data.inject {|pro, val| pro *= val}

# each()メソッドで実装した場合
result = sample_data.each {|data| product_each *= data}

# 計算結果
p "計算結果(inject):" + product_inject.to_s # "計算結果(inject):5040"
p "計算結果(each)  :" + product_each.to_s   # "計算結果(each)  :5040"

# 戻り値
p "inject()の戻り値;" + product_inject.to_s # "inject()の戻り値;5040"
p "each()の戻り値 :" + result.to_s         # "each()の戻り値 :[2, 6, 4, 7, 3, 5]"

inject()メソッドは、途中までの計算結果を格納するための変数を引数として扱っています。サンプルコードで言うところの引数proがそれに該当します。

一方、each()メソッドは引数proにあたる変数をブロック内で用意できません。したがって、外部で変数を用意する必要があります。

このことから、元の配列からある1つの値を求めるような処理を実装する場合は、inject()メソッドを利用するのが最適であると言えます。

また、each()メソッドの戻り値はレシーバ自身であり、実質戻り値に何の意味も持ちません。したがってeach()メソッドは、処理結果を後続の処理で利用することがない場面で利用するのが最適であると言えます。

例えば、配列の各要素をputs()メソッドで出力したい場合は、each()メソッドを使って以下のように実装するのが理想的です。

source_array = [3,7,3,2,8,6]

# 値の出力自体は、後続の処理に何も影響しない
source_array.each { |value| puts value.to_s + "が格納されています" }

# 3が格納されています
# 7が格納されています
# 3が格納されています
# 2が格納されています
# 8が格納されています
# 6が格納されています

map()メソッド

3つ目は、map()メソッドです。

map()メソッドは、配列の各要素に何かしらの処理を施して、その処理結果を列挙し新たな配列を作成する機能を持ちます。inject()メソッドとの違いは、以下の1点です。

  • 全ての繰り返しで共通の変数を引数として用意できないこと

再び総乗計算を例に挙げて、以下のサンプルコードで違いを比べてみましょう。

sample_data = [3, 5, 2, 9, 1, 6]

product_inject = 1
product_map = 1

# inject()メソッドで実装した場合
product_inject = sample_data.inject {|pro, val| pro *= val}

# map()メソッドで実装した場合
result = sample_data.map {|data| product_map *= data}

# 計算結果
p "計算結果(inject):" + product_inject.to_s # "計算結果(inject):1620"
p "計算結果(map)   :" + product_map.to_s    # "計算結果(map)   :1620"

# 戻り値
p "inject()の戻り値;" + product_inject.to_s # "inject()の戻り値;1620"
p "map()の戻り値 :" + result.to_s          # "map()の戻り値 :[3, 15, 30, 270, 270, 1620]"

map()メソッドで総乗計算を行う場合は、each()メソッドの場合と同じように、途中計算結果を入れておくための変数を、外部で用意しなければなりません。やはり、総乗計算などのケースにおいてはinject()メソッドを採用するのが最適であると言えます。

一方、map()メソッドの戻り値はeach()メソッドの場合と異なり、新しい配列が作成されています。サンプルコード18行目で出力される配列には、「15=3×5」「30=3×5×2」「270=3×5×2×9」のように、途中の計算結果が順番に列挙されていることが分かります。

map()メソッドは、主に配列の各要素ごとに何らかの処理を適用し新たな配列を作成したい場合に利用するのが主です。例えば、元の配列の各要素を二乗したものを作成したい場合は、map()メソッドで以下のように実装するのが、理想的と言えます。

source_range = 1..10

# 二乗した値の配列を作成したいので
# map()メソッドを呼び出す
sqr_array = source_range.map { |value|
  value * value
}

p sqr_array # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

傍楽たろう
なお、mapメソッドについては以下の記事でも詳しく解説していますので、こちらもぜひご覧くださいね。
おすすめ記事

みなさんこんにちは! 今回は、 悩める人 ・Rubyのmap()メソッドの使い方をマスターしたい・collect()メソッドやeach()メソッドとどう使い分けたらいいの? というお悩みを解決する記事にな[…]

【Ruby】mapメソッドをマスター | 他のメソッドとの違いも解説

お仕事の途中ですが、少し一休みして、転職独立について考えてみませんか🙌?

現役エンジニアが選ぶおすすめの転職エージェント11選【成功談・失敗談もあります】

レバテックフリーランスの評判ってどう?【現役エンジニアが徹底解説します】

MidWorks(ミッドワークス)の評判ってどう?【現役エンジニアが徹底解説します】

日々の業務に追われて自分を見失わないよう、
定期的にキャリアを振り返るようにしておきましょう🤲

最後に

さて、ここまでRubyのinject()メソッドの使い方について解説してきましたがいかがでしたか?

実装方法は少しだけ難しいものの、他のメソッドと比べて簡潔に処理を実装することができるので、マスターすると開発効率はグンと上がってくれると思います。

今回の記事でしっかりとマスターして、今後の開発でも積極的に使っていってくださいね。

このブログを通じて少しでも「傍(はた)を楽(らく)にする」ことができていれば嬉しく思います。

最後まで読んで頂きありがとうございました。