注入方法の簡単な説明が必要


142
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

私はこのコードを見ていますが、脳は10がどのように結果になるかを記録していません。ここで何が起こっているのか誰かが説明してもらえますか?

ruby  syntax 

3
ウィキペディアを参照してください:フォールド(高次関数):injectは「左折」ですが、(残念ながら)Rubyの使用に副作用があることがよくあります。
user2864740 2015年

回答:


208

最初のブロック引数はアキュムレータと考えることができます。ブロックの各実行の結果はアキュムレータに格納され、次にブロックの次の実行に渡されます。上記のコードの場合、アキュムレータの結果をデフォルトで0にしています。ブロックを実行するたびに、指定した数値が現在の合計に追加され、結果がアキュムレータに格納されます。次のブロック呼び出しは、この新しい値を持ち、それに追加し、再び格納して、繰り返します。

プロセスの最後に、injectはアキュムレータを返します。これは、この場合、配列内のすべての値の合計、つまり10です。

以下は、オブジェクトの配列からハッシュを作成する別の簡単な例です。オブジェクトの文字列表現をキーにしています。

[1,"a",Object.new,:hi].inject({}) do |hash, item|
  hash[item.to_s] = item
  hash
end

この場合、デフォルトでアキュムレータを空のハッシュに設定し、ブロックが実行されるたびにそれを生成します。ブロックの結果はアキュムレータに戻されるため、ハッシュをブロックの最後の行として返す必要があることに注意してください。


ただし、OPによって与えられた例では、何が返されているか(ハッシュが例にあるように)は素晴らしい説明です。結果+説明で終わり、戻り値があるはずです、そうですか?
Projjol

1
@Projjolはresult + explanation、アキュムレータへの変換と戻り値の両方です。これは、ブロックの最後の行であり、暗黙的に戻ります。
KA01

87

inject0例では)で始まる値とブロックを受け取り、リストの要素ごとにそのブロックを1回実行します。

  1. 最初の反復では、開始値として指定した値とリストの最初の要素を渡し、ブロックが返した値(この場合はresult + element)を保存します。
  2. 次に、ブロックを再度実行し、最初の反復の結果を最初の引数として渡し、リストの2番目の要素を2番目の引数として渡して、結果を再び保存します。
  3. リストのすべての要素を消費するまで、この方法を続けます。

これを説明する最も簡単な方法は、例として、各ステップがどのように機能するかを示すことです。これは、この結果を評価する方法を示す架空の手順のセットです。

[1, 2, 3, 4].inject(0) { |result, element| result + element }
[2, 3, 4].inject(0 + 1) { |result, element| result + element }
[3, 4].inject((0 + 1) + 2) { |result, element| result + element }
[4].inject(((0 + 1) + 2) + 3) { |result, element| result + element }
[].inject((((0 + 1) + 2) + 3) + 4) { |result, element| result + element }
(((0 + 1) + 2) + 3) + 4
10

手順を書いてくれてありがとう。これは非常に役立ちました。下の図は、injectメソッドに引数として渡されるものに関して、injectメソッドがその下に実装されている方法であるかどうかについて少し混乱しましたが。

2
以下の図は、それ実装する方法に基づいています。必ずしもこのように実装されているとは限りません。それが、架空のステップのセットであると私が言った理由です。基本的な構造を示していますが、正確な実装ではありません。
ブライアンキャンベル

27

injectメソッドの構文は次のとおりです。

inject (value_initial) { |result_memo, object| block }

上記の例、つまり

[1, 2, 3, 4].inject(0) { |result, element| result + element }

これは出力として10を与えます。

だから、始める前に、各変数に格納されている値が何であるかを見てみましょう:

結果= 0ゼロは0であるinject(value)から取得されました

element = 1配列の最初の要素です。

オーケー!!!だから、上の例を理解してみましょう

ステップ1 [1, 2, 3, 4].inject(0) { |0, 1| 0 + 1 }

ステップ2 [1, 2, 3, 4].inject(0) { |1, 2| 1 + 2 }

ステップ:3 [1, 2, 3, 4].inject(0) { |3, 3| 3 + 3 }

ステップ:4 [1, 2, 3, 4].inject(0) { |6, 4| 6 + 4 }

