Perlu penjelasan sederhana tentang metode injeksi

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

Saya melihat kode ini tetapi otak saya tidak mendaftarkan bagaimana angka 10 dapat menjadi hasilnya. Adakah yang mau menjelaskan apa yang terjadi di sini?


sumber
3
Lihat Wikipedia: Lipat (fungsi tingkat tinggi) : suntikan adalah "lipatan ke kiri", meskipun (sayangnya) sering dengan efek samping dalam penggunaan Ruby.
user2864740

Jawaban:

208

Anda dapat menganggap argumen blok pertama sebagai akumulator: hasil dari setiap run blok disimpan dalam akumulator dan kemudian diteruskan ke eksekusi berikutnya dari blok. Dalam kasus kode yang ditunjukkan di atas, Anda default akumulator, hasil, ke 0. Setiap menjalankan blok menambahkan nomor yang diberikan ke total saat ini dan kemudian menyimpan hasilnya kembali ke akumulator. Panggilan blok berikutnya memiliki nilai baru ini, menambahnya, menyimpannya lagi, dan mengulanginya.

Pada akhir proses, menyuntikkan kembali akumulator, yang dalam hal ini adalah jumlah semua nilai dalam array, atau 10.

Berikut adalah contoh sederhana lain untuk membuat hash dari array objek, dikunci oleh representasi string mereka:

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

Dalam hal ini, kami menyetel akumulator kami ke hash kosong, lalu mengisinya setiap kali blok dijalankan. Perhatikan kita harus mengembalikan hash sebagai baris terakhir dari blok, karena hasil dari blok akan disimpan kembali di akumulator.

Drew Olson
sumber
Namun, penjelasan yang bagus, dalam contoh yang diberikan oleh OP, apa yang dikembalikan (seperti hash ada dalam contoh Anda). Itu berakhir dengan hasil + penjelasan dan harus memiliki nilai balik, ya?
Projjol
1
@ Projjol result + explanationadalah transformasi ke akumulator dan nilai kembali. Ini adalah baris terakhir di blok yang membuatnya kembali secara implisit.
KA01
87

injectmengambil nilai untuk memulai dengan ( 0dalam contoh Anda), dan sebuah blok, dan menjalankan blok itu sekali untuk setiap elemen daftar.

  1. Pada iterasi pertama, ia meneruskan nilai yang Anda berikan sebagai nilai awal, dan elemen pertama dari daftar, dan itu menyimpan nilai yang dikembalikan oleh blok Anda (dalam kasus ini result + element).
  2. Kemudian menjalankan blok lagi, meneruskan hasil dari iterasi pertama sebagai argumen pertama, dan elemen kedua dari daftar sebagai argumen kedua, lagi-lagi menyimpan hasilnya.
  3. Ini berlanjut seperti ini sampai menghabiskan semua elemen dari daftar.

Cara termudah untuk menjelaskan ini adalah dengan menunjukkan cara kerja setiap langkah, misalnya Anda; ini adalah serangkaian langkah imajiner yang menunjukkan bagaimana hasil ini dapat dievaluasi:

