こんにちは!Webコーダー・プログラマーの貝原(@touhicomu)です。
Rubyでは繰り返しを行う処理がいくつか用意されていますが、
injectはブロックを使用して繰り返し処理を行います。
今日は、Rubyのinjectについて以下の内容で解説します。
【基礎】injectの基本的な使い方
【基礎】injectに初期値を設定する
【発展】injectにシンボルを使って演算子を指定する
【発展】条件を指定して、hashから配列を作成する
また、injectの概念は少々わかりづらいため、eachでinjectと同等の処理を行うサンプルコードも紹介していきます。
このページで、Rubyのinjectの使い方をよく把握して自分のスキルとしていきましょう!
injectとは?
injectはeachやmapと同じように繰り返しを行うメソッドです。
ブロックを使って繰り返し計算を行うことが特徴で、
配列オブジェクト.inject {|初期値, 要素| ブロック処理 }
のように記述します。
繰り返し順にブロックの要素が配列分加算されていき、ブロックの処理にて計算を行っていきます。
まだピンとこない方もいると思いますので次項で実際に使い方を見ていきましょう!
injectの基本的な使い方
配列の合計を算出する
それではinjectの基本的な使い方を見ていきましょう。
injectは直感的にわかりにくいので、サンプルコードで挙動を確認していきましょう。
[injectを使って配列の中身の合計を算出する]
array = 1..6 array.inject (0){ |sum,num| p sum+=num}
[実行結果]
1 3 6 10 15 21
上記injectと同等の処理を行うeachのサンプルコードは以下の通りです。
array = 1..6 #以下のinjectと同等の処理をeachで実行します。 #array.inject (0){ |sum,num| p sum+=num} sum = 0 array.each do |num| sum += num p sum end
[実行結果]
1 3 6 10 15 21
injectは第1引数にinjectが内部で保持している変数の初期値を設定できて、その内部変数がブロックの第1ブロック変数となります。
今回の場合はsumが第1ブロック変数で、始めに初期値0が代入されています。
eachの場合は、変数sumに0を代入しています。
また、ブロック引数は第2引数以降で取得できます。今回ブロックの第2引数は、arrayの要素ですのでnumとなります。
ブロックの第1ブロック変数はsumですが、これはinjectの第1引数で初期化された変数で、injectメソッドが内部で持っている変数です。
eachの例では、eachのブロックの外部の変数sumを使用しています。
これは、erachはinjectとは違って、内部に変数を持っていないため、外部で変数sumを用意しないといけないためです。
そして、この例では要素numが順番にsumに加算されています。
これは、injectでもeachでも同じ処理ですね。
そして、injectでもeachでもpメソッドで、変数sumの計算過程を表示しています。
もう一度繰り返しますが、injectは英語で、注入するという意味を持ちます。
なので、sumにarrayの要素であるnumが順番に注入されていくイメージがあると理解しやすいです。
また、上記2つの例のように、eachは少し冗長な書き方でinjectの方が簡潔に書けてエレガントです。
ただ、何をやっているのかは、eachメソッドの方がわかりやすいです。
injectになれてきたら、injectを書いていきましょう。
injectに初期値を設定する
injectにはさきほど0を初期値としたように、初期値を設定することができます。
array = 1..6 array.inject (6){ |sum,num| p sum+=num}
[実行結果]
7 9 12 16 21 27
これを、eachで書くと、以下のようなサンプルコードになります。
array = 1..6 #以下のinjectと同等の処理をeachで実行します。 #array.inject (6){ |sum,num| p sum+=num} sum = 6 array.each do |num| sum += num p sum end
[実行結果]
7 9 12 16 21 27
初期値を設定した結果、さきほどと比べて、全体的に初期値6だけ大きくなりました。
njectの第1引数6で、injectの内部変数が6に初期化されたためです。
その後、injectのブロック変数sumにinjectの内部変数が渡されています。
つまり、ブロック変数sumの初期値は6です。
eachの方でも、sumの初期値を0から6に変更しただけですね。
injectにシンボルを使って演算子を指定する
injectに演算子をシンボルで渡すことで、よりスタイリッシュにinjectを書くことができます。
例えば、
- 配列の中身の合計を算出したい
- 全部掛け合わせた値を計算したい
- 配列の中身を使って順番に引き算をしたい
などさまざまな場面で使用可能です。
array = 1..6 p array.inject(:+) #配列の要素をすべて足す p array.inject(3,:+) #初期値3に対して、配列の要素をすべて足す p array.inject(:*) #配列の要素をすべて掛ける p array.inject(3,:*) #初期値3に対して、配列の要素をすべて掛ける p array.inject(100,:-) #100からarrayの合計値を引く
[実行結果]
21 24 720 2160 79
このようにシンボルで指定した演算子で、配列の中身が順番に初期値に対して演算されていることを確認できます。
これを、eachで書いてみると以下のようなサンプルコードになります。
#p array.inject(:+) #配列の要素をすべて足す def sumope( array, init_value, ope ) sum = init_value array.each do |num| sum = ope.to_proc.call(sum, num) p sum end end array = 1..6 sum = 6 sumope( array, sum, :+ )
[実行結果]
7 9 12 16 21 27
関数sumopeで引数arrayとinit_value、opeを受け取っています。
arrayは計算に使用する配列です。
init_valueは、計算に使用する変数sumの初期値です。
opeは、計算メソッドとして使用するシンボルです。
sumope内では、sumに初期値を代入した後、配列arrayのeachメソッドでループしています。
そして、ループのブロック内で、シンボルope(:+)をto_procして、Procに変換しています。
そして、ProcのメソッドcallでProcを実行しています。
Procはこの場合、「+」メソッドですので、sumとnumが「sum + num」されて、計算結果が変数sumに代入されています。
そして、「p sum」で、sumの内容を表示しています。
このように、injectでは、いろいろな冗長な書き方が省略でき、大変エレガントにコードを書けます。
このエレガントさは、いかにもRubyらさしさと言えるでしょう。
eachで書く場合は、コードが少々冗長になりますが、コードの見た目がわかりやすくなります。
まずはeachで書いてみて、なれてきたらinjectを使用する方が良いでしょう。
条件を指定して、hashから配列を作成する
injectを使って、hashから新しい配列を作成することもできます。
その際に、条件を自由に指定できます。
今回はhashの中から、valueが150以下のもののkeyを新しい配列に詰めます。
hash = {"apple" => 245, "orange"=>120, "peach"=>566, "banana" => 100} new_array = hash.inject([]) {|new_array, (key, val)| new_array << key if val <=150; new_array} p new_array
[実行結果]
["orange", "banana"]
このように、orangeとbananaがしっかりと新しい配列として取り出されていることを確認できます。
これを、eachで書いてみると、以下のようなサンプルコードになります。
hash = {"apple" => 245, "orange"=>120, "peach"=>566, "banana" => 100} #以下のinjectと同等の処理をeachで実行します。 #new_array = hash.inject([]) {|new_array, (key, val)| new_array << key if val <=150; new_array} new_array = [] hash.each do | (key, val ) | if val <= 150 new_array << key end end p new_array
[実行結果]
["orange", "banana"]
eachでは、まず、変数new_arrayを空白の配列[]で初期化しています。
injectでは、injectの第1引数に[]を渡していますね。
このinjectの第1引数で、injectの内部変数が[]に初期化されます。
そのため、injectのブロックの第1ブロック変数new_arrayの初期値は[]です。
eachに話しを戻すと、ブロック変数keyとvalを受け取り、if分にて、「val <= 150」が成り立つ時のみ、配列new_arrayに新しい要素keyを追加しています。
injectの例では、
new_array << key if val <=150
となっています。
injectの例では後置ifとなっているだけで、「val <= 150」なハッシュのキーをnew_arrayの要素として追加している処理は同じですね。
eachの実行結果でも、「val <= 150」なハッシュのキー「"orange"」「"banana"」のみ、配列new_arrayに代入されます。
最後に。pメソッドで配列new_arrayを表示しています。
まとめ
今回は、Rubyのinjectについて学習しました!
学習のポイントを振り返ってみましょう!
「injectは内部変数を1つ保持してループ処理を行い、ブロックの第1ブロック変数に内部部数を渡す。第2ブロック変数はレシーバの要素」
「injectのブロックの返値がinjectの返値となる」
「injectの第1引数で。injectの内部変数を初期化できる」
「injectにProcのシンボルを渡した場合、内部変数とレシーバの要素を、そのProcで演算した結果を返す」
「injectの第1引数に配列を渡した場合、injectの内部変数が配列で初期化される。その内部変数はブロックの第1引数に渡される。そのため。ブロックにてinjectの内部変数である配列を加工できる」
以上の内容を再確認し、ぜひ自分のプログラムに生かし学習を進めてください!