了解的std ::积聚(Understanding std::accumulate)

2019-08-01 00:46发布

我想知道为什么std::accumulate (又名减少),需要3个参数。 对于那些谁不知道什么accumulate是,它的使用就像这样:

vector<int> V{1,2,3};  
int sum = accumulate(V.begin(), V.end(), 0);
// sum == 6

呼吁accumulate相当于:

sum = 0;  // 0 - value of 3rd param
for (auto x : V)  sum += x;

还有一个可选的第四个参数,它允许更换除了与任何其他操作。

我已经听到的理由是,如果你需要让你不要说增加了,但乘以一个向量的元素,我们需要其他的(非零)初始值:

vector<int> V{1,2,3};
int product = accumulate(V.begin(), V.end(), 1, multiplies<int>());

但是,为什么不这样做像Python -用于设置初始值V.begin()并使用范围从开始V.begin()+1 。 事情是这样的:

int sum = accumulate(V.begin()+1, V.end(), V.begin());

这对于任何运算工作。 为什么第三个参数需要呢?

Answer 1:

事情这样,很是烦人的,知道肯定一系列代码不为空,并且要开始从范围内的第一个元素积累。 根据所使用与累积操作,它并不总是显而易见的是什么“零”值使用的是。

如果在另一方面,你只提供需要非空范围的一个版本,这很烦人的不确知他们的范围是不是空的来电。 一个额外的负担放在他们。

一种观点是,两全其美当然同时提供的功能。 作为一个例子,Haskell中既提供foldl1foldr1一起(需要非空列表) foldlfoldr (其反射镜std::transform )。

另一种看法是,既然我们可以在一个平凡的改造等方面来实现(因为你已经证明: std::transform(std::next(b), e, *b, f) - std::next是C ++ 11但问题依然存在),优选的是使界面作为最小,因为它可以与表达能力没有实际损失。



Answer 2:

你让一个错误的假设:这种类型的T是相同类型的InputIterator

但是, std::accumulate是通用的,并允许所有不同种类的创意积累和减少的。

实施例#1:累积跨越雇员薪水

这里有一个简单的例子:一个Employee类,有许多数据字段。

class Employee {
/** All kinds of data: name, ID number, phone, email address... */
public:
 int monthlyPay() const;
};

你不能有意义的“积累”的一组员工。 这是没有意义的; 这是不确定的。 但是,你可以定义有关员工的积累。 比方说,我们要总结全体员工所有的月薪。 std::accumulate可以这样做:

/** Simple class defining how to add a single Employee's
 *  monthly pay to our existing tally */
auto accumulate_func = [](int accumulator, const Employee& emp)
   return accumulator + emp.monthlyPay();
 };

// And here's how you call the actual calculation:
int TotalMonthlyPayrollCost(const vector<Employee>& V)
{
 return std::accumulate(V.begin(), V.end(), 0, accumulate_func);
}

因此,在这个例子中,我们积累了int在的收藏价值Employee的对象。 在这里,累加和是不一样类型的变量,我们实际上是求和。

实施例#2:累积的平均

您可以使用accumulate的更复杂的类型积累,以及-也许要追加值向量; 也许你有你在输入跟踪一些晦涩难懂的统计; 等你积累了什么并不一定只是一个号码; 它可以是更复杂的东西。

例如,下面是一个使用一个简单的例子accumulate计算INTS的矢量的平均:

// This time our accumulator isn't an int -- it's a structure that lets us
// accumulate an average.
struct average_accumulate_t
{
    int sum;
    size_t n;
    double GetAverage() const { return ((double)sum)/n; }
};

// Here's HOW we add a value to the average:
auto func_accumulate_average = 
    [](average_accumulate_t accAverage, int value) {
        return average_accumulate_t(
            {accAverage.sum+value, // value is added to the total sum
            accAverage.n+1});      // increment number of values seen
    };

double CalculateAverage(const vector<int>& V)
{
    average_accumulate_t res =
        std::accumulate(V.begin(), V.end(), average_accumulate_t({0,0}), func_accumulate_average)
    return res.GetAverage();
}

实施例#3:累积的运行平均值

你所需要的初始值的另一个原因是因为该值并不总是你正在做计算的默认/中性值。

让我们构建上,我们已经看到了平均的例子。 但是现在,我们希望能保持运行平均的一类-也就是说,我们可以继续在新的价值观喂养, 到目前为止确认平均值,跨越多个电话。

class RunningAverage
{
    average_accumulate_t _avg;
public:
    RunningAverage():_avg({0,0}){} // initialize to empty average

    double AverageSoFar() const { return _avg.GetAverage(); }

    void AddValues(const vector<int>& v)
    {
        _avg = std::accumulate(v.begin(), v.end(), 
            _avg, // NOT the default initial {0,0}!
            func_accumulate_average);
    }

};

int main()
{
    RunningAverage r;
    r.AddValues(vector<int>({1,1,1}));
    std::cout << "Running Average: " << r.AverageSoFar() << std::endl; // 1.0
    r.AddValues(vector<int>({-1,-1,-1}));
    std::cout << "Running Average: " << r.AverageSoFar() << std::endl; // 0.0
}

这是我们绝对依靠的是能够设置为初始值的情况下std::accumulate -我们需要能够初始化从不同的出发点积累。


综上所述, std::accumulate有利于任何时候你迭代的输入范围,并建立跨范围内一个单一的结果。 但结果并不需要是同一类型的范围,你不能什么初值使用任何假设 - 这就是为什么你必须有一个初步的实例作为累加结果使用。



Answer 3:

如果你想accumulate(V.begin()+1, V.end(), V.begin())你可以只写。 但是,如果你认为v.begin()可能会v.end()(即v是空的)? 如果什么v.begin() + 1未实现(因为V只能器具++,不generized除)? 如果累加器的类型是什么不是元素的类型? 例如。

std::accumulate(v.begin(), v.end(), 0, [](long count, char c){
   return isalpha(c) ? count + 1 : count
});


Answer 4:

由于标准库的算法都应该为(兼容)迭代器的任意范围内的工作。 所以,第一个参数accumulate不必将begin()也可能是之间的任何迭代begin()和一个前end() 它也可以使用反向迭代器。

整个想法是从数据分离算法。 您的建议,如果我理解正确,需要在数据一定的结构。



Answer 5:

它确实是没有必要的。 我们的代码库具有2和3个参数的重载它们使用T{}的值。

但是, std::accumulate是很老的; 它来自于原来的STL。 我们的代码库具有花式std::enable_if逻辑“2个迭代器和初始值”和“2个迭代器和减少操作者”之间进行区分。 这需要C ++ 11。 我们的代码也使用尾随返回类型( auto accumulate(...) -> ... )来计算的返回类型,另一个C ++ 11功能。



文章来源: Understanding std::accumulate