We're hiring!

We're actively seeking developers & designers for our new Detroit location. Learn more

Enumerating Ruby: Enumerator.new

The previous post in this series covered Object#enum_for’s ability to make a collection out of any method that yields values to a block. We saw that #enum_for opened up a lot of opportunities for elegant code and simple tests, but we also saw that we were limited in what we could do with the enumerator if we wanted to stay lazy.

In this post, we’ll explore an alternative solution, using Enumerator.new, which will allow us to filter the scores and still delay the evaluation of our collection until the client absolutely needs it.

Given:

1
2
3
4
5
6
7
8
9
10
11
12
13
class RedWingsFan
  def initialize(hockey_client)
    @hockey_client = hockey_client
  end

  def red_wings_goal_scorers
    @hockey_client.enum_for(:listen_for_hockey_scores).select do |goal|
      goal.team == "Red Wings"
    end.map do |goal|
      goal.scorer
    end
  end
end

Problem:

We want our method to return immediately, and give us an object we can enumerate on-demand.

Solution:

Use Enumerator.new to lazily produce elements, so we don’t have to evaluate everything at once.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class RedWingsFan
  def initialize(hockey_client)
    @hockey_client = hockey_client
  end

  def red_wings_goal_scorers
    Enumerator.new do |yielder|
      @hockey_client.listen_for_hockey_scores do |goal|
        yielder << goal.scorer if goal.team == "Red Wings"
      end
    end
  end
end

red_wings_fan = RedWingsFan.new(HockeyClient.new)
puts "Red Wings scorers:"
red_wings_fan.red_wings_goal_scorers.each do |scorer|
  puts scorer
end

Now our collection is lazy—it does not wait for the game to complete before it returns something we can enumerate, and every iteration of our #each block runs as soon as the goal is made.

Another interesting thing that you can do with Enumerator.new is build an infinite collection. Here’s one for the fibonacci sequence:

1
2
3
4
5
6
7
8
9
10
def fibonacci
  Enumerator.new do |yielder|
    previous = [1, 1]
    while
      current = previous.inject(:+)
      yielder << previous.shift
      previous << current
    end
  end
end

Warning: Do not call #to_a on an infinite enumerator such as this unless you want to use a lot of memory really quickly.

The full source for this post is available here.

My next post will revisit our previous solution to see if there’s a more elegant way to solve the problem.

Enumerating Ruby Series:

This entry was posted in Languages and tagged . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

One Trackback

  1. [...] Enumerator.new [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">