获取 c++ 中浮点数向量的向量的逐元素平均值

Get element-wise mean of vector of vectors of floats in c++

本文关键字:向量 元素 平均值 c++ 浮点数 获取      更新时间:2023-10-16

假设我有以下大小相同的向量:

std::vector<float> a({1, 1, 1});
std::vector<float> b({2, 2, 2});
std::vector<float> c({4, 4, 5});

我想得到逐元素的平均向量:

std::vector<float> mean({2.333, 2.333, 2.666});

实现这一目标的最优雅方法是什么?我可以编写 for 循环来做到这一点,但想知道是否有更好的方法可以做到这一点。

另请注意,我希望解决方案扩展到任意数量的向量(为了举例说明,我使用了三个向量(

对于元素级操作,您应该使用 std::valarray .底漆:

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)}

这适用于 GCC 7.2.1 的 C++11 模式。现在你还没有指定你如何在载体中喂食,所以你想要什么还不清楚。如果您事先知道要处理多少个向量,这应该有效:

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());
}

如果你的内部向量总是具有相同的大小,std::vector似乎不是一个好的选择(它会产生不必要的许多小堆分配并降低数据局部性(。最好使用std::array,或者定义自己的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();
}

或者你可以偷懒,使用像 eigen3 这样的专用库。

实现OP目标的"最优雅方式">是什么,恐怕是一个见仁见智的问题,但我们肯定可以用标准库中的算法替换大多数显式for循环。

向量

向量可能不是每个用例的最佳数据结构,此外,按列遍历此对象可能不是很适合缓存。但是,即使它是强制性的,我们仍然可以通过逐行遍历容器,在临时向量中累积每列的总和并最终计算平均值来执行所有需要的计算。

此代码段显示了一个可能的(稍微更通用的(实现:

#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';
}