映射到Java 8中的运行总和

Tob*_*oby 7 java java-8 java-stream

如果我有收藏集:

List<Long> numbers = asList(2, 2, 4, 5);
Run Code Online (Sandbox Code Playgroud)

我该如何映射/处理这些以建立运行总计。产生类似:

List<Long> runningTotals = asList(2, 4, 8, 13);
Run Code Online (Sandbox Code Playgroud)

更好的是,我该如何构建某物(例如元组)的列表,以便保留原始物:

((2 -> 2), (2 -> 4), (4 -> 8), (5 -> 13));
Run Code Online (Sandbox Code Playgroud)

And*_*ner 10

您不需要Java 8就能做到这一点。确实,这个问题并不适合流,因为计算是有状态的,因为它取决于先前元素的总和,因此您无法从并行化等方面获得好处。

您也可以只使用一个普通的旧循环:

ListIterator<Long> it = list.listIterator();
Long previous = it.next();  // Assuming the list isn't empty.
while (it.hasNext()) {
  it.set(previous += it.next());
}
Run Code Online (Sandbox Code Playgroud)

关于第二个要求,您是否真的需要将原始元素保留在元组中?您可以简单地从其前身减去运行总计列表中的每个元素以恢复原始元素:

AbstractList<Long> something = new AbstractList<Long>() {
  @Override public int size() { return list.size(); }
  @Override public Long get(int i) {
    if (i > 0) {
      return list.get(i) - list.get(i - 1);
    } else {
      return list.get(i);
    }
  }
};
Run Code Online (Sandbox Code Playgroud)


Sam*_*ipp 5

更新:正如 Holger 在Stream.reduce()用于此目的的评论中指出的那样是不正确的。有关更多信息,请参阅ReductionMutable ReductionJava 8 Streams - collect vs reduce

您可以使用 JavaStream.collect()来生成带有总和的列表:

List<Long> numbers = Arrays.asList(2L, 2L, 4L, 5L);
List<Pair> results = numbers.stream()
        .collect(ArrayList::new, (sums, number) -> {
            if (sums.isEmpty()) {
                sums.add(new Pair(number, number));
            } else {
                sums.add(new Pair(number, number + sums.get(sums.size() - 1).getSum()));
            }
        }, (sums1, sums2) -> {
            if (!sums1.isEmpty()) {
                long sum = sums1.get(sums1.size() - 1).getSum();
                sums2.forEach(p -> p.setSum(p.getSum() + sum));
            }
            sums1.addAll(sums2);
        });
Run Code Online (Sandbox Code Playgroud)

这将组合所有数字,并为每个数字创建一对,并加上前一个总和。它使用以下Pair类作为助手:

public class Pair {
    private long number;
    private long sum;

    public Pair(long number, long sum) {
        this.number = number;
        this.sum = sum;
    }

    public long getNumber() {
        return number;
    }

    public void setSum(long sum) {
        this.sum = sum;
    }

    public long getSum() {
        return sum;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您想添加更多信息,您可以轻松更改该帮助程序类。

最后的结果是:

[
    Pair{number=2, sum=2}, 
    Pair{number=2, sum=4}, 
    Pair{number=4, sum=8}, 
    Pair{number=5, sum=13}
]
Run Code Online (Sandbox Code Playgroud)

  • 这在几个方面违反了 `reduce` 的约定(reduce *不是 * foldLeft)。当你删除所有错误的东西时,你会得到一个简单的循环:`List&lt;Pair&gt; results=new ArrayList&lt;&gt;(); for(Long number: numbers) results.add(new Pair(number, sums.isEmpty()? number: number + sums.get(sums.size() - 1).getSum());` (2认同)
  • `reduce` 中使用的对象不需要严格不可变,但它们必须在reduce 函数中以这种方式使用。第一个参数(`ArrayList`)应该是一个标识值,但在添加元素后,它不履行这个角色。在这种情况下,正确的解决方案需要创建新对象,这将是相当昂贵的。所以 `collect` 解决了这个问题,因为它是专门为此类场景设计的。 (2认同)

Jac*_* G. 5

您可以Arrays#parallelPrefix用来达成目标:

List<Long> numbers = Arrays.asList(2L, 2L, 4L, 5L);
long[] copiedArray = numbers.stream().mapToLong(Long::longValue).toArray();

Arrays.parallelPrefix(copiedArray, Long::sum);

System.out.println(IntStream.range(0, numbers.size())
        .mapToObj(i -> "(" + numbers.get(i) + " -> " + copiedArray[i] + ")")
        .collect(Collectors.joining(", ", "[", "]")));
Run Code Online (Sandbox Code Playgroud)

输出:

[(2 -> 2), (2 -> 4), (4 -> 8), (5 -> 13)]
Run Code Online (Sandbox Code Playgroud)