Concepts / Operations Supplied as Arguments

Operations Supplied as Arguments

Accumulate computes an operation, such as a sum, over a sequence by visiting its elements from begin to end and building a running total from an initial value.

  • Programming

Predict Before You Read

Accumulate is a standard-library algorithm that adds up the elements of a sequence. Before reading any further, take a moment to make a guess. Imagine a list of four doubles, 1.0, 2.0, 3.0 and 4.0, and a call that passes the beginning of the list, the end of the list, and the starting value 0.0. What total do you expect the call to return, and which part of the call decides that total: the list, the two iterator arguments, or the 0.0?

Keep your guess in mind. The rest of this article explains exactly how the total is built, so you can check your prediction against the reasoning, step by step.

What Accumulate Computes

Definition

Accumulate computes an operation, such as a sum, over a sequence by visiting its elements from begin to end and building a running total from an initial value.

Two ideas in that definition do most of the work. The first is the running total: a single value that starts at the initial value and changes each time an element is taken in. The second is the traversal: the algorithm visits the elements in order, starting at the beginning of the sequence and moving toward its end. The final running total is the value the call returns.

Reading the Three Arguments

The source states that the three arguments are the start position, the end position, and the initial value, and that each one changes the result. Here is a call that follows the template. The list v holds the four values from the opening prediction.

cpp
Output
total = 10.0
Notes on the code
  • v.begin() is the start position: the first element is included
  • v.end() is the end position: the traversal stops before it, so it is not an element that gets added
  • 0.0 is the initial value: the running total begins here before any element is added

Notice that the sum is 10.0 only because the initial value is 0.0. The initial value is not decoration: it is the first value of the running total, and it is carried into every later step. Change it, and every intermediate total changes with it, which is exactly why each argument matters.

Positions the Call Covers

The diagram below lays the list out by position. Read it as a map of which elements the call visits. For the full call from the previous section, every element from the first to the last is visited, and the end position sits just past the final element, where the traversal stops.

v[0]1.0v[1]2.0v[2]3.0v[3]4.0end positionstop here, no elementnextnextnextreaches end
Which positions does the call cover, where does the begin bound start, where does the end bound stop, and which element is excluded?

The begin bound is the position of v[0], the first element visited. The end bound is the position after v[3]. Moving the end bound back to the position of v[2], as in the partial call, removes v[2] and v[3] from the visit, which is why the result changes from 10.0 to 3.0 while the list itself stays the same.

Building the Running Total Step by Step

The clearest way to understand accumulate is to follow the running total through the list yourself. Each step takes the current total and adds the next element to it. Mathematically, if x_1 through x_n are the elements visited and init is the initial value, the running total s_k after visiting k elements follows this rule.

Formula, step by step
  1. 1
    $s_0$ is the running total before any element is visited, which is the initial value
  2. 2
    $x_k$ is the k-th element taken in from the beginning of the sequence
  3. 3
    $s_k$ is the running total after that element is added, and it is carried forward to the next step
  4. 4
    The returned result is the final running total after the last element has been visited
Worked example

Tracing the total for 1.0, 2.0, 3.0, 4.0 starting at 0.0

Find each running total for accumulate(v.begin(), v.end(), 0.0) when v holds 1.0, 2.0, 3.0 and 4.0.

  1. 1
    Start

    The running total begins at the initial value, 0.0. No element has been taken in yet.

  2. 2
    Visit 1.0

    The total becomes 0.0 + 1.0 = 1.0. This value is carried forward.

  3. 3
    Visit 2.0

    The total becomes 1.0 + 2.0 = 3.0.

  4. 4
    Visit 3.0

    The total becomes 3.0 + 3.0 = 6.0.

  5. 5
    Visit 4.0

    The total becomes 6.0 + 4.0 = 10.0. The end position has now been reached, so the traversal stops.

Result

The call returns 10.0, the last running total in the trace.

start0.0after 1.01.0after 2.03.0after 3.06.0after 4.010.0returned10.0add 1.0add 2.0add 3.0add 4.0end reached
After each element is visited, what is the accumulated value now, and how does it carry forward to the next element?

Read the diagram from left to right. Each box holds the running total at that moment, and each arrow is one visit to an element. The total you finally get back is the value in the last box before the traversal stops. Your opening prediction can now be checked against this trace: the 0.0 sets where the running total starts, and the list supplies the values that are added to it.

Generalized Algorithms and Swappable Parts

