使用 Java 8 流 API 的累积总和

2022-09-02 20:16:24

我有一个整数列表说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));
}

如何将上述命令式代码更改为声明式代码?

list2 should be [1, 3, 6, 10]

答案 1

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

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

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

List<Integer> list2 = Arrays.asList(arr);

这首先通过使用 Collection.toArray 复制到数组,该数组自 JDK 11 起可用。如果您还没有使用Java 11,则可以将第一行替换为传统调用:list1toArray

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

此解决方案不使用流,但它是声明性的,因为将累积操作作为参数接收(在本例中)。Arrays.parallelPrefixInteger::sum

时间的复杂度是,尽管在建立并行处理所需的基础设施时可能涉及一些非次要的恒定成本。但是,根据文档:O(N)

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

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

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


答案 2

对于每个索引:从零迭代到该索引,获取每个元素,并获取总
和 Box ints 以 s
收集到列表Integer

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

您每次都会将每个数字相加,而不是重用以前的累积结果,但流不适合查看以前迭代的结果。

你可以写你自己的收藏家,但在这一点上,老实说,为什么你甚至为流而烦恼?

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(); }
        )
    );

推荐