ยินดีต้อนรับกลับสู่ Ruby Magic รุ่นอื่น! ปีที่แล้ว เราได้เรียนรู้เกี่ยวกับ Enumerable
. ของ Ruby โมดูล ซึ่งมีวิธีการที่คุณใช้เมื่อทำงานกับวัตถุที่นับได้ เช่น อาร์เรย์ ช่วง และแฮช
ย้อนกลับไปตอนนั้น เราได้สร้าง LinkedList
class เพื่อแสดงวิธีการสร้างอ็อบเจกต์ที่นับได้โดยใช้ #each
วิธีการกับมัน โดยใส่ Enumerable
โมดูล เราสามารถเรียกวิธีการเช่น #count
, #map
และ #select
ในรายการที่เชื่อมโยงโดยไม่ต้องดำเนินการเอง
เราได้เรียนรู้วิธีใช้การนับแล้ว แต่ทำงานอย่างไร ส่วนหนึ่งของความมหัศจรรย์ในการนับจำนวนใน Ruby มาจากการใช้งานภายใน ซึ่งทั้งหมดมีพื้นฐานมาจาก #each
เดียว วิธีการและยังอนุญาตให้มีการแจงนับแบบผูกมัด
วันนี้เราจะมาเรียนรู้วิธีใน Enumerable
คลาสถูกนำไปใช้และวิธี Enumerator
ออบเจ็กต์อนุญาตให้ผูกมัดวิธีการแจงนับ
ในขณะที่คุณคุ้นเคย เราจะเจาะลึกโดยใช้ Enumerable
เวอร์ชันของเราเอง โมดูลและ Enumerator
ระดับ. ดังนั้น สวมหมวกนิรภัยแล้วไปลุยกันเลย!
รายการที่เชื่อมโยง
ก่อนที่เราจะเริ่ม เรามาเริ่มด้วยเวอร์ชันใหม่ของคลาสรายการลิงก์ที่เราเขียนไว้ก่อนหน้านี้
class LinkedList
def initialize(head = nil, *rest)
@head = head
if rest.first.is_a?(LinkedList)
@tail = rest.first
elsif rest.any?
@tail = LinkedList.new(*rest)
end
end
def <<(head)
@head ? LinkedList.new(head, self) : LinkedList.new(head)
end
def inspect
[@head, @tail].compact
end
def each(&block)
yield @head if @head
@tail.each(&block) if @tail
end
end
ต่างจากเวอร์ชันก่อนหน้า การใช้งานนี้อนุญาตให้สร้างรายการว่าง รวมทั้งรายการที่มีมากกว่าสองรายการ เวอร์ชันนี้ยังอนุญาตให้ส่งรายการที่เชื่อมโยงเป็นส่วนท้ายเมื่อเริ่มต้นรายการอื่น
irb> LinkedList.new
=> []
irb> LinkedList.new(1)
=> [1]
irb> LinkedList.new(1, 2)
=> [1,[2]]
irb> LinkedList.new(1, 2, 3)
=> [1,[2,[3]]]
irb> LinkedList.new(1, LinkedList.new(2, 3))
=> [1,[2,[3]]]
irb> LinkedList.new(1, 2, LinkedList.new(3))
=> [1,[2,[3]]]
ก่อนหน้านี้ LinkedLIst
. ของเรา คลาสรวม Enumerable
โมดูล. เมื่อทำการแมปเหนือวัตถุโดยใช้หนึ่งใน Enumerable
วิธีการของผลลัพธ์จะถูกเก็บไว้ในอาร์เรย์ ครั้งนี้ เราจะใช้เวอร์ชันของเราเองเพื่อให้แน่ใจว่าเมธอดของเราส่งคืนรายการที่เชื่อมโยงใหม่แทน
วิธีการนับได้
Enumerable
ของทับทิม โมดูลมาพร้อมกับวิธีการแจงนับเช่น #map
, #count
และ #select
. โดยการนำ #each
. ไปใช้ เมธอดและรวม Enumerable
โมดูลในชั้นเรียนของเรา เราจะสามารถใช้วิธีการเหล่านั้นได้โดยตรงในรายการที่เชื่อมโยงของเรา
เราจะใช้ DIYEnumerable
. แทน และนำเข้าสิ่งนั้นแทนเวอร์ชันของ Ruby นี่ไม่ใช่สิ่งที่คุณมักจะทำ แต่จะให้ข้อมูลเชิงลึกที่ชัดเจนว่าการแจงนับทำงานภายในอย่างไร
เริ่มต้นด้วย #count
. แต่ละวิธีที่นำเข้าได้ใน Enumerable
คลาสใช้ #each
วิธีที่เราใช้ใน LinkedList
class วนรอบวัตถุเพื่อคำนวณผลลัพธ์
module DIYEnumerable
def count
result = 0
each { |element| result += 1 }
result
end
end
ในตัวอย่างนี้ เราได้ติดตั้ง #count
วิธีการใหม่ DIYEnumerable
โมดูลที่เราจะรวมไว้ในรายการที่เชื่อมโยงของเรา มันเริ่มตัวนับที่ศูนย์และเรียก #each
วิธีการเพิ่มหนึ่งไปยังตัวนับสำหรับทุกลูป หลังจากวนซ้ำองค์ประกอบทั้งหมด เมธอดจะส่งกลับตัวนับผลลัพธ์
module DIYEnumerable
# ...
def map
result = LinkedList.new
each { |element| result = result << yield(element) }
result
end
end
#map
วิธีการดำเนินการในทำนองเดียวกัน แทนที่จะเก็บตัวนับ จะใช้ตัวสะสม ซึ่งเริ่มเป็นรายการว่าง เราจะวนซ้ำองค์ประกอบทั้งหมดในรายการ และให้บล็อกที่ส่งผ่านในแต่ละองค์ประกอบ ผลลัพธ์ของแต่ละผลตอบแทนจะถูกผนวกเข้ากับรายการสะสม
เมธอดจะคืนค่าตัวสะสมหลังจากวนลูปองค์ประกอบทั้งหมดในรายการอินพุต
class LinkedList
include DIYEnumerable
#...
end
หลังจากรวม DIYEnumerable
. แล้ว ใน LinkedList
. ของเรา เราสามารถทดสอบ #count
. ที่เพิ่งเพิ่มใหม่ของเรา และ #map
วิธีการ
irb> list = LinkedList.new(73, 12, 42)
=> [73, [12, [42]]]
irb> list.count
=> 3
irb> list.map { |element| element * 10 }
=> [420, [120, [730]]]
ทั้งสองวิธีได้ผล! #count
วิธีการนับรายการในรายการอย่างถูกต้องและ #map
เมธอดรันบล็อกสำหรับแต่ละรายการและส่งคืนรายการที่อัปเดต
รายการย้อนกลับ
อย่างไรก็ตาม #map
วิธีดูเหมือนว่าจะได้กลับรายการ ที่เข้าใจได้เป็น #<<
วิธีการในคลาสรายชื่อที่เชื่อมโยงของเราจะเพิ่มรายการลงในรายการแทนการต่อท้ายรายการซึ่งเป็นคุณลักษณะของลักษณะแบบเรียกซ้ำของรายการที่เชื่อมโยง
สำหรับสถานการณ์ที่จำเป็นต้องรักษาลำดับของรายการไว้ เราต้องการวิธีที่จะย้อนกลับรายการเมื่อทำการแมปทับรายการนั้น Ruby ใช้งาน Enumerable#reverse_each
ซึ่งวนรอบวัตถุในทางกลับกัน สิ่งที่ฟังดูเหมือนเป็นทางออกที่ดีสำหรับปัญหาของเรา น่าเศร้าที่เราไม่สามารถใช้วิธีดังกล่าวได้เนื่องจากรายการของเราซ้อนกัน เราไม่รู้ว่ารายการยาวแค่ไหนจนกว่าเราจะวนซ้ำทั้งหมด
แทนที่จะบล็อกรายการในทางกลับกัน เราจะเพิ่มเวอร์ชันของ #reverse_each
ที่ทำสองขั้นตอนนี้ อันดับแรกจะวนซ้ำรายการเพื่อย้อนกลับโดยสร้างรายการใหม่ หลังจากนั้นจะทำการบล็อกเหนือรายการที่กลับรายการ
module DIYEnumerable
# ...
def reverse_each(&block)
list = LinkedList.new
each { |element| list = list << element }
list.each(&block)
end
def map
result = LinkedList.new
reverse_each { |element| result = result << yield(element) }
result
end
end
ตอนนี้ เราจะใช้ #reverse_each
ใน #map
. ของเรา เพื่อให้แน่ใจว่าส่งคืนในลำดับที่ถูกต้อง
irb> list = LinkedList.new(73, 12, 42)
=> [73, [12, [42]]]
irb> list.map { |element| element * 10 }
=> [730, [120, [420]]]
มันได้ผล! เมื่อใดก็ตามที่เราเรียก #map
. ของเรา ในรายการที่เชื่อมโยง เราจะได้รายการใหม่กลับมาอยู่ในลำดับเดียวกันกับรายการเดิม
การแจงนับโยงกับการแจงนับ
ผ่าน #each
วิธีการดำเนินการในคลาสรายการที่เชื่อมโยงของเราและรวม DIYEnumerator
ตอนนี้เราสามารถวนซ้ำทั้งสองวิธีและแมปรายการเชื่อมโยงได้
irb> list.each { |x| p x }
73
12
42
irb> list.reverse_each { |x| p x }
42
12
73
irb> list.reverse_each.map { |x| x * 10 }
=> [730, [120, [420]]]
=> [420, [120, [730]]]
แต่ถ้าเราจำเป็นต้อง map มากกว่ารายการในทางกลับกัน? เนื่องจากตอนนี้เรากลับรายการก่อนที่จะทำการแมปทับ รายการนั้นจะส่งคืนในลำดับเดียวกันกับรายการเดิมเสมอ เราได้ติดตั้งทั้ง #reverse_each
. แล้ว และ #map
ดังนั้นเราจึงควรจะสามารถเชื่อมโยงเข้าด้วยกันเพื่อให้สามารถทำแผนที่ย้อนกลับได้ โชคดีที่ Enumerator
ของ Ruby ชั้นเรียนสามารถช่วยได้
ครั้งที่แล้ว เราแน่ใจว่าได้เรียก Kernel#to_enum
ถ้า LinkedList#each
เมธอดถูกเรียกโดยไม่มีการบล็อก อนุญาตให้ผูกเมธอดที่นับได้โดยการส่งคืน Enumerator
วัตถุ. หากต้องการทราบวิธีการ Enumerator
งานของชั้นเรียน เราจะดำเนินการในเวอร์ชันของเราเอง
class DIYEnumerator
include DIYEnumerable
def initialize(object, method)
@object = object
@method = method
end
def each(&block)
@object.send(@method, &block)
end
end
เช่นเดียวกับ Enumerator
ของ Ruby คลาสตัวแจงนับของเราเป็นตัวห่อหุ้มรอบเมธอดบนออบเจกต์ เราสามารถเชื่อมโยงวิธีการแจงนับได้ด้วยการเดือดปุด ๆ จนถึงวัตถุที่ถูกห่อ
ใช้งานได้เพราะ DIYEnumerator
อินสแตนซ์สามารถนับได้เอง มันใช้ #each
โดยการเรียกอ็อบเจ็กต์ที่ห่อหุ้ม และรวม DIYEnumerable
โมดูลเพื่อให้สามารถเรียกใช้วิธีการนับทั้งหมดได้
เราจะส่งคืนตัวอย่าง DIYEnumerator
. ของเรา class หากไม่มีการส่งบล็อกไปยัง LinkedList#each
วิธีการ
class LinkedList
# ...
def each(&block)
if block_given?
yield @head
@tail.each(&block) if @tail
else
DIYEnumerator.new(self, :each)
end
end
end
ด้วยการใช้ตัวแจงนับของเราเอง ตอนนี้เราสามารถนับลูกโซ่เพื่อให้ได้ผลลัพธ์ในลำดับเดิมโดยไม่ต้องผ่านบล็อกว่างไปยัง #reverse_each
เรียกเมธอด
irb> list = LinkedList.new(73, 12, 42)
=> [73, [12, [42]]]
irb> list.map { |element| element * 10 }
=> [420, [120, [730]]]
การแจกแจงอย่างกระตือรือร้นและขี้เกียจ
นี่เป็นบทสรุปของเราในการใช้งาน Enumerable
โมดูลและ Enumerator
ชั้นเรียนสำหรับตอนนี้ เราได้เรียนรู้วิธีนับจำนวนวิธีทำงาน และวิธีที่ตัวแจงนับช่วยในการแจงนับแบบลูกโซ่ด้วยการห่อออบเจกต์ที่นับได้
แม้ว่าจะมีปัญหาบางประการเกี่ยวกับแนวทางของเรา โดยธรรมชาติแล้ว การแจงนับคือ กระตือรือร้น หมายความว่าจะวนซ้ำรายการทันทีที่มีการเรียกหนึ่งในวิธีการนับได้ แม้ว่าในกรณีส่วนใหญ่จะเป็นเรื่องปกติ แต่การแมปทับรายการในทางกลับกันจะกลับรายการสองครั้ง ซึ่งไม่จำเป็น
เพื่อลดจำนวนลูป เราสามารถใช้ Enumerator::Lazy
เพื่อชะลอการวนซ้ำไปยังช่วงเวลาสุดท้าย และให้รายการที่ซ้ำกันย้อนกลับจะยกเลิกเอง
เราจะต้องบันทึกไว้สำหรับตอนต่อไปแม้ว่า ไม่อยากพลาดแล้วออกสำรวจผลงานภายในที่มีมนต์ขลังของ Ruby ต่อไปหรือไม่? สมัครรับจดหมายข่าวทางอีเมล Ruby Magic เพื่อรับบทความใหม่ส่งถึงกล่องจดหมายของคุณทันทีที่มีการเผยแพร่