Map to a running sum in Java 8

醉酒当歌 提交于 2019-12-22 04:43:15

问题


If I have a collection:

List<Long> numbers = asList(2, 2, 4, 5);

How can I map/process these to build up a running total. To produce something like:

List<Long> runningTotals = asList(2, 4, 8, 13);

Even better, how can I build a list of something (like a tuple) so I can preserve the orignals:

((2 -> 2), (2 -> 4), (4 -> 8), (5 -> 13));

回答1:


Update: As Holger pointed out in the comments using Stream.reduce() for this purpose is not correct. See Reduction and Mutable Reduction or Java 8 Streams - collect vs reduce for more information.

You can use Java Stream.collect() instead to generate your list with sums:

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

This combines all the numbers and creates a pair for each number with the addition to the previous sum. It uses the following Pair class as helper:

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

You can easily change that helper class if you want to add some more information.

The result at the end is:

[
    Pair{number=2, sum=2}, 
    Pair{number=2, sum=4}, 
    Pair{number=4, sum=8}, 
    Pair{number=5, sum=13}
]



回答2:


You don't need Java 8 to do this. Indeed, this problem doesn't lend itself well to streams, because the calculation is stateful, in that it depends upon the sum of previous elements, so you don't get an advantage from things like parallelization.

You may as well just use a plain old loop:

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

In terms of the second requirement, do you really need to preserve the original elements in tuples? You can simply subtract each element in the running total list from its predecessor to recover the original:

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



回答3:


You can use Arrays#parallelPrefix to accomplish your goal:

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(", ", "[", "]")));

Output:

[(2 -> 2), (2 -> 4), (4 -> 8), (5 -> 13)]



回答4:


A simple demonstration -

import java.util.Arrays;
import java.util.List;

public class RunningAdditionDemo{
     private static Integer total = 0;
     private String strOutput = new String();

     public static void main(String []args){
        RunningAdditionDemo demo = new RunningAdditionDemo();
        String output = demo.doRunningAddition();

        System.out.println(output);
     }

     public String doRunningAddition() {
        List<Integer> numbers = Arrays.asList(2, 2, 4, 5);
        numbers.stream().forEach(this::addNumber);

        return String.format("( %s )", strOutput.replaceFirst("..$",""));
     }

     private void addNumber(Integer number) {
        total += number;
        strOutput += String.format("( %d -> %d ), ", number, total);
     }
}



回答5:


I just used forEach feature of Java 8. I initialized the input List of Long type. I created a temp ArrayList (runningSum) that just store running sums whose value initialized to 0.(at index 1). ValuePair creates number and its runningSum for that position and it is stored in result (List) and displayed. Hope this helps

package net.javapedia;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class ValuePairs {
    public ValuePairs(Long n, Long s) {
        number = n;
        sum = s;
    }

    Long number;
    Long sum;

    @Override
    public String toString() {

        return new StringBuilder("(").append(this.number).append(" -> ").append(this.sum).append(")").toString();
    }
}

public class RunningSum {

    public static void main(String[] args) {

        List<Long> numbers = Arrays.asList(2L, 2L, 4L, 5L);
        List<Long> tempRunningSum = new ArrayList<>();
        List<ValuePairs> result = new ArrayList<>();
        tempRunningSum.add(0L);
        numbers.stream().forEach(i -> {
            tempRunningSum.set(0, tempRunningSum.get(0) + i);
            result.add(new ValuePairs(i, tempRunningSum.get(0)));
        });

        System.out.println(result);
    }

}

Output:



来源:https://stackoverflow.com/questions/55230261/map-to-a-running-sum-in-java-8

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!