ถึงเวลาสำหรับ Ruby Magic อีกตอนแล้ว! คราวนี้ เราจะมาดูคุณสมบัติมหัศจรรย์ที่สุดชิ้นหนึ่งของ Ruby ซึ่งมีวิธีการส่วนใหญ่ที่คุณจะใช้เมื่อทำงานกับคลาสที่นับได้ของ Ruby เช่น Array
, Hash
และ Range
. ในกระบวนการนี้ เราจะเรียนรู้สิ่งที่คุณสามารถทำได้ด้วยวัตถุที่นับได้ วิธีแจงนับทำงาน และวิธีทำให้วัตถุสามารถนับได้โดยใช้วิธีเดียว
Enumerable
, #each
และ Enumerator
การแจงนับ หมายถึงการข้ามผ่านวัตถุ ใน Ruby เราเรียกวัตถุว่า นับได้ เมื่ออธิบายชุดของรายการและวิธีการวนรอบแต่ละรายการ
enumerables ในตัวได้รับคุณสมบัติการแจงนับโดยรวม Enumerable
โมดูลซึ่งมีวิธีการเช่น #include?
, #count
, #map
, #select
และ #uniq
, ท่ามกลางคนอื่น ๆ. เมธอดส่วนใหญ่ที่เกี่ยวข้องกับอาร์เรย์และแฮชไม่ได้ใช้งานจริงในคลาสเหล่านี้ แต่รวมอยู่ด้วย
หมายเหตุ :บางวิธี เช่น #count
และ #take
บน Array
คลาส คือ นำมาใช้เฉพาะสำหรับอาร์เรย์แทนที่จะใช้อาร์เรย์จาก Enumerable
โมดูล. ซึ่งมักจะทำเพื่อให้การทำงานเร็วขึ้น
Enumerable
โมดูลอาศัยวิธีการที่ชื่อ #each
ซึ่งจำเป็นต้องดำเนินการในคลาสใดๆ ที่รวมอยู่ในอาร์เรย์ เมื่อเรียกใช้ด้วยบล็อกในอาร์เรย์ #each
method จะดำเนินการบล็อกสำหรับแต่ละองค์ประกอบของอาร์เรย์
irb> [1,2,3].each { |i| puts "* #{i}" }
* 1
* 2
* 3
=> [1,2,3]
ถ้าเราเรียก #each
วิธีการในอาร์เรย์ ไม่มี ผ่านบล็อกเพื่อดำเนินการสำหรับแต่ละองค์ประกอบ เราจะได้รับอินสแตนซ์ของ Enumerator
.
irb> [1,2,3].each
=> #<Enumerator: [1, 2, 3]:each>
อินสแตนซ์ของ Enumerator
อธิบายวิธีการวนซ้ำวัตถุ ตัวแจงนับวนซ้ำวัตถุด้วยตนเองและการแจงนับลูกโซ่
irb> %w(dog cat mouse).each.with_index { |a, i| puts "#{a} is at position #{i}" }
dog is at position 0
cat is at position 1
mouse is at position 2
=> ["dog", "cat", "mouse"]
#with_index
วิธีเป็นตัวอย่างที่ดีของการทำงานของตัวแจงนับที่เปลี่ยนแปลง ในตัวอย่างนี้ #each
ถูกเรียกบนอาร์เรย์เพื่อส่งคืนตัวแจงนับ จากนั้น #with_index
ถูกเรียกให้เพิ่มดัชนีให้กับแต่ละองค์ประกอบของอาร์เรย์เพื่อให้สามารถพิมพ์ดัชนีของแต่ละองค์ประกอบได้
ทำให้วัตถุสามารถนับได้
ภายใต้ประทุนวิธีการเช่น #max
, #map
และ #take
อาศัย #each
วิธีการทำงาน
def max
max = nil
each do |item|
if !max || item > max
max = item
end
end
max
end
ภายใน Enumerable
วิธีการของมีการใช้งาน C แต่ตัวอย่างข้างต้นแสดงให้เห็นคร่าวๆ ว่า #max
ทำงาน โดยใช้ #each
เพื่อวนซ้ำค่าทั้งหมดและจำค่าสูงสุด จะส่งคืนค่าสูงสุด
def map(&block)
new_list = []
each do |item|
new_list << block.call(item)
end
new_list
end
#map
ฟังก์ชันจะเรียกใช้บล็อกที่ส่งผ่านพร้อมกับแต่ละรายการและใส่ผลลัพธ์ลงในรายการใหม่เพื่อส่งคืนหลังจากวนซ้ำค่าทั้งหมดแล้ว
เนื่องจากวิธีการทั้งหมดใน Enumerable
ใช้ #each
ในระดับหนึ่ง ขั้นตอนแรกของเราในการทำให้สามารถระบุคลาสที่กำหนดเองได้คือการนำ #each
ไปใช้ วิธีการ
กำลังดำเนินการ #each
โดยการนำ #each
. ไปใช้ ฟังก์ชันและรวมถึง Enumerable
โมดูลในคลาส จะนับได้และรับเมธอดเช่น #min
, #take
และ #inject
ฟรี
แม้ว่าสถานการณ์ส่วนใหญ่จะอนุญาตให้ย้อนกลับไปยังวัตถุที่มีอยู่เช่นอาร์เรย์และเรียก #each
ลองมาดูตัวอย่างที่เราต้องเขียนเองตั้งแต่ต้น ในตัวอย่างนี้ เราจะใช้ #each
ใน รายการที่เชื่อมโยง เพื่อให้นับได้
รายการที่เชื่อมโยง:รายการที่ไม่มีอาร์เรย์
รายการที่เชื่อมโยงคือชุดขององค์ประกอบข้อมูล ซึ่งแต่ละองค์ประกอบจะชี้ไปที่องค์ประกอบถัดไป แต่ละองค์ประกอบในรายการมีสองค่า ชื่อว่า หัว และ หาง . ส่วนหัวถือค่าขององค์ประกอบ ส่วนหางเป็นลิงก์ไปยังส่วนที่เหลือของรายการ
[42, [12, [73, nil]]
สำหรับรายการที่เชื่อมโยงที่มีค่าสามค่า (42, 12 และ 73) ส่วนหัวขององค์ประกอบแรกคือ 42 และส่วนท้ายคือลิงก์ไปยังองค์ประกอบที่สอง หัวขององค์ประกอบที่สองคือ 12 และส่วนหางถือองค์ประกอบที่สาม ส่วนหัวขององค์ประกอบที่สามคือ 73 และส่วนท้ายคือ nil
ซึ่งระบุจุดสิ้นสุดของรายการ
ใน Ruby สามารถสร้างลิงค์ลิสต์ได้โดยใช้คลาสที่มีตัวแปรอินสแตนซ์สองตัวชื่อ @head
และ @tail
.
class LinkedList
def initialize(head, tail = nil)
@head, @tail = head, tail
end
def <<(item)
LinkedList.new(item, self)
end
def inspect
[@head, @tail].inspect
end
end
#<<
เมธอดใช้เพื่อเพิ่มค่าใหม่ให้กับรายการ ซึ่งทำงานโดยส่งคืนรายการใหม่โดยมีค่าที่ส่งผ่านเป็นส่วนหัว และรายการก่อนหน้าเป็นส่วนท้าย
ในตัวอย่างนี้ #inspect
เพิ่มเมธอดเพื่อให้เราดูรายการเพื่อดูว่ามีองค์ประกอบใดบ้าง
irb> LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
ตอนนี้เรามีรายการที่เชื่อมโยงแล้ว เรามาติดตั้ง #each
. กัน เกี่ยวกับมัน #each
ฟังก์ชั่นรับบล็อกและดำเนินการสำหรับแต่ละค่าในวัตถุ เมื่อนำไปใช้ในรายการที่เชื่อมโยงของเรา เราสามารถใช้ลักษณะแบบเรียกซ้ำของรายการเพื่อประโยชน์ของเราโดยการเรียกบล็อกที่ส่งผ่านใน @head
ของรายการ และโทร #each
บน @tail
ถ้ามันมีอยู่จริง
class LinkedList
def initialize(head, tail = nil)
@head, @tail = head, tail
end
def <<(item)
LinkedList.new(item, self)
end
def inspect
[@head, @tail].inspect
end
def each(&block)
block.call(@head)
@tail.each(&block) if @tail
end
end
เมื่อโทร #each
ในอินสแตนซ์ของรายการที่เชื่อมโยงของเรา มันเรียกบล็อกที่ส่งผ่านด้วย @head
current ปัจจุบัน . จากนั้นจะเรียกแต่ละรายการที่เชื่อมโยงใน @tail
เว้นแต่หางจะ nil
.
irb> list = LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
irb> list.each { |item| puts item }
42
12
73
=> nil
ตอนนี้รายการที่เชื่อมโยงของเราตอบสนองต่อ #each
เราสามารถ include Enumberable
เพื่อให้รายการของเรานับได้
class LinkedList
include Enumerable
def initialize(head, tail = nil)
@head, @tail = head, tail
end
def <<(item)
LinkedList.new(item, self)
end
def inspect
[@head, @tail].inspect
end
def each(&block)
block.call(@head)
@tail.each(&block) if @tail
end
end
irb> list = LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
irb> list.count
=> 3
irb> list.max
=> 73
irb> list.map { |item| item * item }
=> [1764, 144, 5329]
irb> list.select(&:even?)
=> [42, 12]
ส่งคืน Enumerator
ตัวอย่าง
ขณะนี้เราสามารถวนซ้ำค่าทั้งหมดในรายการที่เชื่อมโยงของเราได้ แต่เรายังไม่สามารถโยงฟังก์ชันที่นับได้ ในการทำเช่นนั้น เราจะต้องส่งคืน Enumerator
เช่นเมื่อ #each
. ของเรา ฟังก์ชันถูกเรียกโดยไม่มีการบล็อก
class LinkedList
include Enumerable
def initialize(head, tail = nil)
@head, @tail = head, tail
end
def <<(item)
LinkedList.new(item, self)
end
def inspect
[@head, @tail].inspect
end
def each(&block)
if block_given?
block.call(@head)
@tail.each(&block) if @tail
else
to_enum(:each)
end
end
end
ในการห่อวัตถุในตัวแจงนับ เราเรียก #to_enum
วิธีการกับมัน เราส่ง :each
เนื่องจากเป็นวิธีการที่ตัวแจงนับควรใช้เป็นการภายใน
ตอนนี้กำลังเรียก #each
. ของเรา วิธีการที่ไม่มีบล็อกจะทำให้เราสามารถนับลูกโซ่ได้
irb> list = LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
irb> list.each
=> #<Enumerator: [42, [12, [73, nil]]]:each>
irb> list.map.with_index.to_h
=> {42=>0, 12=>1, 73=>2}
โค้ดเก้าบรรทัดและการรวม
โดยการติดตั้ง #each
โดยใช้ Enumerable
โมดูลและส่งคืน Enumerator
ออบเจ็กต์จากเราเอง เราสามารถอัดรายการลิงก์ของเราให้มากเกินไปโดยเพิ่มโค้ด 9 บรรทัดและรวมเข้าไปด้วย
นี่เป็นการสรุปภาพรวมของจำนวนนับใน Ruby เราอยากทราบว่าคุณคิดอย่างไรกับบทความนี้ หรือหากคุณมีคำถามใดๆ เราคอยมองหาหัวข้อที่จะตรวจสอบและอธิบายอยู่เสมอ ดังนั้นหากคุณต้องการอ่านเรื่องมหัศจรรย์ใน Ruby อย่าลังเลที่จะแจ้งให้เราทราบที่ @AppSignal!