我需要一些帮助来概念化Ruby中的Lazy Enumerator对象。
我认为Enumerator对象的方式是"有状态的"集合的版本。该对象了解哪些元素是集合的一部分,以及" next"应该产生的元素是。它是一个知道它的元素是1,2和3的对象,它已经产生了1和2,因此如果被要求它将产生3。
假设Enumerator的概念化是正确的,我对Lazy Enumerator的工作方式很困难。懒惰的枚举器是由一个常规的"枚举器,但不应该事先计算它的集合。例如,来自 The Ruby Way :
enum = (1..Float::INFINITY).each
lazy = enum.lazy
odds = lazy.select(&:odd)
如果Lazy Enumerator是由Lazy Enumerator构建的,那么Lazy Enumerator是如何懒惰的,因为我在做Enumerator部分,这可能不是懒惰的,首先是?
答案 0 :(得分:6)
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]
链中的每个操作都应用于枚举中的所有值,然后将值列表传递到链中以进行下一个操作。
相比之下,Enumerable #lazy从每个操作返回延迟枚举器(“与值有关的事情”):
> (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
如果你要在无限列表上尝试这些相同的操作,那么非懒惰的枚举器将永远失速,因为它会尝试在无限可枚举上进行广度优先传递,这显然永远不会终止!