【问题标题】:How to calculate differences in a list of integers using lambda expressions如何使用 lambda 表达式计算整数列表中的差异
【发布时间】:2015-06-07 19:21:46
【问题描述】:

假设我有以下数组:{1,2,3,4,6,7,8},它被放入 Stream<Integer> s = Stream.of(1,2,3,4,6,7,8);

如何在 Java lambda 表达式和 Stream 函数中使用来计算每个元素与下一个元素之间的差异(在本例中为 {1,1,1,2,1,1})? 这并不是真正的 reduce 操作,因为 reduce 将整个列表转换为 1 个元素;它也不是地图操作,因为它需要两个元素来计算差异,而不仅仅是一个。

【问题讨论】:

  • 输入确实是一个数组,还是您已经得到一个IntStream,您必须使用它?如果您将值作为数组获取,Ismail's answer 对我来说似乎是合理的。
  • 对于好奇的人来说,这似乎使用 reduce: stackoverflow.com/questions/46350402 是可行的

标签: java lambda java-8 java-stream


【解决方案1】:

您可以循环遍历索引而不是元素,例如

int s[] = {1, 2, 3, 4, 6, 7, 8};
IntStream differences = 
    IntStream.range(0, s.length - 1)
        .map(i -> s[i + 1] - s[i]);

【讨论】:

  • OP 要求 s 成为一个流(我认为使用 toArray() 不是要走的路)。
  • 你创建一个数组,然后是一个流,只需使用流来迭代数组。对我来说它看起来是多余的。
【解决方案2】:

另一个答案似乎已经被接受,但有一个想法,所以我还是发布了它。您可以创建一个Collector,将其直接收集到另一个Stream<Integer>

那么你就可以这样写:

s.collect(intDifferences()).forEach(d -> System.out.print(d + ","));

这是我写的一个实现:

public static Collector<Integer, List<Integer>, Stream<Integer>> intDifferences() {

    return new Collector<Integer, List<Integer>, Stream<Integer>>() {

        @Override
        public BiConsumer<List<Integer>, Integer> accumulator() {
            return List::add;
        }

        @Override
        public Set<Collector.Characteristics> characteristics() {
            return EnumSet.noneOf(Collector.Characteristics.class);
        }

        @Override
        public BinaryOperator<List<Integer>> combiner() {
            return (left, right) -> {
                left.addAll(right);
                return left;
            };
        }

        @Override
        public Function<List<Integer>, Stream<Integer>> finisher() {
            return list -> {
                List<Integer> differences = new ArrayList<>();
                for (int i = 1; i < list.size(); i++) {
                    differences.add(list.get(i) - list.get(i - 1));
                }
                return differences.stream();
            };
        }

        @Override
        public Supplier<List<Integer>> supplier() {
            return ArrayList::new;
        }
    };
}

【讨论】:

    【解决方案3】:

    您可以使用我的 StreamEx 库,它有一个专门针对这种情况的 pairMap 方法:

    StreamEx<Integer> s = StreamEx.of(1,2,3,4,6,7,8);
    s = s.pairMap((a, b) -> b-a);
    s.forEach(System.out::println);
    

    StreamEx 类实现了Stream 并与它完全兼容。同样的事情也适用于原始流:

    IntStreamEx s = IntStreamEx.of(1,2,3,4,6,7,8);
    s = s.pairMap((a, b) -> b-a);
    s.forEach(System.out::println);
    

    【讨论】:

      【解决方案4】:

      如果您允许映射函数有副作用,则可以使用map(),即您可以在流中存储对前一个对象的引用:

      Stream.of(1, 2, 3, 4, 6, 7, 8)
          .map(new Function<Integer, Optional<Integer>>() {
              Optional<Integer> previousValue = Optional.empty();
              @Override
              public Optional<Integer> apply(Integer current) {
                  Optional<Integer> value = previousValue.map(previous -> current - previous);
                  previousValue = Optional.of(current);
                  return value;
              }
          })
          .filter(Optional::isPresent)
          .map(Optional::get)
          .forEach(System.out::println);
      

      注意,这个实现不是pure function,所以你必须小心它的使用方式。上面的示例有效,因为每次都会实例化该函数(特别是,每次使用都会重置 previousValue)。但是,如果重构代码以将函数存储在局部变量中,它将不起作用,因为第二次使用该函数时,previousValue 不会为空。因此,通常不鼓励这种实现方式。

      【讨论】:

      • 该函数只被实例化一次。毕竟是有状态的
      【解决方案5】:
      public int[] diff(int... input) {
          AtomicInteger aInt = new AtomicInteger();
          return IntStream.of(input).map(v -> v - aInt.getAndSet(v)).skip(1).toArray();
      }
      

      不是纯函数,因为它改变了 AtomicInteger 的状态

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-04-11
        • 1970-01-01
        • 2011-09-23
        • 1970-01-01
        • 2016-07-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多