Cần một lời giải thích đơn giản về phương pháp tiêm


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

Tôi đang xem mã này nhưng bộ não của tôi không đăng ký làm thế nào số 10 có thể trở thành kết quả. Ai đó có thể giải thích những gì đang xảy ra ở đây?

ruby  syntax 

3
Xem Wikipedia: Fold (chức năng bậc cao hơn) : chích là "gấp trái", mặc dù (không may) thường có tác dụng phụ trong việc sử dụng Ruby.
dùng2864740

Câu trả lời:


208

Bạn có thể nghĩ về đối số khối đầu tiên là một bộ tích lũy: kết quả của mỗi lần chạy của khối được lưu trữ trong bộ tích lũy và sau đó được chuyển sang lần thực hiện tiếp theo của khối. Trong trường hợp mã được hiển thị ở trên, bạn đang mặc định bộ tích lũy, kết quả là 0. Mỗi lần chạy của khối sẽ thêm số đã cho vào tổng hiện tại và sau đó lưu lại kết quả vào bộ tích. Cuộc gọi khối tiếp theo có giá trị mới này, thêm vào nó, lưu lại và lặp lại.

Khi kết thúc quá trình, tiêm trả về bộ tích lũy, trong trường hợp này là tổng của tất cả các giá trị trong mảng, hoặc 10.

Đây là một ví dụ đơn giản khác để tạo hàm băm từ một mảng các đối tượng, được khóa bởi biểu diễn chuỗi của chúng:

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

Trong trường hợp này, chúng tôi đang mặc định bộ tích lũy của mình thành một hàm băm trống, sau đó điền vào nó mỗi khi khối thực thi. Lưu ý rằng chúng ta phải trả về hàm băm là dòng cuối cùng của khối, vì kết quả của khối sẽ được lưu lại trong bộ tích lũy.


tuy nhiên, lời giải thích tuyệt vời trong ví dụ do OP đưa ra, những gì đang được trả về (như hàm băm nằm trong ví dụ của bạn). Nó kết thúc bằng kết quả + giải thích và nên có giá trị trả về, đúng không?
Projjol

1
@Projjol result + explanationlà cả chuyển đổi cho tích lũy và giá trị trả về. Đây là dòng cuối cùng trong khối làm cho nó trở lại ngầm.
KA01

87

injectlấy một giá trị để bắt đầu ( 0trong ví dụ của bạn) và một khối và nó chạy khối đó một lần cho mỗi thành phần của danh sách.

  1. Trong lần lặp đầu tiên, nó chuyển vào giá trị bạn đã cung cấp làm giá trị bắt đầu và phần tử đầu tiên của danh sách và nó lưu giá trị mà khối của bạn trả về (trong trường hợp này result + element).
  2. Sau đó, nó chạy lại khối, chuyển kết quả từ lần lặp đầu tiên làm đối số thứ nhất và phần tử thứ hai từ danh sách làm đối số thứ hai, một lần nữa lưu kết quả.
  3. Nó tiếp tục theo cách này cho đến khi nó đã tiêu thụ tất cả các yếu tố của danh sách.

Cách dễ nhất để giải thích điều này có thể là chỉ ra cách mỗi bước hoạt động, ví dụ của bạn; đây là một tập hợp các bước tưởng tượng cho thấy kết quả này có thể được đánh giá như thế nào:

