Обратите внимание, что в Ruby 2.0+ вам не нужно этого делать, вы можете просто использовать Enumerable#lazy
, который возвращает Enumerator::Lazy
.
По непонятным мне причинам Lazy
не имеет flatten
, но имеет flat_map
, так что в принципе вы можете просто использовать flat_map
с функция идентификации.
module Enumerable
def lazy_flatten
self.lazy.flat_map { |x| x }
end
end
Lazy#flat_map
в основном занимается декомпозицией любых декомпозируемых элементов, но не совсем -- из документы:
Значение x, возвращаемое блоком, декомпозируется, если выполняется одно из следующих условий:
- x отвечает как на
each
, так и на force
, что означает, что x
является ленивым перечислителем.
- x представляет собой массив или отвечает на
to_ary
.
Обратите внимание, что to_ary
не является методом Enumerable
, предположительно для предотвращения неявных преобразований от бесконечных последовательностей к массивам. Это означает, например, что если вы попытаетесь lazy_flatten
что-то, что содержит Set
или Range
с приведенным выше кодом, это (аргументировано, см. ниже) не будет работать:
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]
Однако это то же самое, что и поведение Array#flatten
:
a.flatten
# => [1, 2, 3, #<Set: {4, 5}>, 6, 7..8]
(Хотя Array#flatten
не будет обнаруживать и разлагать ленивые счетчики, а Lazy#flat_map
будет.)
Принимая во внимание, что код OP или код в ответе Младена Яблановича разложит 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
достаточно умен, чтобы возвращать себя.
person
David Moles
schedule
25.01.2021