STL算法——accumulate

来源:互联网 发布:jquery.md5.js cdn 编辑:程序博客网 时间:2024/06/07 03:22

       如果有一个算法可以让你做各种事, 那这个算法一定是 std:accumulate.

        知道怎样使用它,以及怎样不使用它非常重要。

基本用法

Numeric 类型

 第一件需要知道的事情就是std::accumulate 的所在位置: 它在<numeric>头文件中,而不是像其他兄弟算法一样在<algorithm>头文件中。

正如 Scott Meyers 的《Effective STL》条目37, std::accumulate 用于统计一个区间(范围),换而言之,std::accumulate 把一组元素提炼成一个对象,一个数。

 如果你没有明确指明, std::accumulate 不会对区间内所有元素进行sum。 sum 是通过符号+来进行计算的。 如果我们需要对两个值进行+运算, 那么我们同时需要一个初始值。

以下是使用原型:

template<typename InputIterator, typename T>T accumulate(InputIterator first, InputIterator last, T initialValue);
 因此, 对于一组数字集合, std::accmulate 把它们汇总起来:

std::vector<int> numbers = { 2, 9, -4, 2 };int sum = std::accumulate(begin(numbers), end(numbers), 0);
注意此处存在一个陷进。 以上代码片段中,初始值是整型,但是请看下面的代码片段中,不包含整型数字的集合:

std::vector<double> doubles = { 1.5, 2, 3.5 };double sum = std::accumulate(begin(doubles), end(doubles), 0);
你能预测到它的输出吗?

它的结果 sum = 6。

是不觉得很惊奇,因为1.5+2+3.5=7, 不是6。

为了理解发生类什么,请回头看下 std::accmulate 的原型:

template<typename InputIterator, typename T>T accumulate(InputIterator first, InputIterator last, T initialValue);

注意 类型 T 和集合区间中的元素类型不一定相关。 在我们的调用中, 它是由第三个参数 0 推导出的,因为0是int,所以T是 int。 所以std:;accumulate 以int类型工作, 对每次累加运算进行类去尾截整。

可以简单修改下, 把第三个参数0 改成一个double 类型数值。

std::vector<double> doubles = { 1.5, 2, 3.5 };double sum = std::accumulate(begin(doubles), end(doubles), 0.);
这样最后的sum结果就是7。

这个例子值得我们特别注意,因为他能通过编译没问题,但是计算结果错了而我们却丝毫不知。

其他类型

对于其它类型,如果支持运算符+, 那么它就和numeric类型一样,都可以使用std::accumulate.

 std::string 的 + 运算符拼接了各个元素:

std::vector<std::string> words = { "Winter ", "is ", "Coming." };std::string sentence = std::accumulate(begin(words), end(words), std::string(""));

注意我们需要传递 std::string(""),作为初始值, 而不只是“”。因为后者所推导的T类型是 const char*,而不是std::string, 编译将通不过。

事实上,区间的元素类型没有实现运算符+,也可以使用std::accumulate 的第二个重写函数, 它将使用一个函数(或者函数对象)来代替运算符+。

这个函数的两个参数有可能还是不同的类型。 以下是相关的一个例子。

 我们来考虑一个电梯,它能够承载若干人,但那是总的承载重量不能超过一个特定的阈值。以下代码计算电梯中一群人的总的重量:

double totalWeight = std::accumulate(begin(group), end(group), 0.,                    [](double currentWeight, Person const& person)                    {                        return currentWeight + person.getWeight();                    });
  请看算法中最后一个参数,它代表一个函数(这里是一个lambda表达式),这个函数的第一个参数(currentWeight)以第三个参数(这里是0.)初始化。 新的值不断的“吸收”到当前值。 当前值被“吸收",返回当前值, 或者当区间所有元素被处理,返回累加值。

std::accumulate的重写提供了很多可能性。但是其中有些要尽可能避免,因为它们使得代码像在用斧头在做分解,或者是用锯子。

我们将会给出一个例子,但在此之前,我们先提供一个原则:

std::accumulate是对一个区间汇集成一个数的建模。

确实,想象下如果我们想要对电梯中的每个人进行称重。可以使用std::accumulate通过以下方式实现:

std::accumulate(begin(group),end(group), &weights,[](std::vector<double>*currentWeights, Person const& person){currentWeights->push_back(person.getWeight());returncurrentWeights;});
但这样做是错误的。我曾经看到过别人这样的代码。见鬼,事实上我自己也曾经这么做过,当时对STLalgorithms还不是特别了解。

为什么这是错误的了?因为以上代码中,传入了一个区间,在区间的每个元素中使用了函数,最终结果生出了新的区间。事实上,这个功能应该使用std::transform去实现。

而且,以上代码扭曲了std::accumulate汇集一个区间为一个对象(一个值)的原则。

为了使以上代码更加具有意义。我们使用std::transform来实现该功能:

std::transform(begin(group),end(group), std::back_inserter(weights),              [](Personconst& person){ return person.getWeight();});

你知道有这样一种现象:当你有一把锤子,你会觉得每件事都是指甲?同样的,使用accumulate去表达函数应用就像使用锤子去扫地。你花了很多时间去实现他,而你的邻居(后来者维护的dev)且因此而憎恨死你了。

想听一些类似accumulate误用的建议吗?

“如果std::accumulate的返回值不是一个对象(一个值)”,那么这是一个信号:accumulate不是我们想要的工具。

更多关于std::accumulate

以上这些都会让你对使用accumulate更有效率。但是不单单只有这些。

在关注BenDeane’sCppCon讨论时 std::accumulate:Exploring an Algorithmic Empire.,我意识到这一点。

在文中,为了提起你的阅读兴趣,Ben展示了大量STL的算法可以被std::accumulate实现!同时,accumulate也可以用于实现std::all_of,

还有更多的。

std::accumulate(std::begin(booleans),std::end(booleans), true, std::logical_and<>())

Accumulate是一个很有用的工具。使用它,但是当心。


原文:

https://www.fluentcpp.com/2017/10/17/stdaccumulate-your-knowledge-on-algorithms/