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.
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:
int i = 0
i++
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:
int i = 0
i++
i < n
Our standard linked list loops are slightly different. Some people like to use a for-loop.
For these example linked list loops, I will break the ADT rules of encapsulation and make all data public.
We still have the three fundamental pieces of a traversal loop:
Node* it = collection.head
it = it->next
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:
Node* it = collection.head
it = it->next
it != nullptr
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:
int i = 0
i++
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:
ArbitraryContainer::iterator it = collection.begin()
it++
it < collection.end()
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:
We just have to decide: T oneEntry
or T& oneEntry
or const T& oneEntry
?