carlfilips
carlfilips

Reputation: 2630

How can I get a lazy array in Ruby?

How can I get a lazy array in Ruby?

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.

I'm sure I can do such things in Ruby, but can't seem to work out how.

Upvotes: 30

Views: 12074

Answers (9)

maskkskf wseggw
maskkskf wseggw

Reputation: 23

I surprised no one answered this question appropriately yet

So, recently I found this method Enumerator.produce which in conjunction with .lazy does exactly what you described but in ruby-ish fashion

Examples

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 

Upvotes: 2

Daniel Viglione
Daniel Viglione

Reputation: 9507

The right answer has already identified the "lazy" method, but the example provided was not too useful. I will give a better example of when it is appropriate to use lazy with arrays. As stated, lazy is defined as an instance method of the module Enumerable, and it works on EITHER objects that implement Enumerable module (e.g. arrays - [].lazy) or enumerators which are the return value of iterators in the enumerable module (e.g. each_slice - [].each_slice(2).lazy). Note that in Enumerable module, some of the instance methods return more primitive values like true or false, some return collections like arrays and some return enumerators. Some return enumerators if a block is not given.

But for our example, the IO class also has an iterator each_line, which returns an enumerator and thus can be used with "lazy". The beautiful thing about returning an enumerator is that it does not actually load the collection (e.g. large array) in memory that it is working on. Rather, it has a pointer to the collection and then stories the algorithm (e.g. each_slice(2)) that it will use on that collection, when you want to process the collection with something like to_a, for example.

So if you are working with an enumerator for a huge performance boost, now you can attach lazy to the enumerator. So instead of iterating through an entire collection to match this condition:

file.each_line.select { |line| line.size == 5 }.first(5)

You can invoke lazy:

file.each_line.lazy.select { |line| line.size == 5 }.first(5)

If we're scanning a large text file for the first 5 matches, then once we find the 5 matches, there is no need to proceed the execution. Hence, the power of lazy with any type of enumerable object.

Upvotes: 0

steenslag
steenslag

Reputation: 80105

This will loop to infinity:

0.step{|i| puts i}

This will loop to infinity twice as fast:

0.step(nil, 2){|i| puts i}

This will go to infinity, only if you want it to (results in an Enumerator).

table_of_3 = 0.step(nil, 3)

Upvotes: 1

Mr. Black
Mr. Black

Reputation: 12122

In Ruby 2.0.0, they were introduced new method "Lazy" in Enumerable class.

You can check the lazy function core and usage here..

http://www.ruby-doc.org/core-2.0/Enumerator/Lazy.html
https://github.com/yhara/enumerable-lazy
http://shugomaeda.blogspot.in/2012/03/enumerablelazy-and-its-benefits.html

Upvotes: 4

gregolsen
gregolsen

Reputation: 915

Recently Enumerable::Lazy has been added to ruby trunk. We'll see it in ruby 2.0. In particular:

a = data.lazy.map(&:split).map(&:reverse)

will not be evaluated immediately.
The result is instance of Enumerable::Lazy, that can be lazy chained any further. If you want to get an actual result - use #to_a, #take(n) (#take is now lazy too, use #to_a or #force), etc.
If you want more on this topic and my C patch - see my blog post Ruby 2.0 Enumerable::Lazy

Upvotes: 22

horseyguy
horseyguy

Reputation: 29915

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

Upvotes: 6

sepp2k
sepp2k

Reputation: 370455

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.

Upvotes: 1

carlfilips
carlfilips

Reputation: 2630

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.

Upvotes: 41

carlfilips
carlfilips

Reputation: 2630

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?

Upvotes: -4

Related Questions