使用Java 8流API的累积总和

时间:2019-03-20 16:32:29

标签: 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));
}

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

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

5 个答案:

答案 0 :(得分:10)

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

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

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

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

这首先使用Collection.toArraylist1复制到数组,该数组自JDK 11开始可用。如果您还没有使用Java 11,则可以用传统的{{1 }}通话:

toArray

此解决方案不使用流,而是声明式,因为Integer[] arr = list1.toArray(new Integer[0]); 接受累积操作作为参数(在这种情况下为Arrays.parallelPrefix)。

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

  

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

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

此外,值得一提的是,这种方法行之有效,因为O(N)关联操作。这是必需的。

答案 1 :(得分:5)

对于每个索引:从零迭代到该索引,获取每个元素并获取总和
将整数装到Integer s
收集到列表

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

答案 2 :(得分:3)

您可以使用sublist进行汇总,直到从开始算起当前索引为止:

List<Integer> list = IntStream.range(0, list1.size())
        .mapToObj(i -> list1.subList(0, i + 1).stream().mapToInt(Integer::intValue).sum())
        .collect(Collectors.toList());

答案 3 :(得分:2)

下面是一个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]

答案 4 :(得分:1)

您可以仅使用Stream.collect()

List<Integer> list1 = Arrays.asList(1, 2, 3, 4);
List<Integer> list2 = list1.stream()
        .collect(ArrayList::new, (sums, number) -> {
            if (sums.isEmpty()) {
                sums.add(number);
            } else {
                sums.add(sums.get(sums.size() - 1) + number);
            }
        }, (sums1, sums2) -> {
            if (!sums1.isEmpty()) {
                int sum = sums1.get(sums1.size() - 1);
                sums2.replaceAll(num -> sum + num);
            }
            sums1.addAll(sums2);
        });

此解决方案也适用于并行流。使用list1.parallelStream()list1.stream().parallel()代替list1.stream()

两种情况下的结果均为: [1, 3, 6, 10]