Метод ленивого выравнивания на основе Ruby Enumerator

Майкл Харрисон имеет отличный пост о ленивых перечислителях в Ruby, предоставляя реализацию lazy_select а также lazy_map, Мне интересно, возможна ли следующая реализация lazy_flatten должна иметь специальную обработку для чего-либо, кроме Enumerator а также Enumerable типы.

class Enumerator

  def lazy_flatten
    Enumerator.new do |yielder|
      self.each do |value|
        if value.kind_of? Enumerator
          value.lazy_flatten.each do |v|
            yielder.yield v
          end
        elsif value.kind_of? Enumerable
          value.flatten.each do |v|
            yielder.yield v
          end
        else
          yielder.yield value
        end
      end
    end
  end

end

2 ответа

Решение
  1. Это не кажется мне ленивым, так как вы все еще исполняете старые (не ленивые) flatten под.
  2. Enumerator является Enumerableтак что я думаю, что вам не нужно обрабатывать это отдельно.
  3. Я бы ожидал lazy_flatten быть методом на Enumerable,

Вот как я бы это реализовал:

module Enumerable
  def lazy_flatten
    Enumerator.new do |yielder|
      each do |element|
        if element.is_a? Enumerable
          element.lazy_flatten.each do |e|
            yielder.yield(e)
          end
        else
          yielder.yield(element)
        end
      end
    end
  end
end

Обратите внимание, что в Ruby 2.0+ вам не нужно этого делать, вы можете просто использовать Enumerable#lazy, который возвращает Enumerator::Lazy.

По непонятным мне причинам, Lazyне имеет flatten, но у него есть, поэтому в принципе вы можете просто использовать функцию идентификации .

      module Enumerable
  def lazy_flatten
    self.lazy.flat_map { |x| x }
  end
end

в основном заботится о декомпозиции любых разлагаемых элементов, но не совсем -- из документов :

Значение x , возвращаемое блоком, декомпозируется, если выполняется одно из следующих условий:

  1. х отвечает на оба eachа также force, что обозначает xявляется ленивым счетчиком.
  2. x представляет собой массив или отвечает на .

Обратите внимание, что to_aryэто не метод на Enumerable, предположительно для предотвращения неявных преобразований из бесконечных последовательностей в массивы. Это означает, например, что если вы попытаетесь lazy_flattenчто-то, что содержит a или a с приведенным выше кодом, это (возможно, см. Ниже) не будет работать:

      a = [[1, 2, 3], Set[4, 5], 6, 7..8]
# => [[1, 2, 3], #<Set: {4, 5}>, 6, 7..8] 
f = a.lazy_flatten
# => #<Enumerator::Lazy: #<Enumerator::Lazy: [[1, 2, 3], #<Set: {4, 5}>, 6, 7..8]>:flat_map>  
f.to_a
# => [1, 2, 3, #<Set: {4, 5}>, 6, 7..8]

Однако это то же самое, что и поведение:

      a.flatten
# => [1, 2, 3, #<Set: {4, 5}>, 6, 7..8]

(Несмотря на то что Array#flattenне будет обнаруживать и разлагать ленивые счетчики, и Lazy#flat_mapбудут.)

Принимая во внимание, что код ОП или код в ответе Младена Яблановича разложит Setи Range:

      f = a.lazy_flatten # (M.J.'s code)
# => #<Enumerator: #<Enumerator::Generator:0x00007fd819c166c0>:each>
f.to_a
# => [1, 2, 3, 4, 5, 6, 7, 8]

Однако этот код также будет повторяться бесконечно, если будет передано что-то, что включает бесконечную последовательность:

      a = [[1, 2, 3], Set[4, 5], 6, 7..8, 9..Float::INFINITY]
# => [[1, 2, 3], #<Set: {4, 5}>, 6, 7..8, 9..Infinity]
f = a.lazy_flatten # (M.J.'s code)
# => #<Enumerator: #<Enumerator::Generator:0x00007fd819a73d18>:each>
f.to_a
# => spins at 100% CPU for a while and eventually runs out of memory

Если вы считаете, что это функция, а не ошибка, одним из подходов будет изменение flat_map-основанная реализация для преобразования любых перечислений, которые он находит, в ленивые:

      module Enumerable
  def lazy_flatten
    self.lazy.flat_map do |x|
      x.respond_to?(:lazy) ? x.lazy : x
    end
  end
end

Это работает даже для вложенных ленивых перечислений, т.к. Lazy#lazyдостаточно умен, чтобы вернуть себя.

Другие вопросы по тегам