Run two <algorithm>s side by side on the same input iterator range
Asked Answered
C

3

9

If I want to calculate the sum of a bunch of numbers retrieved from an std::istream, I can do the following:

// std::istream & is = ...
int total = std::accumulate(std::istream_iterator<int>(is),
                            std::istream_iterator<int>(),
                            0);

However, if I want to calculate their average, I need to accumulate two different results:

  • the total sum (std::accumulate)
  • the total count (std::distance)

Is there any way to "merge" these two algorithms and run them "side by side" in a single pass of an iterator range? I would like to do something like:

using std::placeholders;
int total, count;
std::tie(total, count) = merge_somehow(std::istream_iterator<int>(is),
                                       std::istream_iterator<int>(),
                                       std::bind(std::accumulate, _1, _2, 0),
                                       std::distance);
double average = (double)total / count;

Is this possible?

Corell answered 10/12, 2013 at 18:51 Comment(0)
T
11

A ready-made solution for this sort of single-pass accumulation is implemented by Boost.Accumulators. You make a single accumulator, say for sum, count and average, populate it, and then extract all three results at the end.

Theatrical answered 10/12, 2013 at 18:54 Comment(0)
E
9

You cannot merge two different algorithms to be interleaved. The algorithms control the flow, and you can only have one flow. Now, in your particular case you can simulate it:

int count = 0;
int total = std::accumulate(std::istream_iterator<int>(is),
                            std::istream_iterator<int>(),
                            0,
                            [&](int x, int y) { ++count; return x+y; });
Einhorn answered 10/12, 2013 at 18:56 Comment(0)
E
-1

This is total hack, but something like this:

#include <iostream>
#include <algorithm>
#include <tuple>
#include <iterator>
#include <sstream>

namespace Custom {
    template <class InputIterator, class T, class Bind, typename... Args>
       std::tuple<Args...> accumulate (InputIterator first, InputIterator last, 
           T init, T& output, Bind bind, Args&... args)
    {
      while (first!=last) {
        init = bind(init, *first, args...);
        ++first;
      }
      output = init;
      std::tuple<Args...> tuple(args...);
      return tuple;
    }
}

int main() {
    int total = 0, count = 0;
    std::istringstream is;
    is.str("1 2 3 4 5");
    std::tie(count) = Custom::accumulate(std::istream_iterator<int>(is),
        std::istream_iterator<int>(),
        0,
        total,
        std::bind([&] (int a, int b, int& count) { ++count; return a + b; }, 
        std::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
        count);
    std::cout << total << " " << count;
    return 0;
}
Embroideress answered 10/12, 2013 at 19:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.