如果有一个算法可以让你做各种事, 那这个算法一定是 std:accumulate.
知道怎样使用它,以及怎样不使用它非常重要。
第一件需要知道的事情就是std::accumulate 的所在位置: 它在
正如 Scott Meyers 的《Effective STL》条目37, std::accumulate 用于统计一个区间(范围),换而言之,std::accumulate 把一组元素提炼成一个对象,一个数。
如果你没有明确指明, std::accumulate 不会对区间内所有元素进行sum。 sum 是通过符号+来进行计算的。 如果我们需要对两个值进行+运算, 那么我们同时需要一个初始值。
以下是使用原型:
template
T accumulate(InputIterator first, InputIterator last, T initialValue);
因此, 对于一组数字集合, std::accmulate 把它们汇总起来:
std::vector numbers = { 2, 9, -4, 2 };
int sum = std::accumulate(begin(numbers), end(numbers), 0);
注意此处存在一个陷进。 以上代码片段中,初始值是整型,但是请看下面的代码片段中,不包含整型数字的集合:
std::vector 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
T accumulate(InputIterator first, InputIterator last, T initialValue);
可以简单修改下, 把第三个参数0 改成一个double 类型数值。
std::vector doubles = { 1.5, 2, 3.5 };
double sum = std::accumulate(begin(doubles), end(doubles), 0.);
这样最后的sum结果就是7。
这个例子值得我们特别注意,因为他能通过编译没问题,但是计算结果错了而我们却丝毫不知。
对于其它类型,如果支持运算符+, 那么它就和numeric类型一样,都可以使用std::accumulate.
std::string 的 + 运算符拼接了各个元素:
std::vector words = { "Winter ", "is ", "Coming." };
std::string sentence = std::accumulate(begin(words), end(words), 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*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’s 的CppCon讨论时 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/