2009-04-02 36 views
92
[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ý cách số 10 có thể trở thành kết quả. Liệu ai đó có thể giải thích những gì đang xảy ra ở đây?Cần giải thích đơn giản về phương pháp tiêm

+1

Xem [Wikipedia: Fold (hàm bậc cao hơn)] (http://en.wikipedia.org/wiki/Fold_%28higher-order_function%29): tiêm là "gấp trái", mặc dù (không may) thường với các tác dụng phụ trong việc sử dụng Ruby. – user2864740

Trả lời

146

Bạn có thể nghĩ đối số khối đầu tiên là bộ tích lũy: kết quả của mỗi lần chạy khối được lưu trữ trong bộ tích lũy và sau đó được chuyển đến lần thực thi 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 trình tích lũy, kết quả là 0. Mỗi lần chạy của khối sẽ thêm số cho sẵn vào tổng hiện tại và sau đó lưu kết quả trở lại vào bộ tích lũy. Cuộc gọi khối tiếp theo có giá trị mới này, thêm vào, lưu trữ lại và lặp lại.

Vào cuối của quá trình, tiêm lợi nhuận accumulator, mà trong trường hợp này là tổng hợp của tất cả các giá trị trong mảng, hoặc 10

Dưới đây là một ví dụ đơn giản để tạo một hash từ một loạt các các đối tượng, được khóa bằng 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 sẽ đặt giá trị mặc định của chúng tôi thành một băm rỗng, sau đó điền vào mỗi lần khối thực hiện. Lưu ý rằng chúng ta phải trả về giá trị băm như là dòng cuối cùng của khối, vì kết quả của khối sẽ được lưu trữ trở lại trong bộ tích lũy.

+1

Cảm ơn lời giải thích tuyệt vời và ví dụ băm - điều đó thực sự đã làm rõ. –

+0

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

+0

@Projjol 'kết quả + giải thích' là cả phép biến đổi thành bộ tích lũy và giá trị trả về. Đó là dòng cuối cùng trong khối làm cho nó trở lại tiềm ẩn. –

18

Các lặp mã trên bốn yếu tố trong mảng và thêm các kết quả trước để các yếu tố hiện tại:

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

Tiêm áp dụng khối

result + element 

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

67

inject có giá trị bắt đầu bằng (ví dụ: 0 trong ví dụ của bạn) và một khối và nó sẽ chạy khối đó một lần cho từng phần tử trong danh sách.

  1. Trong lần lặp đầu tiên, nó chuyển 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ả lại (trong trường hợp này là result + element).
  2. Sau đó chạy lại khối, chuyển kết quả từ lần lặp đầu tiên làm đối số đầu tiên 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à để cho biết cách hoạt động của từng bước, ví dụ của bạn; đây là một bộ tưởng tượng các bước thể hiện như thế nào kết quả này có thể được đánh giá:

[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 
+0

Cảm ơn bạn đã viết 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 liệu bạn có nghĩa là sơ đồ dưới đây là cách mà phương thức tiêm được thực hiện bên dưới về những gì được truyền làm đố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ộ bước tưởng tượng; nó thể hiện cấu trúc cơ bản, nhưng không thể thực hiện chính xác. –

+0

Biểu hiện trực quan nhất về tiêm mà tôi từng thấy. – scaryguy

13

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

[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ử, tôi sẽ đợi.

Khi không có đối số nào được chuyển đến khi tiêm, hai thành phần đầu tiên được chuyể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 xung quanh, do đó, một cuộc gọi ít hơn được thực hiện cho khối.

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 xảy 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 
11

Số bạn đặt bên trong của bạn() của bơm đại diện cho một vị trí bắt đầu, nó có thể là 0 hoặc 1000. Bên trong ống bạn có hai giữ chỗ | x, y |. x = số bao giờ bạn có bên trong .inject ('x'), và secound đạ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

-1

phải là giống như thế này:

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

Trong khi điều này là thực tế, nó không trả lời câu hỏ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 
5

tldr;inject khác với map theo một cách quan trọng: inject trả về giá trị của lần thực thi cuối cùng của khối trong khi map trả về mảng mà nó lặp lại.

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

ví dụ trên của bạn có thể được viết bằng map như thế này:

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

Cùng hiệu lực thi hành nhưng inject ngắn gọn hơn ở đây.

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

Phương pháp bạn chọn tùy thuộc vào phạm vi bạn muốn cho result. Khi nào thì 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ả, "Lookie tôi, tôi chỉ kết hợp tất cả vào một dòng," nhưng bạn cũng tạm giao bộ nhớ cho x như một biến đầu không cần thiết vì bạn đã có result để làm việc.

3

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

Trong tiếng Anh đơn giản, bạn đang phải trải qua (lặp lại) thông qua mảng này ([1,2,3,4]). Bạn sẽ lặp qua mảng này 4 lần, vì có 4 phần tử (1, 2, 3 và 4). Phương thức inject có 1 đối số (số 0), và bạn sẽ thêm đối số đó vào phần tử thứ nhất (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 giờ đây sẽ được lưu dưới dạng kết quả. Tiếp tục đi: 3 + 3 bằng 6. Và cuối cùng, 6 + 4 tương đương 10

13

Cú pháp đối với phương pháp bơm như sau:

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

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

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

cung cấp số làm đầu ra.

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

result = 0 Các zero đến từ bơm (giá trị) là 0

element = 1 Nó là phần tử đầu tiên của mảng.

Okey !!! Vì vậy, hãy bắt đầu tìm hiểu các 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 Bold-Italic Giá trị là các phần tử lấy từ mảng và chỉ đơn giản là Giá trị Bold là giá trị kết quả.

Tôi hy vọng bạn hiểu được phương pháp của phương pháp #inject của #ruby.

+1

Đây là câu trả lời rất hay! –

0

Bắt đầu tại đây và sau đó xem lại 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 gây nhầm lẫn bạn hoặc lý do bạn có giá trị trong phương pháp không? Câu hỏi hay. Phương thức vận hành là gì?

result.+ 

Bắt đầu như thế nào?

#inject(0) 

Chúng ta có thể làm việc này không?

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

Tính năng này 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ỉ tổng hợp tất cả các phần tử của mảng và tạo một số trong bản ghi nhớ mà bạn thấy trong 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 đếm được của mảng được lặp qua. Đó là ý tưởng cơ bản.

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

Chúng ta 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 trở lại vì vậy đây chỉ đóng vai trò giống như trước

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

trong khối yếu tố thanh tra.

0

Có một hình thức .inject() Đó là phương pháp rất hữu ích [4,5] .inject (&: +) Điều đó sẽ cộng tất cả các yếu tố của khu vực

0

Nó chỉ reduce hoặc fold , nếu bạn quen thuộc với các ngôn ngữ khác.

Các vấn đề liên quan