5

I try to implement that summing up all elements of a vector<vector<int>> in a non-loop ways.
I have checked some relevant questions before, How to sum up elements of a C++ vector?.
So I try to use std::accumulate to implement it but I find it is hard for me to overload a Binary Operator in std::accumulate and implement it.
So I am confused about how to implement it with std::accumulate or is there a better way?
If not mind could anyone help me?
Thanks in advance.

Bowen Peng
  • 1,635
  • 4
  • 21
  • 39
  • 4
    `I find it is hard for me to overload a Binary Operator in std::accumulate and implement it.` Why? What have you tried and why didn't it work? – tkausl Nov 03 '19 at 14:29
  • 4
    You can't do it without a loop. `std::accumulate` has a loop inside it, and by using the algorithm you're just using a loop that was already coded for you. – Spencer Nov 03 '19 at 14:33
  • 3
    Don't overload things in the `std` You also need to show some work. This isn't a tutorial site. You need to ask specific questions about work you've tried. – doug Nov 03 '19 at 14:34
  • 1
    You don't overload anything, you pass a binary operation which you create down to std::accumulate. It could be a function, a lambda, or a functional object. – n. m. could be an AI Nov 03 '19 at 14:59
  • No loops? Why not? Well, whatever the rationale... then can you use `goto`? – Eljay Nov 03 '19 at 15:49
  • 1
    The fundamental issue is that you need a loop or you have to add each item sequentially without a loop. The `std::accumulate` uses a loop. Even specialized processor instructions use a loop. So, why avoid loops? – Thomas Matthews Nov 03 '19 at 17:52
  • @ThomasMatthews same reason one would avoid explicit gotos. Loops are at a too low level of abstraction. – n. m. could be an AI Nov 05 '19 at 04:52

7 Answers7

9

You need to use std::accumulate twice, once for the outer vector with a binary operator that knows how to sum the inner vector using an additional call to std::accumulate:

int sum = std::accumulate(
    vec.begin(), vec.end(),                       // iterators for the outer vector
    0,                                            // initial value for summation - 0
    [](int init, const std::vector<int>& intvec){ // binaryOp that sums a single vector<int>
        return std::accumulate(
            intvec.begin(), intvec.end(), // iterators for the inner vector
            init);                        // current sum
                                          // use the default binaryOp here
    }
);
Shloim
  • 5,281
  • 21
  • 36
6

In this case, I do not suggest using std::accumulate as it would greatly impair readability. Moreover, this function use loops internally, so you would not save anything. Just compare the following loop-based solution with the other answers that use std::accumulate:

int result = 0 ;
for (auto const & subvector : your_vector)
    for (int element : subvector)
        result += element;

Does using a combination of iterators, STL functions, and lambda functions makes your code easier to understand and faster? For me, the answer is clear. Loops are not evil, especially for such simple application.

  • BTW, the question says non-loop ways. Your `for` statements are loops. – Thomas Matthews Nov 03 '19 at 17:54
  • @ThomasMatthews I wrote this answer to counter the obsession of using the STL library at all cost. I agree that it doesn't answer the question as it is, but I find it to be a better alternative than letting OP go through the path of code clutter. Furthermore, using `std::accumulate` is also not answering the question as it uses loops inside it. If we follow this logic, then defining a function `sum` that does the job with loops and then calling this function would also considered a valid answer. – Gilles-Philippe Paillé Nov 03 '19 at 18:11
  • @ThomasMatthews After reading your answer, I see that we both agree on the loop thing, just not on the way of answering ;) – Gilles-Philippe Paillé Nov 03 '19 at 18:25
2

According to https://en.cppreference.com/w/cpp/algorithm/accumulate , looks like BinaryOp has the current sum on the left hand, and the next range element on the right. So you should run std::accumulate on the right hand side argument, and then just sum it with left hand side argument and return the result. If you use C++14 or later,

