使用Java 8流API的累积总和

run*_*ror 11 java java-8 java-stream

我有一个整数列表,说list1,我想得到另一个列表list2,它将包含从开始到当前索引为止的累积总和。如何使用Stream API Java 8做到这一点?

List<Integer> list1 = new ArrayList<>();
list1.addAll(Arrays.asList(1, 2, 3, 4));
List<Integer> list2 = new ArrayList<>();
// initialization
list2.add(list1.get(0));
for(int i=1;i<list1.size();i++) {
// increment step
    list2.add(list2.get(i-1) + list1.get(i));
}
Run Code Online (Sandbox Code Playgroud)

如何将上述命令式代码转换为声明式代码?

list2 should be [1, 3, 6, 10]
Run Code Online (Sandbox Code Playgroud)

Fed*_*ner 12

流不适合此类任务,因为其中涉及状态(累积的部分和)。相反,您可以使用Arrays.parallelPrefix:

Integer[] arr = list1.toArray(Integer[]::new);

Arrays.parallelPrefix(arr, Integer::sum);

List<Integer> list2 = Arrays.asList(arr);
Run Code Online (Sandbox Code Playgroud)

这是第list1一个使用Collection.toArray,从JDK 11开始可用的,将其复制到数组的方法。如果您尚未使用Java 11,则可以用传统toArray调用替换第一行:

Integer[] arr = list1.toArray(new Integer[0]);
Run Code Online (Sandbox Code Playgroud)

该解决方案不使用流,而是声明性的,因为它将Arrays.parallelPrefix累积操作作为参数接收(Integer::sum在这种情况下)。

时间复杂度为O(N),尽管与建立并行处理所需的基础结构可能涉及一些非微小的固定成本。但是,根据文档:

对于大型数组,并行前缀计算通常比顺序循环更有效

因此,似乎值得尝试这种方法。

另外,值得一提的是,这种方法之所以有效,Integer::sum是因为它是一种关联操作。这是一个要求。

  • 从未听说过并行前缀。非常感谢你教会了我一个新概念。:)。我一定会试一试。 (2认同)
  • @run_time_error [更多内容](https://en.wikipedia.org/wiki/Prefix_sum)。 (2认同)
  • 不要让生活变得不必要。使用`list1.toArray(new Integer [0])`。如[这篇伟大的文章](https://shipilev.net/blog/2016/arrays-wisdom-ancients/)所述,在常用的Hotspot JVM中使用零大小甚至更有效。这就是[JDK 11的新方法](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Collection.html#toArray(java .util.function.IntFunction))只是做同样的事情:“ *默认实现以零调用生成器函数,然后将结果数组传递给`toArray(T [])`。*” (2认同)
  • @run_time_error也值得一读:[Java 8中新引入的Arrays.parallelPrefix(…)如何工作?](/sf/ask/3707338701/) (2认同)

Mic*_*ael 6

对于每个索引:从零迭代到该索引,获取每个元素,然后获取和将
Box中的int Integers
收集到列表

IntStream.range(0, list1.size())
    .map(i -> IntStream.rangeClosed(0, i).map(list1::get).sum())
    .boxed()
    .collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

您每次都将每个数字加在一起,而不是重复使用以前的累积结果,但是流不适合查看先前迭代的结果。

您可以编写自己的收集器,但是到现在为止,老实说,您为什么还要烦扰流?

list1.stream()
    .collect(
        Collector.of(
            ArrayList::new,
            (a, b) -> a.add(a.isEmpty() ? b : b + a.get(a.size() - 1)),
            (a, b) -> { throw new UnsupportedOperationException(); }
        )
    );
Run Code Online (Sandbox Code Playgroud)

  • 那是O(n ^ 2)!(与OP的O(n)相反) (5认同)

Yas*_*jaj 5

一个O(n)(仅按顺序工作)解决方案如下,但我觉得它不是很优雅。我想这是一个品味问题

AtomicInteger ai = new AtomicInteger();
List<Integer> collect = list1.stream()
                             .map(ai::addAndGet)
                             .collect(Collectors.toList());
System.out.println(collect); // [1, 3, 6, 10]
Run Code Online (Sandbox Code Playgroud)

  • 此解决方案仅在流是顺序的情况下才有效。并行化它会完全打破这一点。 (7认同)