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

การแจงนับภายใน Ruby

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