【问题标题】:How to sum up elements of a C++ vector?如何总结 C++ 向量的元素?
【发布时间】:2011-03-14 09:40:49
【问题描述】:

找到std::vector中所有元素之和的方法是什么?

假设我有一个向量std::vector<int> vector,其中包含一些元素。现在我想找到所有元素的总和。相同的方法有哪些不同?

【问题讨论】:

  • “多少”?真的吗?这似乎是一个过于模糊的问题。 :p 询问一个好的方法可能更有用。
  • “功能相似”是什么意思?您是否正在寻找 Boost 中 std::accumulate 的替代品? (如果是,为什么?)您是否正在寻找类似于std::accumulate 的功能? (如果有,是什么?)
  • 如果你想要类似于std::accumulate的东西,大概你也希望它在某些方面有所不同(否则你可以只使用std::accumulate);您在寻找与 std::accumulate 的哪些不同之处?

标签: c++ stl vector


【解决方案1】:

其实方法还是蛮多的。

int sum_of_elems = 0;

C++03

  1. 经典for循环:

     for(std::vector<int>::iterator it = vector.begin(); it != vector.end(); ++it)
         sum_of_elems += *it;
    
  2. 使用标准算法:

     #include <numeric>
    
     sum_of_elems = std::accumulate(vector.begin(), vector.end(), 0);
    

    重要提示:最后一个参数的类型不仅用于初始值,还用于结果的类型。如果你把一个 int 放在那里,即使向量有浮点数,它也会累积 int。如果要对浮点数求和,请将 0 更改为 0.00.0f (thanks to nneonneo)。另请参阅下面的 C++11 解决方案。

C++11 及更高版本

  1. b.即使将来发生变化,也会自动跟踪向量类型:

     #include <numeric>
    
     sum_of_elems = std::accumulate(vector.begin(), vector.end(),
                                    decltype(vector)::value_type(0));
    
  2. 使用std::for_each

     std::for_each(vector.begin(), vector.end(), [&] (int n) {
         sum_of_elems += n;
     });
    
  3. 使用基于范围的 for 循环 (thanks to Roger Pate):

     for (auto& n : vector)
         sum_of_elems += n;
    

【讨论】:

  • 当然,在 C++03 中,您可以将 std::for_each 与仿函数一起使用,它只需要比 C++0x lambda 更多的代码行来定义。
  • 为什么你的 lambda 示例使用for_eachaccumulate 会更简洁(即使它不需要 lambda)
  • @jalf:你的观点是正确的,我应该在for_each 中使用accumulate,但是这个例子不是很有用(用于学习目的),因为它表明我们也可以有嵌套的lambda: -)
  • 小心accumulate。最后一个参数的类型不仅用于初始值,还用于结果的类型。如果你在那里放一个int,即使向量有float,它也会累积ints。结果可能有细微的错误,编译器会在不告诉你的情况下将结果转换回浮点数。
  • 如果你有accumulate,为什么还要使用for_each
【解决方案2】:

最简单的方法是使用std:accumulatevector&lt;int&gt; A

#include <numeric>
cout << accumulate(A.begin(), A.end(), 0);

