tags:

views:

131

answers:

4

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.

+1  A: 

Ruby 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?

carlfilips
thank you--believe in your dreams
carlfilips
Wasn't the question about *lazy* generation of objects? The correct solution is not about arrays (which are always completely determined in Ruby) but about other types of `Enumerable`, as in Sanjana's answer.
Marc-André Lafortune
I just started ruby, I thought this was correct. I will fix this--may your wishes come true
carlfilips
+9  A: 

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.

carlfilips
they dont have to be consecutive: (10..100).step(20).take(5) #=> [10, 20, 30, 40, 50]
banister
In Ruby 1.8, you can `require 'backports'` to get this functionality too :-)
Marc-André Lafortune
Note though that doing `fib.map {|x| x+1}.take(10)` will not work, because map will try to create an array. Also note that if you do `fib.take(10)` twice, the elements will be calculated twice (unlike lazy lists where elements are kept in memory once they're calculated). So this isn't exactly equivalent to lazy lists.
sepp2k
In order to do the Enumerator equivalent of `fib.map`, you'd instead do `fib.enum_for(:map)`.
Chuck
+2  A: 

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 Enumeratorclass that can be found here: http://www.michaelharrison.ws/weblog/?p=163

banister
Note that, like with the enumerator solution, you can't do things like `infinite_range.filter {|x| f(x)}.take(5)`, so it doesn't behave like lazy lists.
sepp2k
@sepp2k, added link to site that has implementations of `lazy_select` etc, for `Enumerator` class
banister
+1  A: 

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.

sepp2k