【问题标题】:How can I use std::accumulate and a lambda to calculate a mean?如何使用 std::accumulate 和 lambda 计算平均值?
【发布时间】:2015-04-16 20:08:41
【问题描述】:

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

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;

这是我迄今为止尝试过的,其中-&gt; 表示输出:

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

【问题讨论】:

  • 5 不是正确答案。
  • @BenVoigt 如果您使用整数除法。
  • 整数除法不用于计算平均值。结合std::accumulate,情况更糟——它会毁掉你的部分金额。如果您希望根据整数除法规则对最终结果进行四舍五入,则应在问题中明确说明(然后您找不到均值)。否则,您对整数除法的使用对每个读者来说都是一个错误。

标签: c++ lambda accumulate


【解决方案1】:

你不应该使用整数来存储结果:

传递给函数accumulate:
T accumulate( InputIt first, InputIt last, T init, BinaryOperation op ); 的返回类型取决于第三个参数类型: (T init) 所以你必须放在那里: 0.0 以获得结果为双重

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

【讨论】:

  • @rpattabi 您能否详细说明如何在没有返回类型的情况下重现警告?我做不到。
  • 你的 lambda 的第二种类型不是向量元素的类型吗?含义auto lambda = [&amp;](double a, int b){//...
  • 不能改成int,否则除法不正确。如果您想将它作为 int 传递,那么您应该稍后将其转换为 double:auto lambda = [&amp;](double a, int b) {return a + (double)b / v.size(); };
  • 好点。请考虑这样做(可能在您的答案中使用static_cast&lt;double&gt;(b)。我认为这比当前答案更好,因为它清楚地显示了 lambda 的哪个参数来自累积的结转,哪个是向量的元素。此外,这明确表明正在进行强制转换,并且不会在 lambda 的参数中隐式强制转换。
【解决方案2】:

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

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

这利用了新的 C++14 功能,初始化捕获,将状态存储在 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;
};

【讨论】:

  • 大数据集用这个近似公式比较好,因为原来公式中double的质量可能不够。
  • @AdamF:也可以跟踪误差项,以防止舍入误差累积。
  • 完美。在之前的评论中,我还想承认你的第一个公式:)我用过几次,它很强大,我们甚至不需要将整个数组保存在内存中。
  • @BenVoigt 不错的答案!您应该提到 lambda 捕获表达式仅在 C++14 中有效
  • @vsoftco:我一直打算这样做,然后对舍入误差感兴趣。谢谢你提醒我。
【解决方案3】:

您的运行“平均值”是 lambda 的第一个参数,因此以下是正确的。

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

【讨论】:

  • 整数舍入不会有问题吗?
【解决方案4】:

您使用的三个 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.1'。 那么很明显,在任何情况下,每次调用 lambda 函数时都不需要将 'a' 除以 v.size()。

针对上述情况使用正确的 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;
    }

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

【讨论】:

    【解决方案5】:

    我还没有看到这种不需要传递矢量大小的解决方案 因为已经使用v.begin()v.end() 控制范围:

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

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

    【讨论】:

      猜你喜欢
      • 2012-09-23
      • 2020-11-03
      • 1970-01-01
      • 1970-01-01
      • 2021-10-22
      • 2019-02-07
      • 1970-01-01
      相关资源
      最近更新 更多