Одна из самых крутых особенностей Ruby — это Enumerable методы. Они дают вам большую гибкость для перебора любого типа коллекции и, например, проверки того, возвращает ли какой-либо элемент в этой коллекции значение true для данного блока (#any), или отклонить элементы в коллекции, для которых блок возвращает значение false (#reject). .

Но что, если вы не знаете, сколько элементов вам нужно перебрать? Предположим, например, что вы пытаетесь написать собственный метод для получения простой факторизации числа. В Ruby есть собственный метод для этого в классе Prime, но это хороший пример, иллюстрирующий полезность перечислителей. Один из способов произвести разложение числа на простые множители — перебрать массив простых чисел и посмотреть, делится ли каждое простое число на число, которое вы хотите разложить на множители. Если оно делится без остатка, к результатам добавляется простое число, факторизируемое число сбрасывается до самого себя, деленного на простое число, и итерация начинается заново. Процесс продолжается до тех пор, пока проверяемое простое число не станет больше разлагаемого числа.

class PrimeFactors
  def self.for(number)
    primes.each_with_object([]) do |prime, a|
      return a if prime > number
      number1, mod = number.divmod(prime)
      next unless mod == 0
      number = number1
      a << prime
      redo
    end
  end
...
end

Но сколько простых чисел должен содержать массив? Кто знает? Это должно быть столько, сколько нужно, и мы не можем сказать это заранее. Итак, как мы выбираем набор простых чисел для повторения? Итак, мы знаем, что наибольший простой делитель числа будет меньше самого числа, поэтому мы можем сгенерировать массив простых чисел от 0 до числа. Решето Эратосфена может быть полезным именно для этого. Но для достаточно большого числа этот процесс очень медленный и совершенно ненужный.

Нам нужно что-то, что будет генерировать простые числа по одному, бесконечно. Это может сделать простой цикл. Найдите простое число, передайте его блоку, найдите другое, передайте его и т. д. Но, эй, было бы неплохо, если бы генератор мог реагировать на перечислимые методы? Вместо того, чтобы выдавать новые простые числа какому-либо старому блоку, цикл можно обернуть в объект перечислителя, который отвечает на все перечисляемые методы. В данном случае мы хотим, чтобы он реагировал на #each_with_object.

Вот как это выглядит:

def self.primes
  Enumerator.new do |y|
    y << 2
    i = 3
    loop do
      y << i if prime?(i)
      i += 2
    end
  end
end
def self.prime?(num)
  2.upto(Math.sqrt(num)).each { |i| return false if num % i == 0 }
  true
end

Есть более эффективные способы генерировать простые числа, но нужно видеть, что простые числа генерируются по одному. Enumerator#new берет блок, и этот блок получает единственный параметр, «доходность». Когда объект помещается в yielder, этот объект передается в блок метода, который вызывает перечислитель. Давайте еще раз посмотрим на наш метод первичной факторизации:

  def self.for(number)
    primes.each_with_object([]) do |prime, a|
      return a if prime > number
      number1, mod = number.divmod(prime)
      next unless mod == 0
      number = number1
      a << prime
      redo
    end
  end

Помните, что простые числа возвращают перечислитель. Это не массив. У него неопределенное количество элементов — он будет производить простые числа столько, сколько вам нужно. Но каждый раз, когда он выдает простое число, оно передается в блок each_with_object , как если бы это был следующий элемент в массиве. Теперь нам не нужно гадать, сколько простых чисел нам понадобится. Это не имеет значения! Мы перестанем их генерировать, когда они нам больше не понадобятся, ни до, ни после. И мы получаем все причудливые дополнения, которые дают нам перечисляемые методы. Посмотрите на эту переделку! Он делает именно то, что вы ожидаете, если бы простые числа были массивом — в этом случае он начинает итерацию с 2. Перечислитель действует как постоянно расширяющийся массив, увеличивая один элемент с каждой итерацией.

Вариант использования счетчиков может быть ограничен, но полезно знать, что они есть. Если вы хотите перебрать коллекцию с помощью перечисляемого метода, но не знаете, сколько элементов эта коллекция должна содержать, используйте перечислитель.