How can I get a lazy array in Ruby? E.g., in Haskell, I can talk about [1..]
, which is an infinite list, lazily generated as needed. I can also do things like iterate (+2) 0
, which applies whatever function I give it to generate a lazy list. In this case, it would give me all even numbers. Anyway, I'm sure I can do such things in Ruby, but can't seem to work out how.
views:
131answers:
4Ruby Arrays dynamically expand as needed. You can apply blocks to them to return things like even numbers.
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]
Does this help?
With Ruby 1.9 you can use the Enumerator class. This is an example from the docs:
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]
Also, this is a nice trick:
Infinity = 1.0/0
range = 5..Infinity
p range.take(10) #=> [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
This one only works for consecutive values though.
Lazy range (natural numbers):
Inf = 1.0/0.0
(1..Inf).take(3) #=> [1, 2, 3]
Lazy range (even numbers):
(0..Inf).step(2).take(5) #=> [0, 2, 4, 6, 8]
Note, you can also extend Enumerable
with some methods to make working with lazy ranges (and so on) more convenient:
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]
More info here: http://banisterfiend.wordpress.com/2009/10/02/wtf-infinite-ranges-in-ruby/
There are also implementations of lazy_map, and lazy_select for the Enumerator
class that can be found here:
http://www.michaelharrison.ws/weblog/?p=163
As I already said in my comments, implementing such a thing as lazy arrays wouldn't be sensible.
Using Enumerable instead can work nicely in some situations, but differs from lazy lists in some points: methods like map and filter won't be evaluated lazily (so they won't work on infinite enumerables) and elements that have been calculated once aren't stored, so if you access an element twice, it's calculated twice.
If you want the exact behavior of haskell's lazy lists in ruby, there's a lazylist gem which implements lazy lists.