Rewbie Newbie

Documenting my path on becoming a Rails Developer.

Internal vs. External Iterators

I was wrapping my head around the Enumerator class while reading Pickaxe and it mentioned that the Enumerator class is how Ruby implements external iterators. Of course this caught my attention, if there is an external iterator, then there must also be an internal iterator. I did a little digging and my suspicion was confirmed.

Definition

Internal iterators are iterators that are built into the collection itself. For example, the most vanilla internal iterator in Ruby is the #each method of an Array instance. The #each method traverses over each array element and passes each element to the code block that is associated with the #each method call.

External iterators on the other hand, are objects that traverse a collection, specifically, the logic of how a collection is traversed is dictated by an external object.

Implementation

Creating an internal iterator is to simply call the #each method on a collection and attach a block to the #each method call.

1
2
3
4
5
array = (1..10).to_a

array.each { |n| print n.to_s + " " }

#=> 1 2 3 4 5 6 7 8 9 10

To create an external iterator, the most straight-forward way is to call #to_enum on a collection and a new enumerator object will be returned to you. What is also worth noting is when you call an internal iterator on a collection without attaching a block, it is equivalent to calling #to_enum on the collection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
array = (1..10).to_a

enum = array.to_enum

enum.class
#=> Enumerator

enum = array.each
enum.class
#=> Enumerator

enum = array.map
enum.class
#=> Enumerator

Once you have an external iterator created, you have much greater control over how you can iterate over your collection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
array = (1..10).to_a
enum = array.to_enum

enum.next
#=> 1
enum.next
#=> 2
enum.next
#=> 3

enum.rewind

enum.next
#=> 1

sections = array.each_slice(4)

sections.class
#=> Enumerator

section.next
#=> [1, 2, 3, 4]
sections.next
#=> [5, 6, 7, 8]
sections.next
#=> [9, 10]
sections.next
#=> StopIteration: iteration reached an end

sections.rewind

sections.next
#=> [1, 2, 3, 4]

Benefits and Drawbacks

Internal iterators are easy to implement but lack flexibility. The behavior of an internal iterator is preset by the collection. For example, both #map and #each will always traverse each element of an array from the beginning until the end. Due to these characteristics, internal iterators are suitable for when you need to linearly traverse a collection.

But when linear traversion is not what you are after, expect to write a few more lines of code and exercise control with an external iterator.