[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
Brian Campbell
sumber
Terima kasih telah menuliskan langkah-langkahnya. Ini banyak membantu. Meskipun saya sedikit bingung tentang apakah maksud Anda diagram di bawah ini adalah bagaimana metode injeksi diterapkan di bawah dalam hal apa yang disahkan sebagai argumen untuk menyuntikkan.
2
Diagram di bawah ini didasarkan pada bagaimana bisa dilaksanakan; itu belum tentu diimplementasikan dengan cara ini. Itu sebabnya saya mengatakan itu adalah serangkaian langkah imajiner; itu menunjukkan struktur dasar, tetapi bukan implementasi yang tepat.
Brian Campbell
27

Sintaks untuk metode injeksi adalah sebagai berikut:

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

Mari kita pecahkan contoh di atas yaitu

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

yang memberikan 10 sebagai output.

Jadi, sebelum memulai, mari kita lihat apa saja nilai yang disimpan di setiap variabel:

hasil = 0 Nol berasal dari inject (nilai) yaitu 0

element = 1 Ini adalah elemen pertama dari array.

Oke !!! Jadi, mari kita mulai memahami contoh di atas

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

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

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

Langkah: 4 [1, 2, 3, 4].inject(0) { |6, 4| 6 + 4 }

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

Di sini nilai Bold-Italic adalah elemen yang diambil dari array dan nilai Bold sederhana adalah nilai yang dihasilkan.

Saya harap Anda memahami cara kerja #injectmetode #ruby.

Vishal Nagda
sumber
19

Kode ini mengulangi empat elemen dalam array dan menambahkan hasil sebelumnya ke elemen saat ini:

  • 1 + 2 = 3
  • 3 + 3 = 6
  • 6 + 4 = 10
John Topley
sumber
15

Apa yang mereka katakan, tetapi perhatikan juga bahwa Anda tidak selalu perlu memberikan "nilai awal":

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

sama dengan

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

Cobalah, saya akan menunggu.

Ketika tidak ada argumen yang diteruskan untuk menyuntikkan, dua elemen pertama dilewatkan ke dalam iterasi pertama. Dalam contoh di atas, hasilnya adalah 1 dan elemen adalah 2 pertama kalinya, jadi satu panggilan kurang dibuat ke blok.

Mike Woodhouse
sumber
14

Jumlah yang Anda masukkan ke dalam () injeksi Anda mewakili tempat awal, bisa 0 atau 1000. Di dalam pipa Anda memiliki dua tempat penampung | x, y |. x = berapa nomor yang Anda miliki di dalam .inject ('x'), dan secound mewakili setiap iterasi objek Anda.

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

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

Stuart G
sumber
6

Suntikan menerapkan blok

result + element

untuk setiap item dalam array. Untuk item berikutnya ("elemen"), nilai yang dikembalikan dari blok adalah "hasil". Cara Anda menyebutnya (dengan parameter), "hasil" dimulai dengan nilai parameter itu. Jadi efeknya menambahkan elemen ke atas.

Jonathan Adelson
sumber
6

tldr; injectberbeda dari mapdalam satu cara penting: injectmengembalikan nilai eksekusi terakhir dari blok sedangkan mapmengembalikan array yang diulangi.

Lebih dari itu nilai setiap eksekusi blok dilewatkan ke eksekusi berikutnya melalui parameter pertama ( resultdalam hal ini) dan Anda dapat menginisialisasi nilai itu ( (0)bagian).

Contoh Anda di atas dapat ditulis menggunakan mapseperti ini:

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

Efek yang sama tetapi injectlebih ringkas di sini.

Anda akan sering menemukan tugas terjadi di mapblok, sedangkan evaluasi terjadi di injectblok.

Metode mana yang Anda pilih tergantung pada ruang lingkup yang Anda inginkan result. Kapan tidak menggunakannya akan menjadi sesuatu seperti ini:

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

Anda mungkin seperti semua, "Lihat saya, saya baru saja menggabungkan semuanya menjadi satu baris," tetapi Anda juga mengalokasikan memori untuk sementara xsebagai variabel awal yang tidak diperlukan karena Anda sudah harus resultbekerja dengannya.

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

setara dengan yang berikut:

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
Fred Willmore
sumber
3

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

Dalam bahasa Inggris biasa, Anda akan melalui (iterasi) melalui array ini ( [1,2,3,4]). Anda akan mengulangi melalui array ini 4 kali, karena ada 4 elemen (1, 2, 3, dan 4). Metode suntikan memiliki 1 argumen (angka 0), dan Anda akan menambahkan argumen itu ke elemen 1 (0 + 1. Ini sama dengan 1). 1 disimpan di "hasil". Kemudian Anda menambahkan hasil itu (yaitu 1) ke elemen berikutnya (1 + 2. Ini adalah 3). Ini sekarang akan disimpan sebagai hasilnya. Terus: 3 + 3 sama dengan 6. Dan akhirnya, 6 + 4 sama dengan 10.

Maddie
sumber
2

Kode ini tidak memungkinkan kemungkinan tidak melewati nilai awal, tetapi dapat membantu menjelaskan apa yang terjadi.

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
Andrew Grimm
sumber
1

Mulai di sini dan kemudian tinjau semua metode yang mengambil blok. http://ruby-doc.org/core-2.3.3/Enumerable.html#method-i-inject

Apakah itu blok yang membingungkan Anda atau mengapa Anda memiliki nilai dalam metode ini? Pertanyaan yang bagus. Apa metode operator di sana?

result.+

Seperti apa awalnya?

#inject(0)

Bisakah kita melakukan ini?

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

Apakah ini berhasil?

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

Anda lihat saya sedang membangun ide bahwa itu hanya menjumlahkan semua elemen array dan menghasilkan nomor dalam memo yang Anda lihat di dokumen.

Anda selalu dapat melakukan ini

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

untuk melihat enumerable dari array iterasi melalui. Itu ide dasarnya.

Hanya saja menyuntikkan atau mengurangi memberi Anda memo atau akumulator yang dikirim.

Kami bisa mencoba untuk mendapatkan hasil

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

tetapi tidak ada yang kembali jadi ini hanya bertindak sama seperti sebelumnya

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

di blok elemen inspektur.

Douglas G. Allen
sumber
1

Ini adalah penjelasan yang sederhana dan cukup mudah dimengerti:

Lupakan "nilai awal" karena agak membingungkan di awal.

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

Anda dapat memahami hal di atas sebagai: Saya menyuntikkan "mesin tambah" di antara 1,2,3,4. Artinya, itu adalah 1 ♫ 2 ♫ 3 ♫ 4 dan ♫ adalah mesin tambahan, jadi itu sama dengan 1 + 2 + 3 + 4, dan itu adalah 10.

Anda benar-benar dapat menyuntikkan +di antara mereka:

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

dan itu seperti, menyuntikkan +di antara 1,2,3,4, membuatnya 1 + 2 + 3 + 4 dan itu adalah 10. Ini :+adalah cara Ruby menentukan +dalam bentuk simbol.

Ini cukup mudah dimengerti dan intuitif. Dan jika Anda ingin menganalisis cara kerjanya langkah demi langkah, itu seperti: mengambil 1 dan 2, dan sekarang menambahkannya, dan ketika Anda memiliki hasilnya, simpan dulu (yang 3), dan sekarang, selanjutnya disimpan nilai 3 dan elemen array 3 melalui proses a + b, yaitu 6, dan sekarang menyimpan nilai ini, dan sekarang 6 dan 4 melalui proses a + b, dan 10. Anda pada dasarnya melakukan

((1 + 2) + 3) + 4

dan 10. "Nilai awal" 0hanyalah "basis" untuk memulai. Dalam banyak kasus, Anda tidak membutuhkannya. Bayangkan jika Anda membutuhkan 1 * 2 * 3 * 4 dan itu

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

dan itu dilakukan. Anda tidak perlu "nilai awal" 1untuk melipatgandakan semuanya 1.

nonopolaritas
sumber
0

Ada bentuk lain dari metode .inject () Itu sangat membantu [4,5] .inject (&: +) Itu akan menjumlahkan semua elemen area

Hasanin Alsabounchi
sumber
0

Hanya saja , reduceatau fold, jika Anda terbiasa dengan bahasa lain.

Nick
sumber
-1

Apakah sama dengan ini:

[1,2,3,4].inject(:+)
=> 10
Kata Maadan
sumber
Meskipun ini faktual, itu tidak menjawab pertanyaan.
Mark Thomas