Метод ленивого выравнивания на основе 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 ответа
- Это не кажется мне ленивым, так как вы все еще исполняете старые (не ленивые)
flatten
под. Enumerator
являетсяEnumerable
так что я думаю, что вам не нужно обрабатывать это отдельно.- Я бы ожидал
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 , возвращаемое блоком, декомпозируется, если выполняется одно из следующих условий:
- х отвечает на оба
each
а такжеforce
, что обозначаетx
является ленивым счетчиком.- 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
достаточно умен, чтобы вернуть себя.