C++ 成对函数求值算法(C+;+;,STL)

C++ 成对函数求值算法(C+;+;,STL),c++,algorithm,c++11,stl,c++14,C++,Algorithm,C++11,Stl,C++14,我需要成对地对STL容器应用自定义的func,即: // if c => {a,b,c,d,e,f,g}; // a,b,c,.. are just aliases for some object my_algorithm(c.begin(),c.end(),[](auto a, auto b){ a + b }); // c++14 应该分解为以下内容: temp1 = a + b; temp2 = c + d; temp3 = e + f; temp4 = temp1 + temp2

我需要成对地对STL容器应用自定义的
func
,即:

// if c => {a,b,c,d,e,f,g}; // a,b,c,.. are just aliases for some object
my_algorithm(c.begin(),c.end(),[](auto a, auto b){ a + b }); // c++14
应该分解为以下内容:

temp1 = a + b;
temp2 = c + d;
temp3 = e + f;
temp4 = temp1 + temp2;
temp5 = temp3 + g;
result = temp4 + temp5;
        /\
    /\       /\
  /\  /\
c = {a,b,c,d,e,f,g} // container of elements of type 'my_obj'
tmp = {a,b,c,d,e,f,g} // copy of 'c' to not impact 'c' while executing algorithm
while (tmp.size() > 1)
{
    // partition 'tmp' into even index elements 'c1' and odd index elements 'c2'
    // first iteration would look like this :
    // c1 = {a,c,e,g}
    // c2 = {b,d,f,identity} where 'idendity' is a new element (when 'tmp' size is odd) to match 'g' without impacting final result... identity = 0 for integers addition :)

    // overwrite first elements of 'tmp' with intermediate results
    std::transform(c1.cbegin(), c1.cend(), c2.cbegin(), tmp.begin(), std::plus<my_obj>()); // replace std::plus with any other binary operation including any proper lambda

    // cut 'tmp' ununsed upper half
    tmp.resize(size_t(0.5 * (tmp.size() + 1)));
}
my_obj result = tmp[0];
(我确信这种算法有一个恰当的名称,但我不知道这可能是什么)

我试过使用
std::acculate
,我不确定它的实现是否由标准定义,但在我的例子中,我的编译器似乎解决了这个问题(我认为这称为成对求和,对吧?)

这和我能得到的不一样