[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

Cảm ơn đã viết ra các bước. Điều này đã giúp rất nhiều. Mặc dù tôi đã có một chút bối rối về việc bạn có nghĩa là sơ đồ bên dưới là cách phương thức tiêm được thực hiện bên dưới về mặt những gì được thông qua như là đối số để tiêm.

2
Sơ đồ dưới đây dựa trên cách nó có thể được thực hiện; nó không nhất thiết phải được thực hiện chính xác theo cách này. Đó là lý do tại sao tôi nói đó là một bộ các bước tưởng tượng; nó thể hiện cấu trúc cơ bản, nhưng không thực hiện chính xác.
Brian Campbell

27

Cú pháp của phương thức tiêm như sau:

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

Hãy giải quyết ví dụ trên tức là

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

cung cấp cho 10 là đầu ra.

Vì vậy, trước khi bắt đầu, hãy xem các giá trị được lưu trữ trong mỗi biến là gì:

result = 0 Số 0 đến từ chích (giá trị) bằng 0

phần tử = 1 Đây là phần tử đầu tiên của mảng.

Ôi !!! Vì vậy, hãy bắt đầu hiểu ví dụ trên

Bước 1 [1, 2, 3, 4].inject(0) { |0, 1| 0 + 1 }

Bước 2 [1, 2, 3, 4].inject(0) { |1, 2| 1 + 2 }

Bước 3 [1, 2, 3, 4].inject(0) { |3, 3| 3 + 3 }

Bước 4 [1, 2, 3, 4].inject(0) { |6, 4| 6 + 4 }

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

Ở đây các giá trị Bold-Italic là các phần tử tìm nạp từ mảng và Bold đơn giản giá trị là các giá trị kết quả.

Tôi hy vọng rằng bạn hiểu hoạt động của #injectphương pháp #ruby.


19

Mã lặp lại qua bốn phần tử trong mảng và thêm kết quả trước đó vào phần tử hiện tại:

  • 1 + 2 = 3
  • 3 + 3 = 6
  • 6 + 4 = 10

15

Những gì họ nói, nhưng cũng lưu ý rằng không phải lúc nào bạn cũng cần cung cấp "giá trị bắt đầu":

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

giống như

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

Hãy thử nó, tôi sẽ chờ.

Khi không có đối số nào được truyền vào để tiêm, hai phần tử đầu tiên được truyền vào lần lặp đầu tiên. Trong ví dụ trên, kết quả là 1 và phần tử là 2 lần đầu tiên, do đó, một cuộc gọi ít hơn được thực hiện cho khối.


14

Số bạn đặt bên trong () tiêm của bạn đại diện cho một nơi bắt đầu, nó có thể là 0 hoặc 1000. Bên trong các đường ống bạn có hai người giữ chỗ | x, y |. x = số bao giờ bạn có trong .inject ('x') và ẩn số đại diện cho mỗi lần lặp của đối tượng của bạn.

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

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


6

Tiêm áp dụng khối

result + element

đến từng mục trong mảng. Đối với mục tiếp theo ("phần tử"), giá trị được trả về từ khối là "kết quả". Cách bạn đã gọi nó (với một tham số), "kết quả" bắt đầu bằng giá trị của tham số đó. Vì vậy, hiệu quả là thêm các yếu tố lên.


6

tldr; injectkhác với mapmột cách quan trọng: injecttrả về giá trị của lần thực hiện cuối cùng của khối trong khimap trả về mảng được lặp lại.

Hơn thế nữa, giá trị của mỗi lần thực hiện khối được chuyển sang lần thực hiện tiếp theo thông qua tham số đầu tiên ( resulttrong trường hợp này) và bạn có thể khởi tạo giá trị đó ((0) phần).

Ví dụ trên của bạn có thể được viết bằng cách sử dụng mapnhư thế này:

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

Hiệu ứng tương tự nhưng injectngắn gọn hơn ở đây.

Bạn sẽ thường thấy một bài tập xảy ra trong mapkhối, trong khi đánh giá xảy ra trong injectkhối.

Phương pháp bạn chọn phụ thuộc vào phạm vi bạn muốn result. Khi không sử dụng nó sẽ là một cái gì đó như thế này:

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

Bạn có thể giống như tất cả, "Hãy nhìn tôi, tôi chỉ kết hợp tất cả thành một dòng", nhưng bạn cũng tạm thời phân bổ bộ nhớ cho xmột biến số không cần thiết vì bạn đã phải resultlàm việc với.


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

tương đương như sau:

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

Trong tiếng Anh đơn giản, bạn đang trải qua (lặp lại) thông qua mảng này ( [1,2,3,4]). Bạn sẽ lặp lại qua mảng này 4 lần, bởi vì có 4 phần tử (1, 2, 3 và 4). Phương thức tiêm có 1 đối số (số 0) và bạn sẽ thêm đối số đó vào phần tử thứ 1 (0 + 1. Điều này bằng 1). 1 được lưu trong "kết quả". Sau đó, bạn thêm kết quả đó (là 1) vào phần tử tiếp theo (1 + 2. Đây là 3). Điều này bây giờ sẽ được lưu lại như là kết quả. Tiếp tục đi: 3 + 3 bằng 6. Và cuối cùng, 6 + 4 bằng 10.


2

Mã này không cho phép khả năng không vượt qua giá trị bắt đầu, nhưng có thể giúp giải thích những gì đang diễn ra.

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

Bắt đầu ở đây và sau đó xem xét tất cả các phương pháp có khối. http://ruby-doc.org/core-2.3.3/Enumerable.html#method-i-inject

Đây có phải là khối làm bạn bối rối hoặc tại sao bạn có một giá trị trong phương thức? Câu hỏi hay mặc dù. Phương pháp toán tử ở đó là gì?

result.+

Nó bắt đầu như thế nào?

#inject(0)

Chúng ta có thể làm điều này?

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

Nó có hoạt động không?

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

Bạn thấy tôi đang xây dựng ý tưởng rằng nó chỉ đơn giản là tổng hợp tất cả các yếu tố của mảng và mang lại một số trong bản ghi nhớ mà bạn thấy trong các tài liệu.

Bạn luôn có thể làm điều này

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

để xem vô số các mảng được lặp qua. Đó là ý tưởng cơ bản.

Chỉ cần tiêm hoặc giảm cung cấp cho bạn một bản ghi nhớ hoặc bộ tích lũy được gửi đi.

Chúng tôi có thể cố gắng để có được một kết quả

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

nhưng không có gì trở lại vì vậy điều này chỉ hoạt động giống như trước đây

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

trong khối thanh tra phần tử.


1

Đây là một lời giải thích đơn giản và khá dễ hiểu:

Hãy quên đi "giá trị ban đầu" vì nó hơi khó hiểu khi bắt đầu.

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

Bạn có thể hiểu như trên: Tôi đang tiêm một "máy thêm" vào giữa 1,2,3,4. Có nghĩa, nó là 1 ♫ 2 ♫ 3 4 và là một máy thêm, vì vậy nó giống như 1 + 2 + 3 + 4, và nó là 10.

Bạn thực sự có thể tiêm một +giữa chúng:

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

và nó giống như, tiêm một +khoảng giữa 1,2,3,4, biến nó thành 1 + 2 + 3 + 4 và là 10. Đây :+là cách chỉ định của Ruby+ dưới dạng biểu tượng.

Điều này khá dễ hiểu và trực quan. Và nếu bạn muốn phân tích cách thức hoạt động của nó từng bước, thì giống như: lấy 1 và 2, và bây giờ thêm chúng, và khi bạn có kết quả, hãy lưu trữ nó trước (là 3), và bây giờ, tiếp theo là lưu trữ giá trị 3 và phần tử mảng 3 trải qua quá trình a + b, là 6 và hiện lưu trữ giá trị này, và bây giờ 6 và 4 trải qua quá trình a + b và là 10. Về cơ bản, bạn đang làm

((1 + 2) + 3) + 4

và là 10. "Giá trị ban đầu" 0chỉ là "cơ sở" để bắt đầu. Trong nhiều trường hợp, bạn không cần nó. Hãy tưởng tượng nếu bạn cần 1 * 2 * 3 * 4 và đó là

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

và nó đã được thực hiện. Bạn không cần "giá trị ban đầu" 1để nhân toàn bộ 1.


0

Có một dạng khác của phương thức .inject () Rất hữu ích [4,5] .inject (&: +) Điều đó sẽ cộng tất cả các phần tử của khu vực


0

Đó chỉ là reducehoặc fold, nếu bạn quen thuộc với các ngôn ngữ khác.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.