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

Mastering Ruby's Enumerable Module: ปลดล็อก Powerful Collection Methods

ถึงเวลาอีก episode ของ Ruby Magic! ครั้งนี้ เราจะดูหนึ่งใน Ruby's most magical features ซึ่งให้ most methods ที่คุณจะใช้เมื่อทำงานกับ Ruby's enumerable classes เหมือน Array, Hash และ Range ในกระบวนการ เราจะเรียนรู้ว่าสามารถทำอะไรได้กับ enumerable objects, enumeration ทำงานอย่างไร และทำให้ object enumerable ได้อย่างไรโดย implement single method

Enumerable, #each และ Enumerator

Enumeration หมายถึง traversing over objects ใน Ruby เราเรียก object ว่า enumerable เมื่อมัน describe a set of items และ method ที่ loop over แต่ละตัวของพวกมัน

Built-in enumerables ได้ enumeration features โดย include Enumerable module ซึ่งให้ methods เหมือน #include?, #count, #map, #select และ #uniq และอื่นๆ Most methods ที่ associated กับ arrays และ hashes ไม่ได้ implemented ใน classes เอง พวกมัน included

Note: บาง methods เหมือน #count และ #take บน Array class are implemented specifically สำหรับ arrays แทนที่จะใช้จาก Enumerable module นั่นมักทำเพื่อทำ operation เร็วขึ้น

Enumerable module relies บน method ชื่อ #each ซึ่งต้อง implemented ใน class ใดๆ ที่มัน included ใน เมื่อเรียกพร้อม block บน array #each method จะ execute block สำหรับแต่ละ element ของ array

irb> [1,2,3].each { |i| puts "* #{i}" } * 1 * 2 * 3 => [1,2,3]

ถ้าเราเรียก #each method บน array โดยไม่ ส่ง block เพื่อ execute สำหรับแต่ละ element เราจะได้ instance ของ Enumerator

irb> [1,2,3].each => #

Instances ของ Enumerator describe ว่าจะ iterate over object อย่างไร Enumerators iterate over objects manually และ chain enumeration

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"]

Method #with_index เป็นตัวอย่างดีของ changed enumerators ในตัวอย่างนี้ #each ถูกเรียกบน array คืน enumerator แล้ว #with_index ถูกเรียกเพื่อเพิ่ม indices ให้แต่ละ element ของ array เพื่อให้ print index ของแต่ละ element

Making objects enumerable

ภายใต้หน้าปาก methods เหมือน #max, #map และ #take พึ่งพา #each method เพื่อ function

def max max = nil each do |item| if !max || item > max max = item end end max end

ภายใน Enumerable's methods มี C implementations แตตัวอย่างด้านบนแสดงประมาณว่า #max ทำงานอย่างไร โดยใช้ #each loop over ทุก values และจดจำ highest มันคืน maximum value

def map(&block) new_list = [] each do |item| new_list << block.call(item) end new_list end

Function #map เรียก passed block กับแต่ละ item และวางผลลัพธ์เข้า new list เพื่อคืนหลัง loop over ทุก values

เพราะทุก methods ใน Enumerable ใช้ #each method บาง extent ขั้นแรกในการทำ custom class enumerable คือ implement #each method

Implementing #each

ด้วยการ implement #each function และ include Enumerable module ใน class มันจะกลายเป็น enumerable และได้ methods เหมือน #min, #take และ #inject ฟรี

แม้ most situations จะ allow falling back ไป existing object เหมือน array และเรียก #each method บนมัน มาดูตัวอย่างที่เราต้องเขียนเองจากศูนย์ ในตัวอย่างนี้ เราจะ implement #each บน linked list เพื่อทำให้มัน enumerable

Linked lists: lists without arrays

Linked list คือ collection ของ data elements ซึ่งแต่ละ element points ไป next element แต่ละ element ใน list มีสอง values ชื่อ head และ tail Head เก็บ element's value และ tail เป็น link ไป rest ของ list

[42, [12, [73, nil]] 

สำหรับ linked list ที่มีสาม values (42, 12 และ 73) first element's head คือ 42 และ tail เป็น link ไป second element Second element's head คือ 12 และ tail เก็บ third element Third element's head คือ 73 และ tail เป็น nil ซึ่งบ่งชี้ end ของ list

ใน Ruby linked list สามารถสร้างได้โดยใช้ class ที่ hold สอง instance variables ชื่อ @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

Method #<< ใช้เพิ่ม values ใหม่เข้า list ทำงานโดยคืน new list ด้วย passed value เป็น head และ previous list เป็น tail

ในตัวอย่างนี้ method #inspect ถูกเพิ่มเพื่อเราสามารถดูเข้า list ได้เพื่อ check ว่ามี elements อะไรบ้าง

irb> LinkedList.new(73) << 12 << 42 => [42, [12, [73, nil]]]

ตอนนี้เรามี linked list มา implement #each บนมัน Method #each รับ block และ execute สำหรับแต่ละ value ใน object เมื่อ implement บน linked list ของเรา เราสามารถใช้ list's recursive nature ได้เปรียบโดยเรียก passed block บน list's @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 บน instance ของ linked list ของเรา มันเรียก passed block กับ current @head แล้วเรียก each บน linked list ใน @tail เว้นแต่ tail จะเป็น nil

irb> list = LinkedList.new(73) << 12 << 42 => [42, [12, [73, nil]]] irb> list.each { |item| puts item } 42 12 73 => nil

ตอนนี้ linked list ของเรา respond to #each เราสามารถ include Enumerable เพื่อทำให้ list enumerable

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]

Returning Enumerator instances

เราสามารถ loop over ทุก values ใน linked list ได้แล้ว แต่เราไม่สามารถ chain enumerable functions ได้ยัง เพื่อทำนั้น เราต้อง return Enumerator instance เมื่อ #each function ถูกเรียกโดยไม่มี block

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

เพื่อ wrap object ใน enumerator เราเรียก method #to_enum บนมัน เราส่ง :each เพราะนั่นคือ method ที่ enumerator ควรใช้ภายใน

ตอนนี้ การเรียก #each method โดยไม่มี block จะช่วยให้เรา chain enumeration ได้

irb> list = LinkedList.new(73) << 12 << 42 => [42, [12, [73, nil]]] irb> list.each => # irb> list.map.with_index.to_h => {42=>0, 12=>1, 73=>2}

Nine lines of code และ an include

ด้วยการ implement #each ใช้ Enumerable module และคืน Enumerator objects จากของเราเอง เราสามารถ supercharge linked list ของเราได้โดยเพิ่ม nine lines of code และ an include

นี่คือสรุป overview ของ enumerables ใน Ruby เรายินดีรู้ความคิดเห็นของคุณเกี่ยวกับบทความนี้ หรือถ้ามีคำถาม เรายังคงมองหา topics ที่จะ investigate และ explain ถ้ามีอะไร magical ใน Ruby ที่คุณอยากอ่าน อย่าลังเลบอกเราที่ @AppSignal!