Home > Software engineering >  C : average of vector of structs
C : average of vector of structs

Time:03-11

My struct is a 2D coordinate:

template<typename T>
struct coordinate {
    std::pair<T, T> coords;
    coordinate() : coords({0, 0}) {}
    const T x() const { return coords.first; }
    const T y() const { return coords.second; }

    // functions to set and manipulate x and y

};

I have a std::vector<coordinate<double>> vec and would like to get averages of x and of y coordinates across the vector.

My way is (please don't judge)

double x_ = 0.;
double y_ = 0.;
for (auto v : vec) {
    x_  = v.x();
    y_  = v.y();
}
x_ /= vec.size();
y_ /= vec.size();

I assume there is a more suitable approach? I try to go for std::accumulate, but don't know how to access the struct members in std::accumulate.

I still struggle with C so some explanation to your approach would be great.

CodePudding user response:

Like I said, if you want to do arithmetic operations on your type, you likely want to overload the operators. Thus you can do

#include <numeric>
#include <vector>

template<typename T>
struct coordinate {
    std::pair<T, T> coords;
    coordinate() : coords({0, 0}) {}
    coordinate(T x,T y) : coords(x,y) {}
    T x() const { return coords.first; }
    T y() const { return coords.second; }  

    coordinate& operator =(coordinate const& other){
        coords.first  = other.coords.first;
        coords.second  = other.coords.second;
        return *this;
    }

    template<typename D>
    coordinate& operator/=(D divider){
        coords.first /= divider;
        coords.second /= divider;
        return *this;
    }
};

template<typename T>
coordinate<T> operator (coordinate<T> lhs, coordinate<T> const& rhs){
    return lhs  = rhs;
}

template<typename T, typename D>
coordinate<T> operator/(coordinate<T> lhs, D rhs){
    return lhs /= rhs;
}

template<typename T>
coordinate<T> average(std::vector<coordinate<T>> const& vec){
    if (vec.empty()){
        return coordinate<T>{};
    }
    return std::accumulate(cbegin(vec), cend(vec), coordinate<T>{}) / vec.size();
}

int main()
{
    std::vector<coordinate<double>> vec{{1,1},{2,0}};

    auto avg = average(vec);
}

CodePudding user response:

std::accumulate takes a BinaryOperation that adds one element to the accumulator:

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

template<typename T>
struct coordinate {
    std::pair<T, T> coords;
    coordinate() : coords({0, 0}) {}
    coordinate(T x,T y) : coords(x,y) {}
    const T x() const { return coords.first; }
    const T y() const { return coords.second; }  
};



int main()
{
    std::vector<coordinate<double>> vec{{1,1},{2,0}};
    auto avg = std::accumulate(vec.begin(),vec.end(),coordinate{0,0},[](const auto& a,const auto& b){
        auto ret = a;
        ret.coords.first  = b.coords.first;
        ret.coords.second  = b.coords.second;
        return ret;
    });
    std::cout << avg.coords.first << " " << avg.coords.second;
}

However, this isnt much simpler than your loop. Things are different when you define a operator because thats what accumulate uses by default. Then you could simply write std::accumulate(vec.begin(),vec.end(),coords(0,0));.

Note that I had to add a constructor for the example and you still have to divide by vec.size, perhaps also via a operator/=.

if my vector would only have doubles, then yes. But I don't get, how to use accumulate, if my vector has structs instead doubles.

If T is some struct then that struct should implement an operator or operator =. If the struct does not have either it does not make sense to average it.

  • Related