Loops & Iterators

Thomas J. Kennedy

Contents:

Iterators are an interesting and powerful design paradigm.

One could claim that iterators are not design, and that any discussion of iterators should be in a writing code module. However, we have only looked at the C++ iterator interface. There are iterators in C++, Java, Python 3, and many other languages.

1 Data Structure Specific Loops

Let us revisit some familiar loops, starting with arrays.

1.1 Arrays

We are all familiar with the standard array for-loop.

// let collection be an array of size n
// for a datatype T
T* collection = new T[n]

for (int i = 0; i < n; i++) {
    // do stuff
}

The for-loop has a few pieces. We are interested in three specific parts:

  1. Initialization: int i = 0
  2. Increment Operation: i++
  3. Stop Condition: i < n

The //do stuff comment is a placeholder for the body of the loop. One of my early programming teachers always said:

There is no such thing as a for-loop or do-while-loop. All loops are while loops.

Conceptually, this makes sense. Pick any for-loop, for-each loop, or do-while loop. They are little more than syntactic sugar. With a little effort, any loop can be rewritten as a while loop. Let us rewrite our for-loop:

// let collection be an array of size n
// for a datatype T
T* collection = new T[n]

int i = 0;
while (i < n) {
    // do stuff

    i++;
}

We still have the same three pieces:

  1. Initialization: int i = 0
  2. Increment Operation: i++
  3. Stop Condition: i < n

1.2 Linked Lists

Our standard linked list loops are slightly different. Some people like to use a for-loop.

 

We still have the three fundamental pieces of a traversal loop:

  1. Initialization: Node* it = collection.head
  2. Increment Operation: it = it->next
  3. Stop Condition: it != nullptr
// Let collection be a Linked List that stores data of type T.
LinkedList<T> collection;

for (Node* it = collection.head; it != nullptr; it = it->next) {
    // do stuff
}

Let us write the more elegant (i.e., sane) linked-list-while-loop.

// Let collection be a Linked List
// that stores data of type T.
LinkedList<T> collection;

Node* it = collection.head;

while (it != nullptr) {
    // do stuff

    it = it->next;
}

The same three fundamental pieces are still present:

  1. Initialization: Node* it = collection.head
  2. Increment Operation: it = it->next
  3. Stop Condition: it != nullptr

2 Abstraction & Iterators

When we start working with the C++ STL (e.g., using std::vector and std::list) we get to have more fun!

// let collection be a vector of size n
// for a datatype T
std::vector<T> collection(n);

for (int i = 0; i < collection.size(); i++) {
    // do stuff
}

Nope… that just looks like our usual array-for-loop. Well, that is actually not very surprising. A std::vector is a fancy array. What about the while-loop?

// let collection be a vector of size n
// for a datatype T
std::vector<T> collection;

int i = 0;
while (i < collection.size()) {
    // do stuff

    i++;
}

Nope… that loop is almost identical to our array-while-loop. We still have the same three fundamental pieces:

  1. Initialization: int i = 0
  2. Increment Operation: i++
  3. Stop Condition: i < collection.size()

What if we introduce iterators?

// let collection be a vector of size n for a datatype T
std::vector<T> collection(n);

std::vector<T>::iterator it = collection.begin();
while (it < collection.end()) {
    // do stuff

    it++;
}

Our loop is now more general. The only data structure specific pieces are the two lines that start with std::vector. Let us use a C++ Type Alias.

// let collection be a vector of a datatype T
using ArbitraryContainer = std::vector<T>;

ArbitraryContainer collection;

ArbitraryContainer::iterator it = collection.begin();
while (it < collection.end()) {
    // do stuff

    it++;
}

Now… What does that gain us? Suppose I am tired of using std::vector. Let us switch to std::list:

// let collection be a vector of a datatype T
using ArbitraryContainer = std::list<T>;

ArbitraryContainer collection;

ArbitraryContainer::iterator it = collection.begin();
while (it < collection.end()) {
    // do stuff

    it++;
}

We changed exactly one line.

using ArbitraryContainer = std::vector<T>;

became

using ArbitraryContainer = std::list<T>;

And… we still find the three fundamental traversal-loop operations:

  1. Initialization: ArbitraryContainer::iterator it = collection.begin()
  2. Increment Operation: it++
  3. Stop Condition: it < collection.end()

3 Turn Abstraction Up To Eleven

There is one final loop left to discuss. We replaced data structure specific loops with iterator based loops. There is one more layer of abstraction… the range-based or for-each loop.

using ArbitraryContainer = std::list<T>;

ArbitraryContainer collection;

for (T oneEntry : collection) {
    // do stuff

}

The compiler will take this loop, and use the iterators automatically. The compiler will automatically handle:

  1. Initialization
  2. Increment Operation
  3. Stop Condition

We just have to decide: T oneEntry or T& oneEntry or const T& oneEntry?