Reputation: 20909
Assuming I have following vector of vectors with the same size:
std::vector<float> a({1, 1, 1});
std::vector<float> b({2, 2, 2});
std::vector<float> c({4, 4, 5});
I would like to get the element-wise mean vector:
std::vector<float> mean({2.333, 2.333, 2.666});
What's the most elegant way to achieve this? I can write for-loops to do so but was wondering is there better way to do so.
Also note that I would like the solution to scale to any number of vectors (I'm using three vectors for the sake of giving examples)
Upvotes: 2
Views: 1137
Reputation: 12769
What is the "most elegant way" to achive OP's goal is a matter of opinion, I'm afraid, but surely we can replace most of the explicit for
loops with algorithms from the Standard Library.
A vector of vectors may not be the best data structure for every use case and moreover, traversing this object column-wise may not be very cache friendly. However, even if it's mandatory, we can still perform all the needed calculation by traversing the container row-wise, accumulating the sums of each column in a temporary vector and finally computing the averages.
This snippet shows a possible (slightly more general) implementation:
#include <iostream>
#include <vector>
#include <array>
#include <iterator>
#include <stdexcept>
#include <algorithm>
#include <functional>
template<class ReturnType = double, class Container>
auto elementwise_mean(Container const& mat)
{
using MeansType = std::vector<ReturnType>;
using DistType = typename MeansType::iterator::difference_type;
auto it_row = std::begin(mat);
auto n_rows = std::distance(it_row, std::end(mat));
if ( n_rows == 0 )
throw std::runtime_error("The container is empty");
MeansType means(std::begin(*it_row), std::end(*it_row));
const DistType row_size = means.size();
if ( row_size == 0 )
throw std::runtime_error("The first row is empty");
std::for_each(
++it_row, std::end(mat),
[&means, row_size](auto const& row) {
if ( row_size != std::distance(std::begin(row), std::end(row)) )
throw std::runtime_error("A row has a wrong length");
std::transform(
means.begin(), means.end(), std::begin(row),
means.begin(), std::plus()
);
}
);
std::for_each(means.begin(), means.end(), [n_rows](auto & a){ a /= n_rows; });
return means;
}
template<class Container> void print_out(Container const& c);
int main()
{
std::vector<std::vector<double>> test {
{1.0, 1.0, 1.0},
{2.0, 2.0, 2.0},
{4.0, 4.0, 5.0}
};
auto means = elementwise_mean(test);
print_out(means); // --> 2.33333 2.33333 2.66667
std::array<int, 4> test2[2] = {
{{1, 3, -5, 6}},
{{2, 5, 6, -8}},
};
auto means2 = elementwise_mean<float>(test2);
print_out(means2); // --> 1.5 4 0.5 -1
auto means3 = elementwise_mean<int>(test2);
print_out(means3); // --> 1 4 0 -1
}
template<class Container>
void print_out(Container const& c)
{
for ( const auto x : c )
std::cout << ' ' << x;
std::cout << '\n';
}
Upvotes: 0
Reputation: 2749
If your inner vectors have always the same size, std::vector
seems not like a good choice (it creates unnecessary many small heap allocations and decreases data locality). Better use std::array
, or define your own class Vec
:
#include <vector>
#include <array>
#include <numeric>
#include <algorithm>
template <typename T, std::size_t N>
struct Vec : std::array<T, N> {
Vec() = default;
explicit Vec(std::array<T, N> const& a): std::array<T, N>(a) {}
static Vec zero() { return Vec(std::array<T, N>{0}); }
Vec operator + (Vec const& rhs) const {
Vec result;
std::transform(std::begin(*this), std::end(*this), std::begin(rhs), std::begin(result), std::plus<T>());
return result;
}
template <typename T2>
Vec operator / (T2 const& rhs) const {
Vec result;
std::transform(std::begin(*this), std::end(*this), std::begin(result), [&](T const& lhs) { return lhs/rhs; });
return result;
}
};
Vec<float, 3> elementwise_mean(std::vector<Vec<float, 3>> vecvec) {
return std::accumulate(std::begin(vecvec), std::end(vecvec), Vec<float, 3>::zero()) / vecvec.size();
}
Or you can be lazy and use a dedicated library like eigen3.
Upvotes: 1
Reputation: 261
For element-wise operations, you should be using std::valarray
. Primer:
std::valarray<float> a { 1, 1, 1 };
std::valarray<float> b { 2, 2, 2 };
std::valarray<float> c { 4, 4, 5 };
std::valarray<float> mean = (a + b + c) / 3.f;
std::vector<float> v{std::begin(mean), std::end(mean)}
This works in C++11 mode with GCC 7.2.1. Now you haven't specified how you're feeding in the vectors, so what you want isn't clear. If you know in advance how many vectors you'll be dealing with, this should work:
std::valarray<float> foo(std::vector<std::valarray<float>> args) {
assert(args.size() > 0);
// sum MUST be initialized with a size
// and in this case, all sizes must be the same
// otherwise += is undefined behavior
std::valarray<float> sum(args[0].size());
for (auto c : args) {
sum += c;
}
return (sum / (float)args.size());
}
Upvotes: 4