ステップ:5 [1, 2, 3, 4].inject(0) { |10, Now no elements left in the array, so it'll return 10 from this step| }

ここで、太字の斜体の値は配列からフェッチした要素であり、太字の単純な値は結果の値です。

#injectメソッドの動作をご理解いただければ幸いです#ruby



15

彼らが言ったこと、しかしあなたは必ずしも「開始値」を提供する必要がないことに注意してください:

[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

と同じです

[1, 2, 3, 4].inject { |result, element| result + element } # => 10

試して、待ってみます。

injectに引数が渡されない場合、最初の2つの要素が最初の反復に渡されます。上記の例では、最初はresultが1でelementが2であるため、ブロックへの呼び出しが1つ少なくなります。


14

injectの()内に配置する数値は、開始位置を表します。0または1000の場合があります。パイプ内には、2つのプレースホルダー| x、y |があります。x = .inject( 'x')内にあった番号。secoundはオブジェクトの各反復を表します。

[1, 2, 3, 4].inject(5) { |result, element| result + element } # => 15

1 + 5 = 6 2 + 6 = 8 3 + 8 = 11 11 + 4 = 15


6

注入はブロックを適用します

result + element

配列内の各項目に。次の項目(「要素」)の場合、ブロックから返される値は「結果」です。(パラメーターを使用して)呼び出した方法では、「結果」はそのパラメーターの値から始まります。したがって、効果は要素を追加することです。


6

tldr; injectmap、1つの重要な点で異なります。injectブロックの最後の実行の値をmap返しますが、反復した配列を返します。

それ以上に、各ブロック実行の値は最初のパラメーター(resultこの場合)を介して次の実行に渡され、その値((0)部分)を初期化できます。

上記の例は次のように書くことができますmap

result = 0 # initialize result
[1, 2, 3, 4].map { |element| result += element }
# result => 10

同じ効果ですが、injectここではより簡潔です。

mapブロックで評価が発生するのに対して、割り当てはブロックで発生することがよくありますinject

どの方法を選択するかは、目的のスコープによって異なりますresult。使用しない場合は次のようになります。

result = [1, 2, 3, 4].inject(0) { |x, element| x + element }

「いいね、私はそれをすべて1行にまとめただけ」のように思われるかもしれませんが、一時的にメモリを一時x変数として割り当てたので、すでにresult作業している必要はありませんでした。


4
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

以下と同等です。

def my_function(r, e)
  r+e
end

a = [1, 2, 3, 4]
result = 0

a.each do |value|
  result = my_function(result, value)
end

3

[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

単純な英語では、この配列([1,2,3,4])を通過(反復)しています。4つの要素(1、2、3、および4)があるため、この配列を4回反復します。injectメソッドには1つの引数(数値0)があり、その引数を最初の要素(0 +1。これは1に等しい)に追加します。「結果」に1件保存されます。次に、その結​​果(1)を次の要素(1 + 2、これは3)に追加します。これは結果として保存されます。続ける:3 + 3は6に等しい。そして最後に、6 + 4は10に等しい。


2

このコードでは、開始値を渡さない可能性はありませんが、何が起こっているのかを説明するのに役立つ場合があります。

def incomplete_inject(enumerable, result)
  enumerable.each do |item|
    result = yield(result, item)
  end
  result
end

incomplete_inject([1,2,3,4], 0) {|result, item| result + item} # => 10

1

ここから始めて、ブロックを取るすべてのメソッドを確認してください。 http://ruby-doc.org/core-2.3.3/Enumerable.html#method-i-inject

あなたを混乱させるのはブロックですか、それともなぜメソッドに価値があるのですか?良い質問ですが。そこでの演算子メソッドは何ですか?

result.+

それは何から始まりますか?

#inject(0)

できますか?

[1, 2, 3, 4].inject(0) { |result, element| result.+ element }

これは機能しますか?

[1, 2, 3, 4].inject() { |result = 0, element| result.+ element }

私は、配列のすべての要素を単純に合計し、ドキュメントに表示されるメモに数値を生成するという考えに基づいて構築しています。

あなたはいつでもこれを行うことができます

 [1, 2, 3, 4].each { |element| p element }

配列の列挙可能オブジェクトが反復処理されるのを確認します。それが基本的な考え方です。

注入または削減が送信されるメモまたはアキュムレータを与えるだけです。

結果を得ようとすることができます

[1, 2, 3, 4].each { |result = 0, element| result + element }

何も戻らないので、これは以前と同じように機能します

[1, 2, 3, 4].each { |result = 0, element| p result + element }

要素インスペクターブロック内。


1

これはシンプルで理解しやすい説明です。

「初期値」は最初はややわかりにくいので忘れてください。

> [1,2,3,4].inject{|a,b| a+b}
=> 10

上記は次のように理解できます。私は1、2、3、4の間に「追加マシン」を挿入しています。つまり、1♫2♫3♫4であり、♫は加算マシンであるため、1 + 2 + 3 + 4と同じであり、10です。

あなたは実際に+それらの間にインジェクションすることができます:

> [1,2,3,4].inject(:+)
=> 10

これは、+1、2、3、4の間にa を挿入し、1 + 2 + 3 + 4にして10にするというものです。これ:+は、Ruby +がシンボルの形式で指定する方法です。

これは非常に理解しやすく、直感的です。そして、それが段階的にどのように機能するかを分析したい場合、それは次のようなものです:1と2を取り、それらを追加して、結果が得られたら、最初に格納し(3)、次に、格納されます値3と配列要素3はa + bプロセスを通過します。これは6であり、この値を格納し、6と4はa + bプロセスを通過し、10になります。

((1 + 2) + 3) + 4

「初期値」0は、そもそも「ベース」にすぎません。多くの場合、それは必要ありません。1 * 2 * 3 * 4が必要で、それが

[1,2,3,4].inject(:*)
=> 24

そしてそれが行われます。1全体を乗算するための「初期値」は必要ありません1


0

.inject()メソッドには別の形式があります。これは非常に役立ちます[4,5] .inject(&:+)これは、領域のすべての要素を合計します


0

それだけですreducefold、あなたが他の言語に精通している場合は、。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.