是否可以根据自己计算流中的值

Is it possible to calculate values in a stream based on themselves

如果我有一个 java 流,例如数字,是否可以计算例如该数字的总和(通过将数字添加到“先前”计算的总和)?

例如(1, 2, 3, 5, 7, 9, 0) --> (1, 3, 6, 11, 18, 27, 27)

是的,您可以使用自定义收集器来做到这一点。尽管“流”部分将无事可做。

它看起来像 list.stream().collect( new CustomSequentialCollector() )

您必须对 java 流中的数组求和使用 parallelPrefix

Integer[] arr = {1, 2, 3, 5, 7, 9, 0};
Arrays.parallelPrefix(arr, (x, y) -> x + y);
System.out.println(Arrays.toString(arr));

您必须对 ArrayList

使用 AtomicInteger
List<Integer> list = new ArrayList<>();
list.addAll(Arrays.asList(1, 2, 3, 5, 7, 9, 0));
        
AtomicInteger ai = new AtomicInteger();
List<Integer> sumOfList = list.stream()
                              .map(ai::addAndGet)
                              .collect(Collectors.toList());
System.out.println(sumOfList);

这是基于 this answer 的概念证明。它实现了一个完全无状态的解决方案,时间复杂度为 O(n),也适用于并行流。

    record CumSum(Integer acc, List<Integer> sums) {
        public CumSum() {
            this(0, List.of());
        }

        public CumSum collect(Integer n) {
            return new CumSum(acc + n, concat(sums, Stream.of(acc + n)));
        }

        public CumSum combine(CumSum cumSum) {
            return new CumSum(acc + cumSum.acc, concat(sums, cumSum.sums.stream().map(n -> acc + n)));
        }

        private static List<Integer> concat(List<Integer> sums, Stream<Integer> acc) {
            return Stream.concat(sums.stream(), acc).toList();
        }
    }

    var list1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    var list2 = list1.stream().parallel()
        .reduce(new CumSum(), CumSum::collect, CumSum::combine)
        .sums;

    System.out.println(list2);

输出为

[1, 3, 6, 10, 15, 21, 28, 36, 45, 55]

无论是否使用并行流。

请注意,由于 Java 中缺少真正的不可变列表,CumSumListStream 来回跳转,这不必要地使此实现复杂化. acc 中的单独跟踪也可以通过查看 sums 中的最后一个元素来替换。为了更清楚起见,我选择不这样做。

使用 Streams 的一个简单解决方案是使用 reducing。不过,此解决方案不是无状态的

var inputList = List.of(1, 2, 3, 5, 7, 9, 0);
var result = new ArrayList<>();

var unused = inputList.stream().collect(reducing(0, (a, b) -> {
    result.add(a + b);
    return a + b;
}));

System.out.println(result); // [1, 3, 6, 11, 18, 27, 27]

几点:

  • 使用流时应始终避免突变。流并不是要取代命令式代码。
  • 以上解决方案没有并行空间
  • Streams API 在 Java 中引入了函数式编程(做什么)范例。它不应该与命令式(操作)代码混合。
  • 中的parallelPrefix方案应该是首选方案