c++ 如何使用std::accumulate和lambda来计算平均值?

aiazj4mn  于 2023-05-02  发布在  其他
关注(0)|答案(6)|浏览(370)

我有一个标准库容器,里面有大量的数字,如果我把它们加在一起,它们可能会导致溢出。让我们假设它是这个容器:

std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

我想使用std::accumulate计算这个容器的平均值,但我不能将所有数字相加。我用v[0]/v.size() + v[1]/v.size() + ...来计算。所以我设置:

auto lambda = ...;
std::cout << std::accumulate(v.begin(), v.end(), 0, lambda) << std::endl;

以下是我到目前为止所尝试的,其中->表示输出:

lambda = [&](int a, int b){return (a + b)/v.size();};  ->  1
lambda = [&](int a, int b){return a/v.size() + b/v.size();};  ->  1
lambda = [&](int a, int b){return a/v.size() + b;};  ->  10

我怎样才能产生正确的平均值,使输出为5

r8uurelv

r8uurelv1#

你不应该使用integer来存储结果:
传递给函数accumulate的返回类型:
T accumulate( InputIt first, InputIt last, T init, BinaryOperation op );依赖于第三个参数类型:(T init)所以你必须在这里写:0.0得到结果为double

#include <vector>
#include <algorithm>
#include <iostream>
#include <numeric>
using namespace std;
std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

int main()
{
    auto lambda = [&](double a, double b){return a + b / v.size(); };
    std::cout << std::accumulate(v.begin(), v.end(), 0.0, lambda) << std::endl;
}
368yc8dk

368yc8dk2#

这可能不会很好地舍入,但即使容器上没有size()方法,它也可以工作:

auto lambda = [count = 0](double a, int b) mutable { return a + (b-a)/++count; };

这利用了C14的一个新特性,*initialized capture *,在lambda中存储状态。(你可以通过捕获一个额外的局部变量来做同样的事情,但是它的作用域是局部作用域,而不是lambda的生命周期。对于较旧的C版本,您自然可以将count放在struct的成员变量中,并将lambda主体作为其operator()()实现。
为了防止舍入误差的累积(或至少显著地减少它),可以做如下操作:

auto lambda = [count = 0, error = 0.0](double a, int b) mutable {
   const double desired_change = (b-a-error)/++count;
   const double newa = a + (desired_change + error);
   const double actual_change = newa - a;
   error += desired_change - actual_change;
   return newa;
};
1bqhqjot

1bqhqjot3#

你的运行“average”是lambda的第一个参数,所以下面是正确的。

lambda = [&](int a, int b){return a + b/v.size();};
mf98qq94

mf98qq944#

您正在使用的三个lambda函数不符合标准。

lambda = [&](int a, int b){return (a + b)/v.size();};  ->  1
lambda = [&](int a, int b){return a/v.size() + b/v.size();};  ->  1
lambda = [&](int a, int b){return a/v.size() + b;};  ->  10

这里使用的参数a在给定时间点将平均值带到向量的特定索引。例如,当“B”的值为1时,“a”的值为0。0,当'B'在那一刻变成2时,它应该是'0。那么很清楚,在任何情况下,“a”都不需要被v除。size(),每次调用lambda函数。
在上述情况下使用正确的lambda函数

lambda = [&](double x,double y){return x+y/v.size();}

这里我们通过引用捕获,因为我们需要v的值。size(),如果预先知道,则可以预先传递向量的大小值
工作程序为

#include<iostream>
    #include<numeric>
    #include<vector>
    using namespace std;

    int main(){
        vector<int> v(10);
        iota(v.begin(),v.end(),1);
        double x=accumulate(v.begin(),v.end(),0.0,[&](double x,double y) {return x+y/v.size();});
        cout << x << endl;
    }

附注:'iota'用于以递增的方式初始化一个范围,这里它初始化从1到10的向量

nzkunb0c

nzkunb0c5#

我还没有见过这个解决方案,它不需要传递向量的大小,因为已经用v.begin()v.end()控制了范围:

double mean = accumulate(v.begin(), v.end(), 0., [](double x, double y) { return x+y; }) / v.size();

它可以通过用std::distance(start,end)替换v.size()来进一步改进。

zvms9eto

zvms9eto6#

显然你不需要去这些长度,但你可以创建一个简单的“统计计算器”

struct StatsCalculator
{
   size_t count;
   double sum;
   double sumSq;
   
   double mean() const { return count ? sum/count : NaN(); }
   double variance() const { return count ? (sumSq-sum*sum/count)/count : NaN();  }
   std::tuple<double,double> meanAndVariance() { return { mean(), variance() }; 
   void addValue( double val ) { ++count; sum += val; sumSq += val*val; }
};

你的lambda怎么样在迭代之前创建StatsCalculator示例。然后

auto myLambda = [](StatsCalculator* calculator, int value)
   { calculator->addValue(static_cast<double>(value)); return calculator; }

然后迭代:

StatsCalculator calc;
double mean = std::accumulate(y.begin(), y.end(), &calc, myLambda)->mean();

当然你也可以要求均值和方差的元组。

相关问题