【讨论】:

    【解决方案3】:

    Prasoon 已经提供了许多不同的(和好的)方法来做到这一点,这里不需要重复。不过,我想建议另一种提高速度的方法。

    如果您要经常这样做,您可能需要考虑对您的向量进行“子分类”,以便单独维护元素的总和(而不是实际上子分类由于缺少虚拟析构函数而不确定的向量 - 我说的是一个包含总和和向量的类,has-a 而不是is-a,并提供类似向量的方法)。

    对于空向量,总和设置为零。在每次插入向量时,将要插入的元素添加到总和中。在每次删除时,减去它。基本上,任何可以改变底层向量的东西都会被拦截,以确保总和保持一致。

    这样,您就有了一个非常有效的 O(1) 方法来“计算”任何时间点的总和(只需返回当前计算的总和)。当您调整总数时,插入和删除的时间会稍长一些,您应该考虑到这一性能损失。

    需要求和的次数多于更改向量的向量可能会从该方案中受益,因为计算总和的成本会在所有访问中分摊。显然,如果你只需要每小时求和,而向量每秒变化三千次,那是不合适的。

    这样就足够了:

    class UberVector:
        private Vector<int> vec
        private int sum
    
        public UberVector():
            vec = new Vector<int>()
            sum = 0
    
        public getSum():
            return sum
    
        public add (int val):
            rc = vec.add (val)
            if rc == OK:
                sum = sum + val
            return rc
    
        public delindex (int idx):
            val = 0
            if idx >= 0 and idx < vec.size:
                val = vec[idx]
            rc =  vec.delindex (idx)
            if rc == OK:
                sum = sum - val
            return rc
    

    显然,这是伪代码,您可能希望拥有更多功能,但它显示了基本概念。

    【讨论】:

    • 很有趣,但要小心,因为std::vector 不适合子类化。
    • 对不起,我应该更清楚 - 您可以使用与 vector 相同的方法创建自己的类,在其中维护一个 has-a 向量,而不是成为一个适当的子类 (is-a)。
    • 这是有问题的,除非您禁用访问数据的访问器,包括但不限于operator[](int)、非常量迭代器...
    • @paxdiablo 我相信大卫的意思是如果存储在向量中的数据是通过使用 operator[] 或通过非常量迭代器间接操作的。操纵位置的值现在将不同,这将使总和不正确。如果客户端代码能够保持对“子类”向量中任何元素的可变引用,则无法确保总和是正确的。
    • 这种方法会导致基本向量操作的性能损失。
    【解决方案4】:

    既然可以向后进行,为什么还要向前进行求和?给定:

    std::vector<int> v;     // vector to be summed
    int sum_of_elements(0); // result of the summation
    

    我们可以使用下标,倒数:

    for (int i(v.size()); i > 0; --i)
        sum_of_elements += v[i-1];
    

    我们可以使用范围检查的“下标”,倒数(以防万一):

    for (int i(v.size()); i > 0; --i)
        sum_of_elements += v.at(i-1);
    

    我们可以在 for 循环中使用反向迭代器:

    for(std::vector<int>::const_reverse_iterator i(v.rbegin()); i != v.rend(); ++i)
        sum_of_elements += *i;
    

    我们可以在 for 循环中使用前向迭代器、后向迭代器(哦哦,很棘手!):

    for(std::vector<int>::const_iterator i(v.end()); i != v.begin(); --i)
        sum_of_elements += *(i - 1);
    

    我们可以将accumulate 与反向迭代器一起使用:

    sum_of_elems = std::accumulate(v.rbegin(), v.rend(), 0);
    

    我们可以将for_each 与使用反向迭代器的 lambda 表达式一起使用:

    std::for_each(v.rbegin(), v.rend(), [&](int n) { sum_of_elements += n; });
    

    因此,如您所见,向后求和向量的方法与向前求和向量的方法一样多,其中一些更令人兴奋,并且为非一错误提供了更大的机会.

    【讨论】:

    • 为什么不通过使用模运算符添加素数来循环遍历向量以进行环绕呢? :-)
    • @paxdiablo 你只需要与v.size()相对质数。
    • -1: vector::size() 返回一个无符号值,使得 (v.size() - 1) 等表达式生成警告,或者在最坏的情况下产生雷区。
    • 为什么会有这个答案?向后求和有什么好处,还是你只是在拖钓?
    • @Lynn:如果向量的末端在高速缓存中很热(来自前一个向前的循环),那么是的,在当前的 Intel x86 CPU 上向后循环可以明显更快。此外,将循环计数器计数为零可以在 asm 中为编译器节省一条指令,如果它不展开循环,这可能很重要。不过,在向前循环时,预取有时会稍微好一些,因此通常总是向后循环并不好。
    【解决方案5】:
    #include<boost/range/numeric.hpp>
    int sum = boost::accumulate(vector, 0);
    

    【讨论】:

    • 感谢您的回答。顺便说一句,std::accumulate 和 boost::accumulate 在时间复杂度上的区别是什么?
    • std 和 boost 的累积时间复杂度相同——线性。在这种情况下, boost::accumulate 比手动发送 begin 和 end 更容易输入。没有真正的区别。
    • boost::accumulate 只是std::accumulate 的包装。
    • 非增强方式并不难:#include &lt;numeric&gt;std::accumulate(v.begin(), v.end(), (int64_t)0);。请注意,初始累加器值的类型用作累加器类型,因此如果您想将 8 位元素相加成 64 位结果,那么您就是这样做的。
    【解决方案6】:

    也可以像这样使用std::valarray&lt;T&gt;

    #include<iostream>
    #include<vector>
    #include<valarray>
    
    int main()
    {
        std::vector<int> seq{ 1,2,3,4,5,6,7,8,9,10 };
        std::valarray<int> seq_add{ seq.data(), seq.size() };
        std::cout << "sum = " << seq_add.sum() << "\n";
    
        return 0;
    }
    

    有些人可能觉得这种方法效率不高,因为valarray 的大小需要与向量 的大小一样大,并且初始化valarray 也需要时间。

    在这种情况下,不要使用它,而是将其作为总结序列的另一种方式。

    【讨论】:

      【解决方案7】:

      仅限 C++0x:

      vector<int> v; // and fill with data
      int sum {}; // or = 0 ... :)
      for (int n : v) sum += n;
      

      这与其他地方提到的 BOOST_FOREACH 类似,并且与用于累积或 for_each 的有状态仿函数相比,在更复杂的情况下具有相同的清晰优势。

      【讨论】:

      • 如果您将for (int n : v) sum += n; 更改为for (auto n : v) sum += n;,它将适用于任何矢量模板。我知道 OP 指的是vector,但这种方式稍微更通用:-)
      【解决方案8】:

      我是一个 Perl 用户,我们的游戏是找到各种不同的方法来增加一个变量……这在这里并没有什么不同。 C++中有多少种方法求向量元素之和的答案大概是an infinity...

      我的 2 美分:

      使用 BOOST_FOREACH,摆脱丑陋的迭代器语法:

      sum = 0;
      BOOST_FOREACH(int & x, myvector){
        sum += x;
      }
      

      迭代索引(非常容易阅读)。

      int i, sum = 0;
      for (i=0; i<myvector.size(); i++){
        sum += myvector[i];
      }
      

      另一个是破坏性的,像堆栈一样访问向量:

      while (!myvector.empty()){
         sum+=myvector.back();
         myvector.pop_back();
      }
      

      【讨论】:

      • 为什么说迭代索引效率低下?你这么说的依据是什么?
      • @bobobobo:嗯,效率低下可能是过度的。您必须同时从向量和增量计数器计算有效数据位置,但这两个操作之一就足够了,但取消引用迭代器的成本可能会更糟。因此,我将删除这个词。
      • 优化编译器可以优化掉索引变量,如果需要,只需使用指针增量。 (它可以使循环退出条件成为与start + length 的指针比较)。实际的迭代器也应该完全优化掉。请记住,这不是 perl;它完全编译为 asm,而不是解释。
      【解决方案9】:
       #include<iostream>
          #include<vector>
          #include<numeric>
          using namespace std;
          int main() {
             vector<int> v = {2,7,6,10};
             cout<<"Sum of all the elements are:"<<endl;
             cout<<accumulate(v.begin(),v.end(),0);
          }
      

      【讨论】:

      • 我觉得这是我能想到的最简单的了
      【解决方案10】:

      我在答案中没有注意到的另一个选项是使用 c++17 中引入的std::reduce

      但是您可能会注意到许多编译器不支持它(GCC 10 以上可能很好)。但最终,支持会到来。

      使用std::reduce,优势在于使用执行策略。指定执行策略是可选的。当指定的执行策略为std::execution::par时,算法可能会使用硬件并行处理能力。使用大尺寸向量时,增益可能会更明显。

      例子:

      //SAMPLE
      std::vector<int> vec = {2,4,6,8,10,12,14,16,18};
          
      //WITHOUT EXECUTION POLICY
      int sum = std::reduce(vec.begin(),vec.end());
          
      //TAKING THE ADVANTAGE OF EXECUTION POLICIES
      int sum2 = std::reduce(std::execution::par,vec.begin(),vec.end());
          
      std::cout << "Without execution policy  " << sum << std::endl;
      std::cout << "With execution policy  " << sum2 << std::endl;
      

      std::reduce 需要 &lt;numeric&gt; 标头。 '&lt;execution&gt;' 用于执行策略。

      【讨论】:

        【解决方案11】:

        似乎没有人解决对向量中可能包含 NaN 值的元素求和的情况,例如numerical_limits&lt;double&gt;::quite_NaN()

        我通常循环遍历元素并直截了当地检查。

        vector<double> x;
        
        //...
        
        size_t n = x.size();
        
        double sum = 0;
        
        for (size_t i = 0; i < n; i++){
        
          sum += (x[i] == x[i] ? x[i] : 0);
        
        }
        

        一点也不花哨,即没有迭代器或任何其他技巧,但我就是这样做的。有时,如果循环内还有其他事情要做,我希望代码更具可读性,我会写

        double val = x[i];
        
        sum += (val == val ? val : 0);
        
        //...
        

        在循环内部并在需要时重新使用val

        【讨论】:

          【解决方案12】:

          std::accumulate 可能存在溢出问题,因此最好的方法是对更大的数据类型变量进行基于范围的累积以避免溢出问题。

          long long sum = 0;
          for (const auto &n : vector)
            sum += n;
          

          然后使用static_cast&lt;&gt; 进一步向下转换为适当的数据类型。

          【讨论】:

            【解决方案13】:

            这很容易。 C++11 提供了一种简单的方法来总结向量的元素。

            sum = 0; 
            vector<int> vec = {1,2,3,4,5,....}
            for(auto i:vec) 
               sum+=i;
            cout<<" The sum is :: "<<sum<<endl; 
            

            【讨论】:

              猜你喜欢
              • 2021-09-12
              • 2017-11-22
              • 1970-01-01
              • 1970-01-01
              • 2021-07-03
              • 2021-12-28
              • 2015-07-16
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多