【问题标题】:Map to a running sum in Java 8映射到 Java 8 中的运行总和
【发布时间】:2019-08-09 08:18:59
【问题描述】:

如果我有收藏:

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

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

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

更好的是,我怎样才能建立一个东西的列表(比如一个元组),这样我就可以保留原来的东西:

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

【问题讨论】:

    标签: java java-8 java-stream


    【解决方案1】:

    您不需要 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());
    }
    

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

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

    【讨论】:

    • 第一个答案似乎不起作用:它会错误地将运行总数计算为 2、4、6、9,而答案应该是 2、4、8、13(使用来自问题)-啊,那是在编辑之前。现在工作:)
    【解决方案2】:

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

    您可以改用 Java Stream.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);
            });
    

    这会组合所有数字,并为每个数字加上前一个总和创建一对。它使用以下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;
        }
    }
    

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

    最后的结果是:

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

    【讨论】:

    • 易于阅读的答案,让我想起了 foldLeft。即使没有 Pair 类型,它也能满足我的需求,并使其灵活,我可以轻松地将 + 运算符换成其他运算符(这实际上是我想要做的)
    • 这在几个方面违反了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());
    • @Holger 感谢您指出这一点,我使用Stream.collect() 更新了我的答案。
    • collect 更适合这项工作,但 ArrayList::addAll 仍然不是适合该操作的组合器。你需要像(list1, list2) -&gt; { if(!list1.isEmpty()) { long sum = list1.get(list1.size()-1).getSum(); list2.replaceAll(p -&gt; new Pair(p.getNumber(), p.getSum()+sum)); } list1.addAll(list2); }这样的东西
    • reduce 中使用的对象不需要严格不可变,但在归约函数中必须以这种方式使用。第一个参数(ArrayList)应该是一个标识值,但是在添加元素之后,它就不能发挥这个作用了。在这种情况下,正确的解决方案需要创建新对象,这将是相当昂贵的。所以collect 解决了这个问题,因为它是专门为此类场景设计的。
    【解决方案3】:

    您可以使用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(", ", "[", "]")));
    

    输出:

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

    【讨论】:

      【解决方案4】:

      一个简单的演示 -

      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】:

        我刚刚使用了Java 8的每个特性。我初始化了Long类型的输入List。我创建了一个临时 ArrayList(runningSum),它只存储运行总和,其值初始化为 0.(在索引 1 处)。 ValuePair 为该位置创建 number 及其 runningSum,并将其存储在结果 (List) 中并显示。希望这会有所帮助

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

        输出:

        【讨论】:

          猜你喜欢
          • 2018-12-22
          • 1970-01-01
          • 2019-10-03
          • 1970-01-01
          • 2015-09-01
          • 1970-01-01
          • 2019-01-21
          • 1970-01-01
          • 2016-02-12
          相关资源
          最近更新 更多