Dean Svendsen
Dean Svendsen

Reputation:

Summing struct members inside a vector

Consider the following:

typedef struct {
    int a;
    int b;
    int c;
    int d;
} ABCD;

typedef std::vector<ABCD> VecABCD;

Say I wanted to add up every 'a' member in a vector of type VecABCD. Easy! I just loop through the vector, and sum as I go.

int CalcSumOfA(const VecABCD &vec)
{
    int sumOfA = 0;
    VecABCD::const_iterator it;
    for(it=vec.begin();it!=vec.end();it++)
        sumOfA += it->a;
    return sumOfA;
}

Say I wanted to do the same thing with 'b'? Easy! I'd write....essentially the same function, but with only trivial changes. Same with 'c' and 'd'.

So, is there a more concise, less repetitive way of doing this? I'd like to do something like:

int sumOfA = SumOfMembers(myVec, a);

but I can't conceive of how I'd put such a function together. Ideally, it'd be a template, and I could use it with a vector of any kind of structure, not specifically bound to VecABCD. Anyone have any ideas?

Upvotes: 21

Views: 12149

Answers (6)

Synck
Synck

Reputation: 3548

This might be possible with C++ ranges in the future. As they support projections.
Projections allow to execute an algorithm on a member variable of the iterated class.

Currently (2022) the algorithm library doesn't yet have a range version of accumulate.
There is a paper for adding in to C++ 23: P2214: A Plan for C++23 Ranges

But you can already use it/try it with the range v3 library.

#include <iostream>
#include <range/v3/numeric/accumulate.hpp>

struct Point
{
   int x;
   int y;
};

int main()
{
   const auto points = std::vector{ Point{1, 1}, Point{2, 4}, Point{3, 0} };

   const int xSum = ranges::accumulate(points, 0, std::plus(), &Point::x);
   const int ySum = ranges::accumulate(points, 0, std::plus(), &Point::y);

   std::cout << "X sum: " << xSum << " - Y sum: " << ySum << '\n';
   // output: X sum: 6 - Y sum: 5
}

Upvotes: 0

Todd Gardner
Todd Gardner

Reputation: 13521

STL summations can be done with std::accumulate

#include <functional>

accumulate(v.begin(), v.end(), 0, bind(plus<int>(), _1, bind(&ABCD::a, _2)))

If you wanted this to be more generic, you could take a tr1::function to the member you want to bind:

int sum_over_vec(const vector<ABCD>& v, const tr1::function<int (const ABCD&)>& member)
{
  return accumulate(v.begin(), v.end(),
                    0,
                    bind(plus<int>(),
                         _1,
                         bind(member, _2)));
};

// ...

int sum_a = sum_over_vec(vec, bind(&ABCD::a, _1));

Another way to do it, rather than putting your logic in the functor, would be to put the logic in the iterator, using a boost::transform iterator:

tr1::function<int (const ABCD&)> member(bind(&ABCD::a, _1));
accumulate(make_transform_iterator(v.begin(), member),
           make_transform_iterator(v.end(),   member),
           0);

EDITED TO ADD: C++11 lambda syntax

This becomes somewhat clearer with the C++11 lambdas (though unfortunately not shorter):

accumulate(v.begin(), v.end(), 0,
    [](int sum, const ABCD& curr) { return sum + curr.a });

and

int sum_over_vec(const vector<ABCD>& v, const std::function<int (const ABCD&)>& member)
{
  return accumulate(v.begin(), v.end(), 0,
      [&](int sum, const ABCD& curr) { return sum + member(curr}); });
};

Usage:

// Use a conversion from member function ptr to std::function.
int sum_a = sum_over_vec(vec, &ABCD::a);
// Or using a custom lambda sum the squares.
int sum_a_squared = sum_over_vec(vec,
    [](const ABCD& curr) { return curr.a * curr.a; });

Upvotes: 27

Tom
Tom

Reputation: 45114

You could use for_each. Its an option.

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;
typedef struct{
    int a;

}ABCD;

typedef vector<ABCD> vecABCD;

struct sum  : public unary_function<ABCD, void>
{
  sum(){count.a=count.b=count.c=count.d=0;}
  void operator() (ABCD x) {
       count.a+=x.a;
       count.b+=x.b;
       count.c+=x.c;
       count.d+=x.d;
   }
  ABCD count;
};

int main()
{

  ABCD s1={1,2,3,4};
  ABCD s2={5,6,7,8};

  vecABCD v;
  v.push_back(s1);
  v.push_back(s2);
  sum s = for_each(v.begin(), v.end(), sum());
  cout<<s.count.a<<endl;

}

output:

4

Upvotes: 1

Adam Rosenfield
Adam Rosenfield

Reputation: 400274

Another option would be to use pointer-to-members:

int CalcSumOf(const VecABCD & vec, int ABCD::*member)
{
    int sum = 0;
    for(VecABCD::const_iterator it = vec.begin(), end = vec.end(); it != end; ++it)
        sum += (*it).*member;
    return sum;
}
...
int sumA = CalcSumOf(myVec, &ABCD::a);  // find sum of .a members
int sumB = CalcSumOf(myVec, &ABCD::b);  // find sum of .b members
// etc.

Upvotes: 12

user44556
user44556

Reputation: 6083

Let's add one more option, unfortunately an ugly one. It's possible to take the relative address from start of the struct ABCD to it's member by offsetof -function. Deliver return value to function and it can do counting using relative location from the start of each struct. If your types may differ from int, you may want to deliver also the sizeof information.

Upvotes: 0

Billy ONeal
Billy ONeal

Reputation: 106549

Use std::accumulate :)

Upvotes: 0

Related Questions