Concepts / Algorithms Working Through Iterators

Algorithms Working Through Iterators

An algorithm operates on its data through iterators and knows nothing about the container that stores the elements.

  • Programming

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?

  • Nothing changes, because the values are the same

  • The routine must be rewritten, because its way of walking the data no longer matches the layout

Reveal answer
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.

Containerholds the elementsIteratorthe middle partyAlgorithmdoes the worksupplies on requestoperates through
How the data, the iterator, and the algorithm connect, and where the algorithm's knowledge stops. The algorithm never touches the container directly.

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.

Algorithmrequests iteratorsContainersupplies iteratorsbegin()starting iteratorend()ending iterator
Who calls whom, in what order, when an algorithm asks a container for its starting and ending positions. The container is passive: it only answers.

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.

Start at beginfirst position in rangeRead elementvalue at current positionAdvance iteratormove to next positionReached end?compare with end()Stoprange finishednoyes
How the algorithm advances step by step through the elements, and when it stops: it stops once the iterator reaches the position supplied by end().
Worked example

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.

  1. 1
    Receive the range

    The routine holds a starting iterator and an ending iterator. It has no reference to the container or its layout.

  2. 2
    First 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.

  3. 3
    Second step

    The iterator is at 7. The running total becomes 11. The iterator moves forward, and it is still not at the ending position.

  4. 4
    Third step

    The iterator is at 1. The running total becomes 12. The iterator moves forward, and it is still not at the ending position.

  5. 5
    Fourth 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.

Result

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?

  • The algorithm and the container must both change

  • Only the container changes; the algorithm stays the same

Reveal answer
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 fails

    The 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() and end(). The knowledge is asymmetric, not absent.

    Do this instead

    Say 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 fails

    The 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 instead

    Treat 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 fails

    The 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 instead

    The 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 fails

    That 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 instead

    As long as the container still supplies iterators, the algorithm can stay the same when the storage changes.

Tracing the Handoff Yourself

MEDIUM

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() or end()?
  • 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() and end().
  • 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.

Related concepts

More concepts starting with A