The source describes accumulate as a generalized algorithm. Generalizing here means two things. First, the algorithm is not tied to one container, so it applies to every standard-library sequence. Second, it does not hard-wire the operation applied to the elements, because that operation can be supplied as an argument. The same pattern of visiting elements from begin to end, starting from an initial value, stays the same.

The version you have seen so far applies the most common operator for this algorithm, which is addition for a sum. The source explains that each generalized algorithm is supplemented by a version that applies the most common operator, which means a more general version also exists. In that general version, the operation is a parameter, so a different operation can be passed in instead of the built-in sum. The source points to a later section for the full details, so treat this section as orientation.

accumulategeneralized algorithmsequenceany standard-librarysequenceinitial valuestarts the running totaloperationsupplied as an argumentvisitsbegins withapplies
How are the sequence, the operation, and the initial value separate parts that can each be swapped without changing the accumulate algorithm itself?

Because the three parts are separate, you can change one without rewriting the others. A different sequence can be passed where the list was, a different initial value can start the total, and a different operation can replace addition. The traversal from begin to end still defines the order in which the elements are taken in.

Finding Where a Result Goes Wrong

When an accumulate result looks wrong, the source's advice is to trace the running total step by step and find the first point where it disagrees with what you expected. Suppose you expected 10.0 but a call returned 110.0. Writing out both traces side by side shows exactly where they split.

start0.0start100.0after 1.01.0after 1.0101.0after 2.03.0after 2.0103.0after 3.06.0after 3.0106.0after 4.010.0after 4.0110.0same list, different initial value
At which step does the running total first diverge from the value I expected, and is the cause the initial value, the bounds, or the operation?

The first disagreement is at the start: the expected trace begins at 0.0, while the actual call begins at 100.0. Every later step inherits that difference, so the final total is off by exactly 100.0. Here the cause is the initial value, not the list and not the operation. Tracing the total in this way tells you which argument to inspect first.

  • Common mistake

    Treating the initial value as a detail that can be ignored

    What it looks like
    accumulate(v.begin(), v.end(), 100.0) over 1.0, 2.0, 3.0, 4.0 returns 110.0, not 10.0.
    Why it fails

    The source states that each argument changes the result. The initial value is the first running total and is carried into every later step.

    Do this instead

    Name the initial value as you read the call and check it first when the total is off by a constant amount.

  • Common mistake

    Assuming the end bound is an element that gets added

    What it looks like

    accumulate(v.begin(), v.begin() + 2, 0.0) returns 3.0, not 10.0, because the traversal stops before the element at that position.

    Why it fails

    The end position marks where the visit stops, so elements at and after it are not included.

    Do this instead

    Count the elements from the begin position up to, but not including, the end position.

  • Common mistake

    Assuming the list determines the whole answer

    What it looks like

    Two calls over the same list with different initial values give totals that differ by the difference between those initial values.

    Why it fails

    The list supplies the elements, but the three arguments together decide the result.

    Do this instead

    Compare all three arguments of two calls before concluding that the list is the cause.

Practice Tracing a Total

EASY

A list w holds 2.0, 5.0 and 1.0. Predict the value of accumulate(w.begin(), w.end(), 0.0) by writing the running total after each element. Then check whether your answer changes if the initial value is 10.0 instead.

Hints
  • Start the running total at the initial value, 0.0, and add 2.0 first.
  • Write each intermediate total on its own line, so you can compare it with the expected value at that step.
  • With the initial value 10.0, every intermediate total should be 10.0 larger than before.
MEDIUM

Using the same list w, find the end position that makes accumulate(w.begin(), ??, 0.0) return 2.0. Explain which element is excluded and why the result matches your trace.

Hints
  • The result 2.0 comes from only the first element of w.
  • The end position must sit just past the first element, so it is the position of the second element.

Key Takeaways

  • Accumulate computes an operation, such as a sum, over a sequence by visiting its elements from begin to end and building a running total from an initial value.
  • The three arguments are the start position, the end position, and the initial value, and each one changes the result.
  • Because the end position marks where the visit stops, only the elements from the begin position up to that point are included.
  • Generalized algorithms work on every standard-library sequence and let the operation be supplied as an argument, so the sequence, operation and initial value are separate parts.
  • When a result looks wrong, trace the running total step by step to find the first step where it disagrees with your expectation, then inspect the argument responsible.

Related concepts

More concepts starting with O