[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10
ฉันกำลังดูรหัสนี้ แต่สมองของฉันไม่ได้ลงทะเบียนว่าหมายเลข 10 จะกลายเป็นผลลัพธ์ได้อย่างไร บางคนจะอธิบายว่าเกิดอะไรขึ้นที่นี่
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10
ฉันกำลังดูรหัสนี้ แต่สมองของฉันไม่ได้ลงทะเบียนว่าหมายเลข 10 จะกลายเป็นผลลัพธ์ได้อย่างไร บางคนจะอธิบายว่าเกิดอะไรขึ้นที่นี่
คำตอบ:
คุณสามารถคิดว่าอาร์กิวเมนต์แรกของบล็อกเป็นตัวสะสม: ผลลัพธ์ของการรันแต่ละครั้งของบล็อกจะถูกเก็บไว้ในตัวสะสมแล้วส่งต่อไปยังการดำเนินการครั้งต่อไปของบล็อก ในกรณีของรหัสที่แสดงด้านบนคุณกำลังเริ่มต้นการสะสมผลลัพธ์เป็น 0 แต่ละการรันของบล็อกจะเพิ่มจำนวนที่กำหนดให้กับผลรวมปัจจุบันแล้วเก็บผลลัพธ์กลับเข้าไปในตัวสะสม การบล็อกครั้งถัดไปมีค่าใหม่เพิ่มเข้าไปเก็บไว้อีกครั้งและทำซ้ำ
ในตอนท้ายของกระบวนการฉีดคืนค่าแอคคิวมูเลเตอร์ซึ่งในกรณีนี้คือผลรวมของค่าทั้งหมดในอาร์เรย์หรือ 10
นี่เป็นอีกตัวอย่างง่ายๆในการสร้างแฮชจากอาเรย์ของวัตถุโดยใช้การแทนค่าสตริง:
[1,"a",Object.new,:hi].inject({}) do |hash, item|
hash[item.to_s] = item
hash
end
ในกรณีนี้เรากำลังเริ่มต้นการสะสมของเราเป็นแฮชเปล่าแล้วเติมมันทุกครั้งที่บล็อกดำเนินการ โปรดสังเกตว่าเราต้องคืนค่าแฮชเป็นบรรทัดสุดท้ายของบล็อกเนื่องจากผลลัพธ์ของบล็อกจะถูกเก็บไว้ในตัวสะสม
result + explanation
เป็นทั้งการแปลงเป็นตัวสะสมและค่าตอบแทน มันเป็นบรรทัดสุดท้ายในบล็อกที่ทำให้มันกลับมาโดยปริยาย
inject
รับค่าเริ่มต้นด้วย ( 0
ในตัวอย่างของคุณ) และบล็อกและมันรันบล็อกนั้นหนึ่งครั้งสำหรับแต่ละองค์ประกอบของรายการ
result + element
)วิธีที่ง่ายที่สุดในการอธิบายสิ่งนี้อาจแสดงให้เห็นว่าแต่ละขั้นตอนทำงานอย่างไรสำหรับตัวอย่างของคุณ นี่เป็นชุดของขั้นตอนจินตภาพที่แสดงว่าผลลัพธ์นี้สามารถประเมินได้อย่างไร:
[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 (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
รหัสวนซ้ำไปตามองค์ประกอบทั้งสี่ภายในอาร์เรย์และเพิ่มผลลัพธ์ก่อนหน้านี้ให้กับองค์ประกอบปัจจุบัน:
สิ่งที่พวกเขาพูด แต่โปรดทราบว่าคุณไม่จำเป็นต้องให้ "ค่าเริ่มต้น" เสมอไป:
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10
เป็นเช่นเดียวกับ
[1, 2, 3, 4].inject { |result, element| result + element } # => 10
ลองฉันจะรอ
เมื่อไม่มีข้อโต้แย้งถูกส่งผ่านไปยังสององค์ประกอบแรกจะถูกส่งผ่านไปยังการทำซ้ำครั้งแรก ในตัวอย่างด้านบนผลลัพธ์คือ 1 และองค์ประกอบคือ 2 ในครั้งแรกดังนั้นการโทรน้อยลงหนึ่งบล็อก
จำนวนที่คุณใส่ไว้ใน () ของการฉีดของคุณแทนตำแหน่งเริ่มต้นอาจเป็น 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
การฉีดใช้บล็อก
result + element
ไปยังแต่ละรายการในอาร์เรย์ สำหรับรายการถัดไป ("องค์ประกอบ") ค่าที่ส่งคืนจากบล็อกคือ "ผลลัพธ์" วิธีที่คุณเรียกมัน (พร้อมพารามิเตอร์) "ผลลัพธ์" เริ่มต้นด้วยค่าของพารามิเตอร์นั้น ดังนั้นเอฟเฟกต์จึงเพิ่มองค์ประกอบเข้าไป
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
ทำงานด้วย
[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
[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
รหัสนี้ไม่อนุญาตให้มีความเป็นไปได้ที่จะไม่ผ่านค่าเริ่มต้น แต่อาจช่วยอธิบายสิ่งที่เกิดขึ้น
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
เริ่มที่นี่แล้วทบทวนวิธีการทั้งหมดที่ใช้บล็อก 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,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
และมันจะทำ คุณไม่จำเป็นต้องเป็น "ค่าเริ่มต้น" ของการคูณสิ่งทั้งหมดที่มี1
1
มีรูปแบบอื่นของ. inject () วิธีการที่เป็นประโยชน์มาก [4,5]. inject (&: +) ที่จะรวมองค์ประกอบทั้งหมดของพื้นที่
เป็นเพียงreduce
หรือfold
ถ้าคุณคุ้นเคยกับภาษาอื่น
เหมือนกับสิ่งนี้:
[1,2,3,4].inject(:+)
=> 10