nothrow
nothrow

Reputation: 16168

sum of non-integer elements in std::vector

I was reading following question: How to sum up elements of a C++ vector?, and I wanted to use second method (sum_of_elems =std::accumulate(vector.begin(),vector.end(),0);//#include <numeric>).

However, I don't have std::vector<int>, but std::vector<struct packet>. The packet is defined as following:

struct packet {
    /// ...
    int length() const;
    ///
}

and I want sum of packet lengths.

This is what I tried:

std::vector<packet> packets;
...
std::accumulate(packets.begin(), packets.end(), 0, [](const packet& a, const packet& b){return a.length() + b.length();});

but it doesn't work. In C#, I'd write something like

packet[] p;
p.Select(x => p.length()).Sum();

Is it possible to do something like that in C++? I can write method for iterating through the vector and doing it on my own, but I prefer the functional approach where possible.

Upvotes: 2

Views: 308

Answers (5)

Matthieu M.
Matthieu M.

Reputation: 299940

I would note that the C# implementation is slightly different, in essence.

In C++ you are trying to add int and packet whilst in C# you first provide a transformation step from packet to int and then add ints.

The equivalent C++, without adaptation:

std::vector<size_t> lengths; // a length cannot be negative!

std::transform(packets.begin(),
               packets.end(),
               backward_inserter(lengths),
               [](packet const& p) { return p.length(); });

auto const sum = std::accumulate(lengths.begin(), lengths.end(), 0ul);

Of course, it is wasteful to store the intermediate lengths, however it does work out of the box.

But because we are cool, let us have look at Boost.Range, and more precisely:

Which have a bit of coolness like Linq:

#include <boost/range/numeric.hpp> // accumulate
#include <boost/range/adaptor/transformed.hpp>

size_t total_size(std::vector<packet> const& packets) {
    return boost::accumulate(
        packets | boost::transformed([](packet const& p) { return p.length(); }),
        0ul);
}

Upvotes: 4

Saksham
Saksham

Reputation: 9380

Apart from lamba, you can change it to

std::accumulate(packets.begin(), packets.end(), 0, packet());

Where you can define functor as:

int operator() (int result, const packet& obj)
{
    return result+ obj.length();
}

Upvotes: 1

Mike Seymour
Mike Seymour

Reputation: 254501

The first parameter of the accumulate operation is the running total. In your case, this is an integer, not a packet, so your lambda should be

[](int a, const packet& b) {return a + b.length();}

Upvotes: 2

Timo T&#252;rschmann
Timo T&#252;rschmann

Reputation: 4696

The problem is your accumulate function. Its first parameter has to be of the type you're trying to accumulate (int in this case) and add a value on top of that.

Your lambda function should look like this: [](int currTotal, const packet& b){return currTotal + b.length();}

Upvotes: 1

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275575

You are accumulating via a binary operation. Your accumulated value starts with 0 (an int), so the left hand side of your binary operation has to be convertible-from 0 -- otherwise, how does it start adding?

Try this:

std::accumulate(
  packets.begin(),
  packets.end(),
  0,
  [](int a, const packet& b){
    return a + b.length();
  }
);

you can also do this via a simple loop:

int acc = 0;
for( const packet& p : packets ) {
  acc += packets.length();
}

Upvotes: 3

Related Questions