概念化Ruby中的枚举器和惰性枚举器

时间:2015-05-23 19:19:49

标签: ruby

我需要一些帮助来概念化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部分,这可能不是懒惰的,首先是?

1 个答案:

答案 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

如果你要在无限列表上尝试这些相同的操作,那么非懒惰的枚举器将永远失速,因为它会尝试在无限可枚举上进行广度优先传递,这显然永远不会终止!