ต้องการคำอธิบายง่ายๆของวิธีการฉีด


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

ฉันกำลังดูรหัสนี้ แต่สมองของฉันไม่ได้ลงทะเบียนว่าหมายเลข 10 จะกลายเป็นผลลัพธ์ได้อย่างไร บางคนจะอธิบายว่าเกิดอะไรขึ้นที่นี่

ruby  syntax 

3
ดูWikipedia: Fold (ฟังก์ชันลำดับสูงกว่า) : inject เป็น "fold left" แม้ว่า (น่าเสียดาย) บ่อยครั้งที่มีผลข้างเคียงในการใช้ Ruby
2864740

คำตอบ:


208

คุณสามารถคิดว่าอาร์กิวเมนต์แรกของบล็อกเป็นตัวสะสม: ผลลัพธ์ของการรันแต่ละครั้งของบล็อกจะถูกเก็บไว้ในตัวสะสมแล้วส่งต่อไปยังการดำเนินการครั้งต่อไปของบล็อก ในกรณีของรหัสที่แสดงด้านบนคุณกำลังเริ่มต้นการสะสมผลลัพธ์เป็น 0 แต่ละการรันของบล็อกจะเพิ่มจำนวนที่กำหนดให้กับผลรวมปัจจุบันแล้วเก็บผลลัพธ์กลับเข้าไปในตัวสะสม การบล็อกครั้งถัดไปมีค่าใหม่เพิ่มเข้าไปเก็บไว้อีกครั้งและทำซ้ำ

ในตอนท้ายของกระบวนการฉีดคืนค่าแอคคิวมูเลเตอร์ซึ่งในกรณีนี้คือผลรวมของค่าทั้งหมดในอาร์เรย์หรือ 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

injectรับค่าเริ่มต้นด้วย ( 0ในตัวอย่างของคุณ) และบล็อกและมันรันบล็อกนั้นหนึ่งครั้งสำหรับแต่ละองค์ประกอบของรายการ

  1. ในการวนซ้ำครั้งแรกมันจะส่งผ่านค่าที่คุณให้ไว้เป็นค่าเริ่มต้นและองค์ประกอบแรกของรายการและจะบันทึกค่าที่บล็อกของคุณส่งคืน (ในกรณีนี้result + element)
  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

ขอบคุณที่เขียนขั้นตอน สิ่งนี้ช่วยได้มาก แม้ว่าฉันจะสับสนเล็กน้อยเกี่ยวกับว่าคุณหมายถึงแผนภาพด้านล่างเป็นวิธีการฉีดจะดำเนินการภายใต้ในแง่ของสิ่งที่ผ่านไปเป็นข้อโต้แย้งเพื่อฉีด

2
แผนภาพด้านล่างจะขึ้นอยู่กับวิธีการที่มันอาจจะนำมาใช้; มันไม่จำเป็นต้องถูกนำมาใช้อย่างถูกต้องด้วยวิธีนี้ นั่นเป็นเหตุผลที่ฉันบอกว่ามันเป็นขั้นตอนในจินตนาการ มันแสดงให้เห็นถึงโครงสร้างพื้นฐาน แต่ไม่ใช่การใช้งานที่แน่นอน
Brian Campbell

27

ไวยากรณ์สำหรับวิธีการฉีดเป็นดังนี้:

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

ลองแก้ตัวอย่างข้างบนนั่นคือ

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

ซึ่งให้10เป็นผลลัพธ์

ดังนั้นก่อนที่จะเริ่มลองมาดูกันว่าค่าที่เก็บไว้ในแต่ละตัวแปรคืออะไร:

ผล = 0ศูนย์มาจากการฉีด (ค่า) ซึ่งเป็น 0

องค์ประกอบ = 1มันเป็นองค์ประกอบแรกของอาร์เรย์

Okey !!! ดังนั้นเรามาเริ่มทำความเข้าใจกับตัวอย่างข้างต้น

ขั้นตอนที่ 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


19

รหัสวนซ้ำไปตามองค์ประกอบทั้งสี่ภายในอาร์เรย์และเพิ่มผลลัพธ์ก่อนหน้านี้ให้กับองค์ประกอบปัจจุบัน:

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

15

สิ่งที่พวกเขาพูด แต่โปรดทราบว่าคุณไม่จำเป็นต้องให้ "ค่าเริ่มต้น" เสมอไป:

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

เป็นเช่นเดียวกับ

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

ลองฉันจะรอ

เมื่อไม่มีข้อโต้แย้งถูกส่งผ่านไปยังสององค์ประกอบแรกจะถูกส่งผ่านไปยังการทำซ้ำครั้งแรก ในตัวอย่างด้านบนผลลัพธ์คือ 1 และองค์ประกอบคือ 2 ในครั้งแรกดังนั้นการโทรน้อยลงหนึ่งบล็อก


14

จำนวนที่คุณใส่ไว้ใน () ของการฉีดของคุณแทนตำแหน่งเริ่มต้นอาจเป็น 0 หรือ 1,000 ภายในท่อที่คุณมีที่วางสองตำแหน่ง | 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; injectแตกต่างจากmapในวิธีสำคัญหนึ่ง: 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 }

คุณอาจเป็นเหมือนทุกคน "มองฉันฉันเพิ่งรวมมันทั้งหมดไว้ในบรรทัดเดียว" แต่คุณยังจัดสรรหน่วยความจำชั่วคราวเพื่อ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 ครั้งเนื่องจากมี 4 องค์ประกอบ (1, 2, 3 และ 4) เมธอด inject มี 1 อาร์กิวเมนต์ (หมายเลข 0) และคุณจะเพิ่มอาร์กิวเมนต์นั้นไปยังองค์ประกอบที่ 1 (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

คุณสามารถฉีด a เข้าไปข้าง+ในได้:

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

และมันก็เหมือนกับ, ฉีด a เข้าไป+ในระหว่าง 1,2,3,4, ทำให้มันเป็น 1 +2 + 3 + 4 และก็คือ 10. :+วิธีของรูบี้คือการระบุ+ในรูปแบบของสัญลักษณ์

มันค่อนข้างเข้าใจง่ายและเข้าใจง่าย และถ้าคุณต้องการวิเคราะห์วิธีการทำงานทีละขั้นตอนมันเป็นเช่น: การ 1 และ 2 และตอนนี้เพิ่มพวกเขาและเมื่อคุณมีผลให้เก็บไว้ก่อน (ซึ่งคือ 3) และตอนนี้ถัดไปจะถูกเก็บไว้ ค่า 3 และองค์ประกอบอาร์เรย์ 3 ที่ผ่านกระบวนการ a + b ซึ่งก็คือ 6 และตอนนี้เก็บค่านี้และตอนนี้ 6 และ 4 จะผ่านกระบวนการ a + b และ 10 คุณกำลังทำ

((1 + 2) + 3) + 4

และคือ 10 "ค่าเริ่มต้น" 0เป็นเพียง "ฐาน" เพื่อเริ่มต้นด้วย ในหลายกรณีคุณไม่ต้องการมัน ลองนึกภาพถ้าคุณต้องการ 1 * 2 * 3 * 4 และเป็น

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

และมันจะทำ คุณไม่จำเป็นต้องเป็น "ค่าเริ่มต้น" ของการคูณสิ่งทั้งหมดที่มี11


0

มีรูปแบบอื่นของ. inject () วิธีการที่เป็นประโยชน์มาก [4,5]. inject (&: +) ที่จะรวมองค์ประกอบทั้งหมดของพื้นที่



โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.