Computer >> Máy Tính >  >> Lập trình >> Ruby

Mô-đun Enumerable kỳ diệu của Ruby

Đã đến lúc cho một tập khác của Ruby Magic! Lần này, chúng ta sẽ xem xét một trong những tính năng kỳ diệu nhất của Ruby, cung cấp hầu hết các phương thức bạn sẽ sử dụng khi làm việc với các lớp có thể liệt kê của Ruby như Array , HashRange . Trong quá trình này, chúng ta sẽ tìm hiểu những gì bạn có thể làm với các đối tượng có thể liệt kê, cách liệt kê hoạt động và cách làm cho một đối tượng có thể liệt kê bằng cách triển khai một phương thức duy nhất.

Enumerable , #eachEnumerator

Liệt kê đề cập đến việc đi ngang qua các đối tượng. Trong Ruby, chúng tôi gọi một đối tượng là enumerable khi nó mô tả một tập hợp các mục và một phương thức để lặp lại từng mục.

Các bảng liệt kê tích hợp sẵn có được các tính năng liệt kê của chúng bằng cách đưa vào Enumerable mô-đun cung cấp các phương thức như #include? , #count , #map , #select#uniq , giữa những người khác. Hầu hết các phương thức được liên kết với mảng và hàm băm không thực sự được triển khai trong chính các lớp này, chúng được bao gồm.

Lưu ý :Một số phương pháp, như #count#take trên Array lớp học, được triển khai cụ thể cho các mảng thay vì sử dụng các mảng từ Enumerable mô-đun. Điều đó thường được thực hiện để giúp hoạt động nhanh hơn.

Enumerable mô-đun dựa trên một phương thức có tên #each , cần được triển khai trong bất kỳ lớp nào mà nó bao gồm. Khi được gọi với một khối trên một mảng, #each phương thức sẽ thực thi khối cho mỗi phần tử của mảng.

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

Nếu chúng ta gọi #each phương thức trên một mảng không có truyền một khối để thực thi cho từng phần tử của nó, chúng ta sẽ nhận được một bản sao của Enumerator .

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

Các bản sao của Enumerator mô tả cách lặp qua một đối tượng. Điều tra viên lặp qua các đối tượng theo cách thủ công và liệt kê chuỗi.

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 phương pháp là một ví dụ điển hình về cách hoạt động của các điều tra viên đã thay đổi. Trong ví dụ này, #each được gọi trên mảng để trả về một liệt kê. Sau đó, #with_index được gọi để thêm chỉ số vào từng phần tử của mảng để cho phép in chỉ mục của từng phần tử.

Làm cho các đối tượng có thể liệt kê được

Bên dưới, các phương pháp như #max , #map#take dựa vào #each phương thức hoạt động.

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

Trong nội bộ, Enumerable Các phương thức của có triển khai C, nhưng ví dụ trên cho thấy gần như thế nào #max làm. Bằng cách sử dụng #each để lặp lại tất cả các giá trị và ghi nhớ giá trị cao nhất, nó sẽ trả về giá trị lớn nhất.

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

#map hàm gọi khối đã truyền với mỗi mục và đặt kết quả vào một danh sách mới để trả về sau khi lặp qua tất cả các giá trị.

Vì tất cả các phương thức trong Enumerable sử dụng #each ở một mức độ nào đó, bước đầu tiên của chúng ta trong việc tạo một lớp tùy chỉnh có thể liệt kê là triển khai #each phương pháp.

Triển khai #each

Bằng cách triển khai #each và bao gồm Enumerable mô-đun trong một lớp, nó trở nên có thể liệt kê và nhận các phương thức như #min , #take#inject miễn phí.

Mặc dù hầu hết các tình huống đều cho phép quay trở lại một đối tượng hiện có như một mảng và gọi #each trên đó, hãy xem một ví dụ mà chúng ta phải tự viết nó từ đầu. Trong ví dụ này, chúng tôi sẽ triển khai #each trên danh sách được liên kết để làm cho nó có thể liệt kê được.

