Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

โมดูลนับจำนวนเวทย์มนตร์ของ Ruby

ถึงเวลาสำหรับ 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!