【问题标题】:What are practical uses for STL's 'partial_sum'?STL 的“partial_sum”有什么实际用途?
【发布时间】:2011-05-06 04:59:47
【问题描述】:

partial_sum 算法在STL 中的实际用途是什么/在哪里?

还有哪些其他有趣/重要的示例或用例?

【问题讨论】:

    标签: c++ algorithm stl sum partial


    【解决方案1】:

    我用它来减少我的玩具 lambda 演算解释器中一个简单的标记清除垃圾收集器的内存使用量。

    GC 池是一个大小相同的对象数组。目标是消除未链接到其他对象的对象,并将剩余对象压缩到数组的开头。由于对象在内存中移动,因此每个链接都需要更新。这需要一个对象重映射表。

    partial_sum 允许以压缩格式存储表(每个对象只有一位),直到扫描完成并释放内存。由于对象很小,这显着减少了内存使用。

    1. 递归标记使用的对象并填充布尔数组。
    2. 使用remove_if 将标记的对象压缩到池的开头。
    3. 在布尔值上使用partial_sum 以生成指向新池的指针/索引表。
      • 之所以有效,是因为第 N 个标记的对象在数组中有 N 个前面的 1,并获取池索引 N。
    4. 再次扫描池并使用重映射表替换每个链接。

    将重映射表放在刚刚释放的内存中,对数据缓存特别友好。

    【讨论】:

    • 当我想构建离散概率分布的 CDF 时,我使用 partial_sum
    • partial_sum 也称为“扫描”。 Matlab 名称为“cumsum”,表示累积和。
    • 这个答案似乎是这个话题最有趣的。但是,真的很难理解你到底做了什么,以及partial_sum 究竟是如何帮助你的。因此,如果您可以发布一些代码以使事情易于理解,那就太好了。
    【解决方案2】:

    关于部分和要注意的一点是,它是撤消相邻差异的操作,很像 - 撤消 +。或者更好的是,如果您还记得微积分是微分消除积分的方式。更好,因为相邻的差异本质上是微分,部分和是积分。

    假设您模拟了一辆汽车,并且在每个时间步,您都需要知道位置、速度和加速度。您只需要存储其中一个值,就可以计算另外两个值。假设您在每个时间步存储位置,您可以将位置的相邻差值给出速度,并将速度的相邻差值给出加速度。或者,如果你存储加速度,你可以取部分和来给出速度,而速度的部分和给出位置。

    部分求和是大多数人不会经常使用的功能之一,但当您找到合适的情况时会非常有用。很像微积分。

    【讨论】:

      【解决方案3】:

      我上次(本来)使用它是在将离散概率分布(p(X = k) 的数组)转换为累积分布(p(X

      不过,该代码不在 C++ 中,所以我自己做了部分求和。

      【讨论】:

      • 从精确的角度来看,在感兴趣的点评估 pdf 实际上要好得多,而不是按照您的尝试去做。不过还是个不错的建议。
      • @sonicoder:我不明白你的意思,抱歉。数组 is 是函数,这就是离散分布与连续分布相反的含义。毫无疑问,更精确。
      • 抱歉错过了,不知为何我一直在想。
      • 这是一个最小的可运行示例:stackoverflow.com/a/42332864/895245
      【解决方案4】:

      您可以使用它来生成单调递增的数字序列。例如,下面会生成一个包含数字 1 到 42 的 vector

      std::vector<int> v(42, 1);
      std::partial_sum(v.begin(), v.end(), v.begin());
      

      这是一个日常用例吗?可能不会,尽管我发现它在很多场合都很有用。

      您还可以使用std::partial_sum 生成阶乘列表。 (不过,这甚至更没用,因为可以用典型整数数据类型表示的阶乘数量非常有限。不过这很有趣 :-D)

      std::vector<int> v(10, 1);
      std::partial_sum(v.begin(), v.end(), v.begin());
      std::partial_sum(v.begin(), v.end(), v.begin(), std::multiplies<int>());
      

      【讨论】:

      • 你会很高兴知道 iota 在 C++0x 中死而复生。
      • 这些看起来更像是在 sgi stl 和 cpp-ref 页面上找到的琐碎示例,但还是不错的尝试。
      • @sonicoder:嗯,我回答中的第一个例子是我唯一使用过partial_sum 的东西。这肯定没有像 Potatoswatter 的回答那样令人兴奋。
      • 这很像我的回答。你的序列总是加 1;我的有时会增加 0。我对这里的反应感到非常惊讶,我应该发布那个程序......当我有时间......
      【解决方案5】:

      个人用例:轮盘赌-轮盘选择

      我在轮盘赌选择算法中使用partial_sum (link text)。该算法从容器中随机选择元素,其概率与预先给定的某个值成线性关系。

      因为我的所有元素都可以从带来不必要的标准化值中进行选择,所以我使用partial_sum 算法来构建类似“轮盘赌”的东西,因为我总结了所有元素。然后我在这个范围内选择了一个随机变量(最后一个partial_sum 是所有变量的总和)并使用stl::lower_bound 搜索我的随机搜索到达的“轮子”。 lower_bound 算法返回的元素是被选中的元素。

      除了使用partial_sum 的代码清晰和富有表现力的优势之外,我还可以在尝试GCC 并行模式时获得一些速度,该模式为某些算法带来并行化版本,其中之一是 partial_sum (@ 987654323@)。

      我知道的另一个用途:并行处理中最重要的算法原语之一(但可能离 STL 有点远)

      如果您对使用 partial_sum 的重度优化算法感兴趣(在这种情况下,同义词“scan”或“prefix_sum”下的结果可能会更多),请访问并行算法社区。他们一直需要它。如果不使用它,您将找不到基于quicksortmergesort 的并行排序算法。此操作是使用的最重要的并行原语之一。我认为它最常用于计算动态算法中的偏移量。想想快速排序中的一个分区步骤,它被拆分并馈送到并行线程。在计算之前,您不知道分区的每个插槽中的元素数量。因此,您需要为所有线程设置一些偏移量以供以后访问。

      也许您会在GPU 处理这个现在热门的话题中找到更多信息。一篇关于 Nvidia 的 CUDA 和 scan-primitive 的短文以及一些应用示例,您可以在 Chapter 39. Parallel Prefix Sum (Scan) with CUDA 中找到。

      【讨论】:

        【解决方案6】:

        个人用例:从 CLRS 计数排序的中间步骤:

        COUNTING_SORT (A, B, k)
        
        for i ← 1 to k do
            c[i] ← 0
        for j ← 1 to n do
            c[A[j]] ← c[A[j]] + 1
        //c[i] now contains the number of elements equal to i
        
        
        // std::partial_sum here
        for i ← 2 to k do
            c[i] ← c[i] + c[i-1]
        
        
        // c[i] now contains the number of elements ≤ i
        for j ← n downto 1 do
            B[c[A[i]]] ← A[j]
            c[A[i]] ← c[A[j]] - 1
        

        【讨论】:

          【解决方案7】:

          您可以建立一个“移动总和”(移动平均线的前身):

          template <class T>
          void moving_sum (const vector<T>& in, int num, vector<T>& out)
          {
              // cummulative sum
              partial_sum (in.begin(), in.end(), out.begin());
          
              // shift and subtract
              int j;
              for (int i = out.size() - 1; i >= 0; i--) {
                  j = i - num;
                  if (j >= 0)
                      out[i] -= out[j];
              }    
          }
          

          然后调用它:

          vector<double> v(10);
          // fill in v
          vector<double> v2 (v.size());
          moving_sum (v, 3, v2);
          

          【讨论】:

          • partial_sum 之前的移位和减法不太可能溢出。
          • 另外,考虑让您的moving_sum 将迭代器作为参数,而不是vector
          • 什么是“移动总和”?
          【解决方案8】:

          你知道,我实际上确实使用过一次 partial_sum()... 这是一个有趣的小问题,我在一次工作面试中被问到。我非常喜欢它,我回家编码了。

          问题是:给定一个连续的整数序列,找到具有最高值的最短子序列。例如。给定:

          Value: -1  2  3 -1  4 -2 -4  5
          Index:  0  1  2  3  4  5  6  7
          

          我们会找到子序列 [1,4]

          现在显而易见的解决方案是运行 3 个 for 循环,遍历所有可能的开始和结束,并依次将每个可能的子序列的值相加。效率低下,但可以快速编写代码并且很难出错。 (特别是当第三个 for 循环只是 accumulate(start,end,0) 时。)

          正确的解决方案涉及分而治之/自下而上的方法。例如。将问题空间分成两半,对每一半计算该部分中包含的最大子序列、包括起始编号的最大子序列、包括结束编号的最大子序列以及整个部分的子序列。有了这些数据,我们就可以将这两部分组合在一起,而无需对任何一个部分进行进一步评估。显然,每一半的数据可以通过进一步将每一半分成两半(四分之一),每四分之一分成两半(八分之一),依此类推,直到我们有琐碎的单例。这一切都非常有效。

          但除此之外,我还想探索第三种(效率稍低)的选项。它类似于 3-for-loop 的情况,只是我们将相邻的数字相加以避免太多工作。这个想法是,当我们可以添加 t1=a+b、t2=t1+c 和 t3=t2+d 时,不需要添加 a+b、a+b+c 和 a+b+c+d。这是一个空间/计算权衡的事情。它通过转换序列来工作:

          Index: 0 1 2  3  4
          FROM:  1 2 3  4  5
            TO:  1 3 6 10 15
          

          从而为我们提供了从 index=0 开始到 index=0,1,2,3,4 结束的所有可能的子字符串。

          然后我们迭代这个集合,减去连续的可能“开始”点......

          FROM:  1 3 6 10 15
            TO:  - 2 5  9 14
            TO:  - - 3  7 12
            TO:  - - -  4  9
            TO:  - - -  -  5
          

          从而为我们提供所有可能子序列的值(总和)。

          我们可以通过max_element()找到每次迭代的最大值。

          通过 partial_sum() 最容易完成第一步。

          剩下的步骤通过 for 循环和 transform(data+i,data+size,data+i,bind2nd(minus(),data[i-1] )).

          显然 O(N^2)。但仍然有趣和有趣......

          【讨论】:

          • 这是一个很常见的问题,Programming Pearls Column 8 有一个需要 O(n) 的答案,cs.bell-labs.com/cm/cs/pearls/sketch08.htmlcs.bell-labs.com/cm/cs/pearls/maxsum.c
          • 我的立场是正确的。我以前没有见过编程珍珠,也没有见过这个特殊的问题。我(错误地)被告知 O(NlogN) 是最好的解决方案。虽然我觉得删除我的帖子很诱人,但也许最好让它作为一个例子。 (对我自己和其他人......)
          【解决方案9】:

          部分和通常在并行算法中很有用。考虑代码

          for (int i=0; N>i; ++i) {
            sum += x[i];
            do_something(sum);
          }
          

          如果你想并行化这段代码,你需要知道部分和。我正在使用 GNUs 并行版本的 partial_sum 来做一些非常相似的事情。

          【讨论】:

            【解决方案10】:

            我经常使用部分求和,不是求和,而是根据前面的顺序计算序列中的当前值。

            例如,如果您集成一个功能。每个新步骤都是上一步,vt += dvdtvt = integrate_step(dvdt, t_prev, t_prev+dt);

            【讨论】:

              【解决方案11】:

              在非参数贝叶斯方法中,有一个 Metropolis-Hastings 步骤(每次观察),用于确定对新的或现有的集群进行采样。如果必须对现有集群进行采样,则需要使用不同的权重来完成。这些加权可能性在以下示例代码中进行了模拟。

              #include <random>                                                                                                       
              #include <iostream>                                                                                                     
              #include <algorithm>                                                                                                    
              
              int main() {                                                                                                            
              
                  std::default_random_engine generator(std::random_device{}());                                                       
                  std::uniform_real_distribution<double> distribution(0.0,1.0);                                                       
              
                  int K = 8;                                                                                                          
              
                  std::vector<double> weighted_likelihood(K);                                                                         
                  for (int i = 0; i < K; ++i) {                                                                                       
                      weighted_likelihood[i] = i*10;                                                                                  
                  }                                                                                                                   
                  std::cout << "Weighted likelihood: ";                                                                               
                  for (auto i: weighted_likelihood) std::cout << i << ' ';                                                            
                  std::cout << std::endl;                                                                                             
              
                  std::vector<double> cumsum_likelihood(K);                                                                           
                  std::partial_sum(weighted_likelihood.begin(), weighted_likelihood.end(), cumsum_likelihood.begin());                
              
                  std::cout << "Cumulative sum of weighted likelihood: ";                                                             
                  for (auto i: cumsum_likelihood) std::cout << i << ' ';                                                              
                  std::cout << std::endl;                                                                                             
              
                  std::vector<int> frequency(K);                                                                                      
              
                  int N = 280000;                                                                                                     
                  for (int i = 0; i < N; ++i) {                                                                                       
                      double pick = distribution(generator) * cumsum_likelihood.back();                                               
              
                      auto lower = std::lower_bound(cumsum_likelihood.begin(), cumsum_likelihood.end(), pick);                        
                      int index = std::distance(cumsum_likelihood.begin(), lower);                                                    
                      frequency[index]++;                                                                                             
                  }                                                                                                                   
              
                  std::cout << "Frequencies: ";                                                                                       
                  for (auto i: frequency) std::cout << i << ' ';                                                                      
                  std::cout << std::endl;                                                                                             
              
              }
              

              请注意,这与https://stackoverflow.com/users/13005/steve-jessop 的答案没有什么不同。添加它是为了提供有关特定情况的更多上下文(非参数贝叶斯方法,例如 Neal 使用 Dirichlet 过程作为先验的算法)和使用 partial_sum 结合 lower_bound 的实际代码。

              【讨论】:

              • 很高兴知道std::lower_bound!该文档提到它返回大于或等于边界的第一个元素。为了避免在序列中排在第一位时选择 0 概率,我建议改用 std::upper_bound。只会选择高于(不等于)随机选择的值(并且永远不会选择 0 概率)。您需要确保您的随机生成器是独占的(从不返回随机范围的上限),以便在所有情况下都能正常工作。 uniform_real_distribution 例如这样做。
              猜你喜欢
              • 2011-04-25
              • 2018-08-07
              • 2011-04-02
              • 2019-05-17
              • 2015-04-22
              • 2021-07-22
              • 1970-01-01
              • 2011-09-20
              • 2015-06-07
              相关资源
              最近更新 更多