Algorithms Working Through Iterators
An algorithm operates on its data through iterators and knows nothing about the container that stores the elements.
Summing Values Without Knowing Where They Live
Imagine writing a routine that adds up every value in a collection. The arithmetic is the same no matter where the values live. They might sit in a fixed-size block, in a linked chain of nodes, or in a file that is read one item at a time. The adding logic never changes, yet a naive routine can end up tied to one particular storage layout. If it walks through that layout directly, then changing how the data is stored forces a rewrite of the routine, even though the adding logic is untouched.
This article examines the way out of that tangle. Instead of letting the algorithm reach directly into storage, the design places a third party between them. That third party is the iterator. The algorithm works through the iterator, and the container hands the iterator out when it is requested. The rest of this article traces how that handoff works and why it is valuable.
Three Storage Layouts and One Routine
Before looking at the iterator, hold the problem in mind with three concrete storage layouts for the same five values. In a fixed-size block, the values sit side by side and each one is reached by its position. In a linked chain, each value sits in a node that points to the next node, so reaching the third value means following two links. In a file read one item at a time, the values are not all in memory at once; each one is fetched as the reader moves forward.
What do you think happens?
If a summing routine were written to walk the fixed-size block by reading positions directly, what happens to that routine when the same five values are moved into a linked chain?
Reveal answer
The routine must be rewritten, because its way of walking the data no longer matches the layout.
The source describes the problem directly: a routine written to walk one particular storage layout becomes tied to that layout, so changing the storage forces a rewrite even though the adding logic never changed.
The Iterator as a Third Party
An iterator is the middle party in this design. The algorithm operates on its data through iterators, and it knows nothing about the container in which the elements are stored. The container does not reach out to the algorithm either. Its only role in this relationship is to supply iterators when someone asks for them. Each side therefore has a narrow contract: the algorithm needs only an iterator, and the container needs only the ability to produce one.
Who Asks Whom: begin() and end()
The request between the two parties is named in the source through two calls, begin() and end(). The algorithm asks the container for a starting position and an ending position. The container answers by providing iterators. The algorithm receives those iterators and uses them to move through the elements. The order matters: the algorithm first asks for the start, then asks for the end, and only then begins moving between them.
Notice what is absent from this exchange. No algorithm refers to the container's internal layout, and no container refers to the algorithms that will read it. The container never learns which routine is asking; it simply answers the request with iterators.
Stepping From begin to end
Once the algorithm holds the starting and ending iterators, it advances step by step. The iterator at begin() is the first position of the range. The algorithm reads the element at its current position, moves the iterator forward, and checks whether the iterator has reached the ending position. If it has not, the loop repeats. If it has, the algorithm stops. The algorithm never needs to know whether the elements sit in a block, a chain, or a file; it only needs to advance and compare.
Summing a Range Without Knowing Its Storage
A routine must add up the values in a range whose elements are 4, 7, 1, and 6. The routine is given an iterator from begin() and an iterator from end(). The storage behind them is not visible to the routine.
- 1Receive the range
The routine holds a starting iterator and an ending iterator. It has no reference to the container or its layout.
- 2First step
The iterator is at the first element, 4. The running total becomes 4. The iterator moves forward, and it is not yet at the ending position.
- 3Second step
The iterator is at 7. The running total becomes 11. The iterator moves forward, and it is still not at the ending position.
- 4Third step
The iterator is at 1. The running total becomes 12. The iterator moves forward, and it is still not at the ending position.
- 5Fourth step
The iterator is at 6. The running total becomes 18. The iterator moves forward and now equals the ending position, so the loop stops.
The total is 18. The same routine would produce 18 if the four values were stored in a fixed-size block, a linked chain, or a file read one item at a time, because the routine only ever used the iterator.
Dependency Direction and Flexibility
The separation runs in two directions, and both directions matter. The algorithm depends on iterators. The container depends only on being able to supply iterators. Because of this, storage and algorithms can change independently. The source describes the result as very general and flexible software.
The reason follows directly from the mechanism. Because each algorithm depends only on iterators, one algorithm can be applied to any container that supplies them. Because each container depends only on supplying iterators, one container can be read by any algorithm that works through them. Changing the storage therefore leaves the algorithm alone, and changing the algorithm leaves the storage alone, as long as the iterator handoff stays the same.
What do you think happens?
Suppose the five values are moved from a fixed-size block into a file read one item at a time, and the summing routine still receives its range from begin() and end(). Which components must change?
Reveal answer
Only the container changes; the algorithm stays the same.
The algorithm depends only on iterators, and the container only needs to supply them. The storage can change while the summing routine is left untouched.
Common Misreadings of the Separation
The separation between algorithms and containers is easy to misread. The following misreadings are the ones most worth correcting, because each one hides the actual mechanism.
Common mistake The algorithm and the container are unaware of each other entirely.
Why it failsThe source is precise: the algorithm operates on its data through iterators and knows nothing about the container. The container, however, is the party that answers requests for iterators through
begin()andend(). The knowledge is asymmetric, not absent.Do this insteadSay that the algorithm knows about iterators, and the container knows how to supply them. Each side has one narrow obligation.
Common mistake The iterator is just another name for the container.
Why it failsThe iterator is a third party placed between the algorithm and the storage. The container hands it out on request, and the algorithm works through it.
Do this insteadTreat the iterator as the handoff object that sits between the two parties.
Common mistake The container calls the algorithm when it wants the algorithm to run.
Why it failsThe source states that a container knows nothing about the algorithms that operate on its elements. Its only job is to supply iterators when someone asks.
Do this insteadThe algorithm initiates the request by calling begin() and end(). The container responds to that request.
Common mistake Changing the storage always requires rewriting the algorithm.
Why it failsThat rewrite is exactly the problem the design avoids. A routine tied to one storage layout must be rewritten, but a routine that works through iterators is not tied to that layout.
Do this insteadAs long as the container still supplies iterators, the algorithm can stay the same when the storage changes.
Tracing the Handoff Yourself
A counting routine must count how many values in a range are greater than 5. The range is given by an iterator from begin() and an iterator from end(). Trace the handoff: list, in order, every call the routine makes to the container, then explain what the routine knows about the storage when it reads each value. Finally, state which of the two parties would need to change if the values were moved to a different storage layout.
Hints
- Start with the request: which call happens first,
begin()orend()? - Think about what the routine can and cannot see. It receives iterators, not the container itself.
- Remember the question about change: the source says storage and algorithms can change independently.
Key Takeaways
- An algorithm operates on its data through iterators and knows nothing about the container that stores the elements.
- A container knows nothing about the algorithms that operate on its elements; it only supplies iterators when someone asks, through calls such as
begin()andend(). - The dependency is asymmetric: the algorithm depends on the iterator, and the container depends only on being able to produce iterators.
- Because each side depends only on iterators, one algorithm can work on any container that supplies them, and storage and algorithms can change independently, which the source describes as very general and flexible software.