Danh sách được liên kết:danh sách không có mảng

Danh sách được liên kết là một tập hợp các phần tử dữ liệu, trong đó mỗi phần tử trỏ đến phần tử tiếp theo. Mỗi phần tử trong danh sách có hai giá trị, được đặt tên là head đuôi . Phần đầu giữ giá trị của phần tử và phần đuôi là liên kết đến phần còn lại của danh sách.

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

Đối với danh sách được liên kết có ba giá trị (42, 12 và 73), phần đầu của phần tử đầu tiên là 42 và phần đuôi là liên kết đến phần tử thứ hai. Phần đầu của phần tử thứ hai là 12 và phần đuôi chứa phần tử thứ ba. Đầu của phần tử thứ ba là 73 và đuôi là nil , cho biết phần cuối của danh sách.

Trong Ruby, một danh sách liên kết có thể được tạo bằng cách sử dụng một lớp chứa hai biến cá thể có tên @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

#<< được sử dụng để thêm các giá trị mới vào danh sách, phương thức này hoạt động bằng cách trả về một danh sách mới với giá trị đã truyền là phần đầu và danh sách trước đó là phần đuôi.

Trong ví dụ này, #inspect phương thức được thêm vào để chúng ta có thể xem trong danh sách để kiểm tra xem nó chứa những phần tử nào.

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

Bây giờ chúng ta có một danh sách được liên kết, hãy triển khai #each trên đó. #each hàm nhận một khối và thực thi nó cho mỗi giá trị trong đối tượng. Khi triển khai nó trên danh sách được liên kết của chúng tôi, chúng tôi có thể sử dụng tính chất đệ quy của danh sách để làm lợi thế của chúng tôi bằng cách gọi khối đã truyền trên @head của danh sách và gọi #each trên @tail , nếu nó 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 một phiên bản của danh sách được liên kết của chúng tôi, nó gọi khối đã chuyển với @head hiện tại . Sau đó, nó gọi từng thứ trên danh sách được liên kết trong @tail trừ khi đuôi là nil .

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

Bây giờ danh sách được liên kết của chúng tôi phản hồi với #each , chúng ta có thể include Enumberable để làm cho danh sách của chúng tôi có thể liệt kê được.

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]

Trả lại Enumerator phiên bản

Bây giờ chúng ta có thể lặp lại tất cả các giá trị trong danh sách liên kết của mình, nhưng chúng ta chưa thể xâu chuỗi các hàm có thể liệt kê được. Để làm điều đó, chúng tôi sẽ cần trả về một Enumerator ví dụ khi #each của chúng tôi hàm được gọi mà không có khối.

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

Để bọc một đối tượng trong một liệt kê, chúng tôi gọi #to_enum phương pháp trên đó. Chúng tôi chuyển :each , vì đó là phương pháp mà điều tra viên nên sử dụng trong nội bộ.

Bây giờ, gọi #each của chúng tôi phương thức không có khối sẽ cho phép chúng tôi liệt kê chuỗi.

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}

Chín dòng mã và một dòng bao gồm

Bằng cách triển khai #each bằng cách sử dụng Enumerable mô-đun và trả về Enumerator đối tượng của riêng chúng tôi, chúng tôi có thể tăng thêm danh sách được liên kết của mình bằng cách thêm chín dòng mã và một bao gồm.

Phần này kết thúc phần tổng quan của chúng ta về các phép liệt kê trong Ruby. Chúng tôi rất muốn biết bạn nghĩ gì về bài viết này hoặc nếu bạn có bất kỳ câu hỏi nào. Chúng tôi luôn theo dõi các chủ đề để điều tra và giải thích, vì vậy nếu có điều gì kỳ diệu trong Ruby mà bạn muốn đọc, đừng ngần ngại cho chúng tôi ngay tại @AppSignal!