auto temp = c[0];
std::for_each(c.begin()+1,c.end(),[&temp](auto a){temp + a); // c++14
我浏览了STL和Boost,但没有找到相关内容。有没有提供这种算法的库?如果没有,有什么好的STL兼容实现的想法吗

编辑
我只是想补充一点,我对添加传统意义上的元素并不感兴趣——在这种情况下,顺序并不重要。我的函数将进行更复杂、加权的求和,如果以这种方式执行,将给出不同的结果。不过,我的问题更一般。

以下是我在C++11标准下尝试的STL兼容解决方案:

#include <cassert>
#include <cmath>
#include <cstddef>

#include <array>
#include <iostream>
#include <iterator>

namespace detail {

  // Returns first power of two which is strictly less than n
  unsigned int pot_half(std::ptrdiff_t n) {
    assert(n > 1);
    return 1 << (static_cast<unsigned int>(ceil(log2(n))) - 1);
  }

} // end namespace detail

struct tree_fold_on_empty_range : std::exception {};

template <typename Iterator, typename F>
auto tree_fold(const Iterator & begin, const Iterator & end, F && func) -> decltype(func(*begin, *end)) {
  std::ptrdiff_t diff = end - begin;
  switch (diff) {
    case 0: throw tree_fold_on_empty_range{}; // or, return {}; ?
    case 1: return *begin;
    case 2: return func(*begin, *(begin + 1));
    default: {
      Iterator mid{begin};
      std::advance(mid, detail::pot_half(diff));
      return func(tree_fold(begin, mid, func), tree_fold(mid, end, func));
    }
  }
}

int main() {
  for (uint n = 2; n < 20; ++n) {
    std::cout << n << " -> " << detail::pot_half(n) << std::endl;
  }
  std::cout << std::endl;

  std::array<int, 8> test{1, 2, 3, 4, 5, 6, 7, 8};
  std::cout << tree_fold(test.begin(), test.end(), [](int a, int b){ return a + b; }) << std::endl;
  std::cout << tree_fold(test.begin(), test.end(), [](int a, int b){ return a - b; }) << std::endl;
}
我认为这表明它是正确的:

1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 = 36
((1 - 2) - (3 - 4)) - ((5 - 6) - (7 - 8)) =
((-1) - (-1)) - ((-1) - (-1)) =
0 - 0 = 0
请注意,对于不是二的幂的范围,“正确”行为有点含糊不清。在我的版本中,我总是以小于
n
的二次方的一次方分割长度范围
n
。如果你给它二的幂,你总是得到一个完全平衡的二叉树。如果你给它6,你会得到这样的结果:

temp1 = a + b;
temp2 = c + d;
temp3 = e + f;
temp4 = temp1 + temp2;
temp5 = temp3 + g;
result = temp4 + temp5;
        /\
    /\       /\
  /\  /\
c = {a,b,c,d,e,f,g} // container of elements of type 'my_obj'
tmp = {a,b,c,d,e,f,g} // copy of 'c' to not impact 'c' while executing algorithm
while (tmp.size() > 1)
{
    // partition 'tmp' into even index elements 'c1' and odd index elements 'c2'
    // first iteration would look like this :
    // c1 = {a,c,e,g}
    // c2 = {b,d,f,identity} where 'idendity' is a new element (when 'tmp' size is odd) to match 'g' without impacting final result... identity = 0 for integers addition :)

    // overwrite first elements of 'tmp' with intermediate results
    std::transform(c1.cbegin(), c1.cend(), c2.cbegin(), tmp.begin(), std::plus<my_obj>()); // replace std::plus with any other binary operation including any proper lambda

    // cut 'tmp' ununsed upper half
    tmp.resize(size_t(0.5 * (tmp.size() + 1)));
}
my_obj result = tmp[0];
然而,并没有什么东西说总是除以2是不正确的,所以你们会得到这样的树结构

        /\
    /\       /\
  /\       /\

所以我觉得你的问题有点不够具体。也许这对你来说并不重要,只要深度是
O(logn)

自2015年11月以来,我一直在一个所谓的VectorFuncRange容器中工作,该容器在C++14中以STL样式解析它

我做了自己的beta版,它可以很好地模拟std::vector容器,但是使用了一个func_range()方法,该方法在O(logn)中返回一个范围内的函数求值,作为树求值。我赞成,即使在内部作为树进行评估,它们也只是向量,具有O(1)随机访问、在摊销O(1)中推回、最坏情况O(logn)等。我还没有编写一些std::vector方法,例如emplace_back()和不同的构造,但用作向量的主要方法是。出于测试原因,我比较了rang_func()和range_func_dumb(),第二个版本以线性顺序计算函数

VectorFuncRange.h我的当前版本: 这是一个测试代码,它有5种不同的方式,包括整数、矩阵和其他类型以及许多函数:


我曾考虑过加入一个公共Git,但我想在此之前我应该组织更多的代码,我不知道其他人是否有兴趣参与其中。

您应该看看std::transform的第二种形式:

在C++ 11附近的伪代码中,算法的STL实现可能是这样的:

temp1 = a + b;
temp2 = c + d;
temp3 = e + f;
temp4 = temp1 + temp2;
temp5 = temp3 + g;
result = temp4 + temp5;
        /\
    /\       /\
  /\  /\
c = {a,b,c,d,e,f,g} // container of elements of type 'my_obj'
tmp = {a,b,c,d,e,f,g} // copy of 'c' to not impact 'c' while executing algorithm
while (tmp.size() > 1)
{
    // partition 'tmp' into even index elements 'c1' and odd index elements 'c2'
    // first iteration would look like this :
    // c1 = {a,c,e,g}
    // c2 = {b,d,f,identity} where 'idendity' is a new element (when 'tmp' size is odd) to match 'g' without impacting final result... identity = 0 for integers addition :)

    // overwrite first elements of 'tmp' with intermediate results
    std::transform(c1.cbegin(), c1.cend(), c2.cbegin(), tmp.begin(), std::plus<my_obj>()); // replace std::plus with any other binary operation including any proper lambda

    // cut 'tmp' ununsed upper half
    tmp.resize(size_t(0.5 * (tmp.size() + 1)));
}
my_obj result = tmp[0];
c={a,b,c,d,e,f,g}//类型为“my_obj”的元素的容器
tmp={a,b,c,d,e,f,g}//在执行算法时复制'c'以不影响'c'
而(tmp.size()>1)
{
//将“tmp”划分为偶数索引元素“c1”和奇数索引元素“c2”
//第一次迭代如下所示:
//c1={a,c,e,g}
//c2={b,d,f,identity}其中'idendity'是一个新元素(当'tmp'大小为奇数时)以匹配'g',而不影响最终结果…identity=0表示整数加法:)
//用中间结果覆盖“tmp”的第一个元素
std::transform(c1.cbegin()、c1.cend()、c2.cbegin()、tmp.begin()、std::plus());//将std::plus替换为任何其他二进制操作,包括任何正确的lambda
//切割“tmp”未使用的上半部分
tmp.resize(大小(0.5*(tmp.size()+1));
}
my_obj result=tmp[0];

显然,在开始时复制“c”并在每次迭代时将“tmp”分成两半是有成本的。您可以从这里决定如何进行优化:)

考虑我提出的一些建议解决方案(特别是Chris Beck的),我现在正试图进一步优化这些解决方案。我把它转移到了另一个线程,因为代码打开了许多值得讨论的问题

部分和()?有点不清楚应该是什么结果
std::acculate
,这是标准明确定义的。似乎您希望自下而上构造一个平衡树,这是可能的,但在标准库afaik中是不可能的。(“构造平衡树”有不同的算法,这取决于你对“平衡”的定义,你只提供了一个例子;我认为这对于一个实现来说不够精确。)事实上,我从CUDA知道这种累积,在那里它被称为“平行缩减”我认为以这种方式添加它们没有任何好处,除非您真的要使用多个线程?或者,如果您担心某个浮点问题,则可能。。。我很难说这种算法通常有多有用,我不认为我会期望在标准库中找到它。我对传统意义上的添加不感兴趣。在我的例子中,我传递的函数做了一个更复杂、加权的求和,如果这样做的话,会得到不同的结果。我不一定在寻找一个涉及树的算法,所以我没有想到这样的细节。我认为这在很大程度上取决于情况:因为我们讨论的是操作顺序确实重要的情况,所以非二次幂的行为是否可以接受取决于操作/情况。这在我的情况下可能会起作用,但我必须运行一些测试。谢谢,这是一个很好的尝试,但在我的情况下,
a、b、c、d、e、f
的复制成本相当高。。也许我可以使用一些poi进行优化