Search code examples
c++algorithmc++17foldaccumulate

Is there a fold like algorithm in std (or failing that: boost) available?


A very simpel example is multiplication - suppose I have a vector:

std::vector<int> ints = {1,2,3,4};

With a naive approach I can just use std::accumulate (or std::reduce) and it looks like this:

int result = std::accumulate(ints.begin(), ints.end(), int{}, [](const int &a, const int &b){return a*b;});

but since the initial value is zero - the result becomes zero as well (For this specific case, one way I could fix it is by putting a '1' as initial).

I would rather use an algorithm that does the above but without an initial value 'side-effect' (ie. just multiply the numbers in vector).

A similar problem is often encountered within string handling where a delimiter must be inserted between elements.


Solution

  • What you're talking about can be reframed as a generalisation of accumulate over the last N-1 elements of your range, with the 1st element being the initial value.

    So you can just write:

    std::accumulate(std::next(std::begin(ints)), std::end(ints), *std::begin(ints), OP);
    

    You have to assume that ints is non-empty, though, which raises my main point: what should a hypothetical standard function return when the range is empty? Should its results simply be undefined? Is that sensible?

    (current draft) 237) accumulate is similar to the APL reduction operator and Common Lisp reduce function, but it avoids the difficulty of defining the result of reduction on an empty sequence by always requiring an initial value

    Accumulate sidesteps this issue and provides a boatload of flexibility, by doing things the way it does. I think that's a good thing.

    Combined with the ability to simply provide an appropriate initial value like 1 for your operation over the whole range, I'm not convinced there's much need for this hypothetical alternative in the standard.

    It might also be difficult to come up with two names for it that mirror the already-asymmetrically-named "accumulate" and "reduce".


    template <class InputIt, class T, class BinaryOperation>
    T fold_if_you_really_want_to(InputIt first, InputIt last, BinaryOperation op)
    {
        // UB if range is empty. Whatevs.
        T init = *first;
        return std::accumulate(++first, last, std::move(init), std::move(op));
    }
    

    …or something like that anyway. Note that this necessarily copies the first element; you could avoid that if you weren't lazy by calling into std::accumulate like I did. 😊