如何在 Ruby 中获取懒加载数组?
在 Haskell 中,我可以使用 [1..]
来表示一个无限列表,它会在需要时进行惰性生成。我还可以做一些类似于 iterate (+2) 0
的操作,它会将我给定的任何函数应用于生成惰性列表,也就是会给我所有的偶数。
我相信在 Ruby 中也能够实现类似的操作,但是我似乎无法找到方法。
如何在 Ruby 中获取懒加载数组?
在 Haskell 中,我可以使用 [1..]
来表示一个无限列表,它会在需要时进行惰性生成。我还可以做一些类似于 iterate (+2) 0
的操作,它会将我给定的任何函数应用于生成惰性列表,也就是会给我所有的偶数。
我相信在 Ruby 中也能够实现类似的操作,但是我似乎无法找到方法。
使用 Ruby 1.9,你可以使用 Enumerator 类。这是来自文档的一个示例:
fib = Enumerator.new { |y|
a = b = 1
loop {
y << a
a, b = b, a + b
}
}
p fib.take(10) #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
此外,这是一个不错的技巧:
Infinity = 1.0/0
range = 5..Infinity
p range.take(10) #=> [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
然而,这个只适用于连续的值。
require 'backports'
来获取这个功能。 :-) - Marc-André Lafortunefib.map {|x| x+1}.take(10)
将不起作用,因为 map 会尝试创建一个数组。还要注意,如果您两次执行 fib.take(10)
,那么元素将被计算两次(与惰性列表不同,惰性列表在计算后将元素保留在内存中)。因此,这并不完全等同于惰性列表。 - sepp2kfib.map
的 Enumerator 等效操作,你需要使用 fib.enum_for(:map)
。 - Chuck最近在ruby trunk中添加了Enumerable::Lazy,我们将在ruby 2.0中看到它。
a = data.lazy.map(&:split).map(&:reverse)
该方法不会立即被执行。
其结果是 Enumerable::Lazy 类的实例,可以进一步进行懒惰链接。如果您想要获取实际结果,请使用#to_a
、(#take(n)
#take
现在也是懒惰的,请使用#to_a
或#force
等)。
如果您想了解更多关于这个主题和我的 C 补丁,请参阅我的博客文章Ruby 2.0 Enumerable::Lazy
惰性序列(自然数):
Inf = 1.0/0.0
(1..Inf).take(3) #=> [1, 2, 3]
懒惰的范围(偶数):
(0..Inf).step(2).take(5) #=> [0, 2, 4, 6, 8]
请注意,您还可以使用一些方法扩展Enumerable
,以便更方便地处理惰性范围等内容:
module Enumerable
def lazy_select
Enumerator.new do |yielder|
each do |obj|
yielder.yield(obj) if yield(obj)
end
end
end
end
# first 4 even numbers
(1..Inf).lazy_select { |v| v.even? }.take(4)
output:
[2, 4, 6, 8]
Enumerator
类的lazy_map和lazy_select实现:http://www.michaelharrison.ws/weblog/?p=163
infinite_range.filter {|x| f(x)}.take(5)
这样的操作,因此它不像惰性列表那样运行。 - sepp2klazy_select
等的实现,适用于Enumerator
类。 - horseyguyEnumerator.produce
,与.lazy
结合使用可以完全实现你所描述的功能,但以一种Ruby风格的方式。Enumerator.produce(0) do
_1 + 2
end.lazy
.map(&:to_r)
.take(1_000)
.inject(&:+)
# => (999000/1)
def fact(n)
= Enumerator.produce(1) do
_1 + 1
end.lazy
.take(n)
.inject(&:*)
fact 6 # => 720
0.step{|i| puts i}
这将以两倍于无限循环速度的方式循环:
0.step(nil, 2){|i| puts i}
只要你愿意,这将会无限循环(结果为枚举器)。
table_of_3 = 0.step(nil, 3)
正如我在评论中所说的那样,实现诸如惰性数组之类的东西是不明智的。
在某些情况下,使用 Enumerable 可以很好地工作,但与惰性列表有所不同:像 map 和 filter 这样的方法不会被惰性评估(因此它们不适用于无限可枚举对象),并且已经计算过的元素不会被存储,因此如果您两次访问一个元素,则会计算两次。
如果您想要在 Ruby 中获得与 Haskell 的惰性列表完全相同的行为,则可以使用 lazylist gem 实现惰性列表。
[].lazy
)或枚举器,这些枚举器是可枚举模块中迭代器的返回值(例如each_slice-[].each_slice(2).lazy
)。请注意,在可枚举模块中,一些实例方法返回更原始的值,如true或false,一些返回集合,如数组,一些返回枚举器。如果没有给出块,则某些返回枚举器。
但对于我们的例子,IO类还具有一个迭代器each_line,它返回一个枚举器,因此可以与“lazy”一起使用。返回枚举器的美妙之处在于,它实际上并不会将它正在处理的集合(例如大型数组)加载到内存中。相反,它有一个指向集合的指针,然后存储算法(例如each_slice(2)
),当您想要使用to_a
之类的东西处理集合时,它将在该集合上使用该算法。例如。
如果你正在使用枚举器来获得更高的性能,现在你可以将lazy附加到枚举器上。这样,你就不需要遍历整个集合来匹配条件了:
file.each_line.select { |line| line.size == 5 }.first(5)
您可以调用lazy:
file.each_line.lazy.select { |line| line.size == 5 }.first(5)
Ruby的数组会根据需要动态扩展。您可以应用块来返回诸如偶数之类的内容。
array = []
array.size # => 0
array[0] # => nil
array[9999] # => nil
array << 1
array.size # => 1
array << 2 << 3 << 4
array.size # => 4
array = (0..9).to_a
array.select do |e|
e % 2 == 0
end
# => [0,2,4,6,8]
这有帮助吗?
Enumerable
,就像Sanjana的答案一样。 - Marc-André Lafortune