2015-05-23 2 views
1

Мне нужно немного помочь в концептуализации объектов Lazy Enumerator в Ruby.Концептуализация счетчиков и ленивых счетчиков в Ruby

То, как я думаю об объекте Enumerator, представляет собой «версию с сохранением состояния» коллекции. Объект понимает, какие элементы являются частью коллекции, и каков должен быть «следующий» элемент. Это объект, который знает, что его элементы, скажем, 1, 2 и 3, уже дали 1 и 2, и поэтому он даст 3, если их попросят.

Предполагая, что эта концептуализация Enumerator верна, мне сложно провести время с помощью Lazy Enumerator. Lazy Enumerator построен из «обычного» счетчика, но он не должен заранее рассчитывать свой набор. Например, из The Ruby Way:

enum = (1..Float::INFINITY).each 
lazy = enum.lazy 
odds = lazy.select(&:odd) 

Если Ленивый Enumerator построен от ленивого Enumerator, то как это Ленивый Enumerator ленивым, так как я делаю часть Enumerator, которая предположительно не ленивый, во-первых?

+1

Крис уже дал хороший ответ, просто заметите, что ленивый перечислитель может быть просто построен из строгой версии, потому что даже строгий экземпляр не выполняет (пока) выполнение какой-либо оценки, а содержит только квитанцию ​​- выражение, как будет оцениваться. Ленивая версия фактически такая же, как и строгая, только эквивалентные методы экземпляра возвращают еще один ленивый счетчик вместо конечного результата. –

ответ

5

Enumerator#lazy Позволяет эффективно создавать цепочку перечислимых операций, которые применяются к каждому значению, так как он повторяется из перечислимого. Для сравнения, обычные счетчики выполняют каждую операцию ко всем значениям в перечислимом, а затем передают результат по цепочке на следующую операцию в цепочке. Вы можете думать о ленивых перечислителях как о «глубинных первых» операциях, а обычные счетчики - как операции «в ширину».

Нормальные счетчики возвращают результат подсчета:

> (1..10).select(&:odd?) 
=> [1, 3, 5, 7, 9] 

Если бы вы были приковать эти операции, вы могли бы выполнить некоторый список операций на конечном список значений:

> (1..10).select(&:odd?) 
=> [1, 3, 5, 7, 9] 

> (1..10).select(&:odd?).map {|v| v * 2 } 
=> [2, 6, 10, 14, 18] 

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

сравнения, перечислимые # ленивые возвращается ленивые счетчики («вещи делать со значением») от каждой операции:

> (1..10).lazy.select(&:odd?) 
=> #<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:select> 
> (1..10).lazy.select(&:odd?).map {|v| v * 2 } 
=> #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:select>:map> 

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

> (1..10).lazy.select(&:odd?).map {|v| v * 2 }.next 
=> 2 

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

+1

Лучшее объяснение ленивых счетчиков, которые я когда-либо читал! –

+1

Невероятное объяснение! Спасибо! –

+0

Именно то, что я искал. –

Смежные вопросы