auto binary_op = [&](auto cur_sum, const auto& el){
    auto rhs_sum = std::accumulate(el.begin(), el.end(), 0);
    return cur_sum + rhs_sum;
};

I didn't try to compile the code though :). If i messed up the order of arguments, just replace them.

Edit: wrong terminology - you don't overload BinaryOp, you just pass it.

smitsyn
  • 578
  • 3
  • 6
2

Signature of std::accumulate is:

T accumulate( InputIt first, InputIt last, T init,
              BinaryOperation op );

Note that the return value is deduced from the init parameter (it is not necessarily the value_type of InputIt).

The binary operation is:

Ret binary_op(const Type1 &a, const Type2 &b);

where... (from cppreference)...

The type Type1 must be such that an object of type T can be implicitly converted to Type1. The type Type2 must be such that an object of type InputIt can be dereferenced and then implicitly converted to Type2. The type Ret must be such that an object of type T can be assigned a value of type Ret.

However, when T is the value_type of InputIt, the above is simpler and you have:

using value_type = std::iterator_traits<InputIt>::value_type;
T binary_op(T,value_type&).

Your final result is supposed to be an int, hence T is int. You need two calls two std::accumulate, one for the outer vector (where value_type == std::vector<int>) and one for the inner vectors (where value_type == int):

#include <iostream>
#include <numeric>
#include <iterator>
#include <vector>

template <typename IT, typename T>
T accumulate2d(IT outer_begin, IT outer_end,const T& init){
    using value_type = typename std::iterator_traits<IT>::value_type;
    return std::accumulate( outer_begin,outer_end,init,
        [](T accu,const value_type& inner){
            return std::accumulate( inner.begin(),inner.end(),accu);
        });
}

int main() {
    std::vector<std::vector<int>> x{ {1,2} , {1,2,3} };
    std::cout << accumulate2d(x.begin(),x.end(),0);
}
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

Solutions based on nesting std::accumulate may be difficult to understand.

By using a 1D array of intermediate sums, the solution can be more straightforward (but possibly less efficient).

int main()
   {

   // create a unary operator for 'std::transform'
   auto accumulate = []( vector<int> const & v ) -> int
      {
      return std::accumulate(v.begin(),v.end(),int{});
      };

   vector<vector<int>> data = {{1,2,3},{4,5},{6,7,8,9}}; // 2D array

   vector<int> temp; // 1D array of intermediate sums
   transform( data.begin(), data.end(), back_inserter(temp), accumulate );
   int result = accumulate(temp);

   cerr<<"result="<<result<<"\n";

   }

The call to transform accumulates each of the inner arrays to initialize the 1D temp array.

Brent Bradburn
  • 51,587
  • 17
  • 154
  • 173
  • In principle, using implicit loops rather than raw `for` gives you a standard way of specifying parallelism ([if your compiler is up to spec](https://stackoverflow.com/q/51031060/86967)). See: [Extensions for parallelism](https://en.cppreference.com/w/cpp/experimental/parallelism) – Brent Bradburn Nov 03 '19 at 23:08
1

To avoid loops, you'll have to specifically add each element:

std::vector<int> database = {1, 2, 3, 4};
int sum = 0;
int index = 0;
// Start the accumulation
sum = database[index++];
sum = database[index++];
sum = database[index++];
sum = database[index++];

There is no guarantee that std::accumulate will be non-loop (no loops). If you need to avoid loops, then don't use it.

IMHO, there is nothing wrong with using loops: for, while or do-while. Processors that have specialized instructions for summing arrays use loops. Loops are a convenient method for conserving code space. However, there may be times when loops want to be unrolled (for performance reasons). You can have a loop with expanded or unrolled content in it.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
1

With range-v3 (and soon with C++20), you might do

const std::vector<std::vector<int>> v{{1, 2}, {3, 4, 5, 6}};
auto flat = v | ranges::view::join;
std::cout << std::accumulate(begin(flat), end(flat), 0);

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302