Đến episode Ruby Magic khác! Lần này, xem một trong magical features nhất Ruby, cung cấp hầu hết methods bạn dùng khi làm việc với enumerable classes như Array, Hash và Range. Quá trình đó, học what you can do với enumerable objects, enumeration works như thế nào, và làm object enumerable bằng implement single method.
Enumerable, #each và Enumerator
Enumeration là traverse over objects. Trong Ruby, gọi object enumerable khi nó describe set of items và method loop qua each của chúng.
Built-in enumerables get enumeration features bằng include Enumerable module, cung cấp methods như #include?, #count, #map, #select, #uniq, amongst others. Hầu hết methods associated với arrays và hashes không actually implemented trong classes themselves, chúng included.
Note: Một số methods, như #count và #take trên Array class, are implemented specifically cho arrays thay vì dùng từ Enumerable module. Thường để operation faster.
Module Enumerable rely on method tên #each, cần implement trong bất kỳ class include nó. Khi gọi với block trên array, #each execute block cho each array element.
irb> [1,2,3].each { |i| puts "* #{i}" }
* 1
* 2
* 3
=> [1,2,3]
Gọi #each trên array without pass block, nhận instance Enumerator.
irb> [1,2,3].each
=> #
Instances của Enumerator describe cách iterate over object. Enumerators iterate objects manually và 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"]
#with_index method good example changed enumerators work. Ví dụ này, #each gọi trên array trả enumerator. Sau đó #with_index gọi add indices cho each array element cho phép print each element index.
Making objects enumerable (Làm object enumerable)
Under the hood, methods như #max, #map, #take rely on #each method để function.
def max
max = nil
each do |item|
if !max || item > max
max = item
end
end
max
end
Internally, Enumerable methods có C implementations, nhưng ví dụ trên roughly show #max works. Dùng #each loop qua all values và remember highest, trả maximum value.
def map(&block)
new_list = []
each do |item|
new_list << block.call(item)
end
new_list
end
#map function gọi passed block với each item và put result vào new list trả về sau loop all values.
Vì all methods in Enumerable dùng #each to some extent, first step làm custom class enumerable là implement #each method.
Implementing #each
Bằng implement #each function và include Enumerable module trong class, nó becomes enumerable và nhận methods như #min, #take, #inject for free.
Mặc dù most situations allow fallback existing object như array và call #each trên đó, hãy xem example phải write từ scratch. Ví dụ này implement #each trên linked list để làm enumerable.
Linked lists: lists without arrays
Linked list là collection data elements, mỗi element point đến next. Mỗi element có 2 values: head và tail. Head hold element value, tail link đến rest of list.
[42, [12, [73, nil]]
Linked list 3 values (42, 12, 73), first element head 42, tail link second element. Second element head 12, tail hold third. Third element head 73, tail nil indicate end of list.
Trong Ruby, linked list tạo bằng class hold 2 instance variables @head và @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 dùng add new values to list, work bằng return new list với passed value as head, previous list as tail.
Ví dụ thêm #inspect để see into list check elements.
irb> LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
Giờ implement #each trên linked list. #each function take block và execute cho each value in object. Implement trên linked list, dùng recursive nature: call passed block trên @head, gọi #each trên @tail nếu tồn tại.
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
Khi gọi #each trên instance linked list, call passed block với current @head. Sau đó call each trên linked list in @tail trừ khi tail nil.
irb> list = LinkedList.new(73) << 12 << 42
=> [42, [12, [73, nil]]]
irb> list.each { |item| puts item }
42
12
73
=> nil
Giờ linked list respond to #each, include Enumerable làm 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
Giờ loop qua all values linked list, nhưng chưa chain enumerable functions. Để làm đó, cần return Enumerator instance khi #each gọi without 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 trong enumerator, gọi #to_enum trên nó. Pass :each, vì đó method enumerator dùng internally.
Giờ gọi #each without block cho phép 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 và an include
Bằng implement #each dùng Enumerable module và return Enumerator objects từ own, chúng ta supercharge linked list bằng add nine lines code và an include.
Kết thúc overview enumerables Ruby. Chúng tôi muốn biết thoughts bạn về bài này, hoặc questions. Luôn tìm topics investigate và explain, nếu anything magical Ruby bạn muốn đọc, đừng ngần ngại let us now @AppSignal!