【问题标题】:Equivalent of Scala's foldLeft in Java 8等效于 Java 8 中的 Scala 的 foldLeft
【发布时间】:2017-05-05 13:21:59
【问题描述】:

什么相当于 Scala 在 Java 8 中的 foldLeft

我很想它是reduce,但reduce 必须返回与它所减少的相同类型的东西。

例子:

import java.util.List;

public class Foo {

    // this method works pretty well
    public int sum(List<Integer> numbers) {
        return numbers.stream()
                      .reduce(0, (acc, n) -> (acc + n));
    }

    // this method makes the file not compile
    public String concatenate(List<Character> chars) {
        return chars.stream()
                    .reduce(new StringBuilder(""), (acc, c) -> acc.append(c)).toString();
    }
}

上面代码中的问题是accumulator:new StringBuilder("")

因此,谁能指出与foldLeft/fix my code 的正确等效项?

【问题讨论】:

  • 仅供参考:语言的名称是“Scala”,而不是“SCALA”。 (我相信有一种不同的语言叫做“SCALA”,可能不是你的意思。)
  • @JörgWMittag 除非您有来源可以找到具有相同名称但大写的不同语言,否则我会感到非常惊讶。我认为大写的拼写来自习惯于大写语言的老经理,比如 BASIC 和 FORTRAN :D
  • @nafg:我尝试用谷歌搜索它,但这有点困难,因为谷歌搜索“SCALA”也会返回“Scala”的结果。我相信,我是在我们今天所说的 IBM 中端系统上的“大数据分析”的背景下看到的,但在“大数据”(或 Scala)出现之前。但是,我个人从未在 IBM 中端系统上工作过,所以我不记得相关工具、框架、库或语言的名称,以执行更好的 google 查询。 Scala 用于大数据,IBM 大力推动 Scala 的事实也无济于事。

标签: java java-8 reduce foldleft


【解决方案1】:

更新:

这是修复代码的初步尝试:

public static String concatenate(List<Character> chars) {
        return chars
                .stream()
                .reduce(new StringBuilder(),
                                StringBuilder::append,
                                StringBuilder::append).toString();
    }

它使用以下reduce method

<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);

这听起来可能令人困惑,但如果您查看 javadocs 有一个很好的解释,可以帮助您快速掌握细节。归约等价于以下代码:

U result = identity;
for (T element : this stream)
     result = accumulator.apply(result, element)
return result;

如需更深入的解释,请查看this source

这种用法是不正确的,因为它违反了 reduce 的约定,该约定规定累加器应该是一个关联的、非干扰的、无状态的函数,用于将附加元素合并到结果中。换句话说,由于身份是可变的,因此在并行执行的情况下结果将被破坏。

正如下面的 cmets 中所指出的,正确的选项是使用以下缩减:

return chars.stream().collect(
     StringBuilder::new, 
     StringBuilder::append, 
     StringBuilder::append).toString();

供应商StringBuilder::new 将用于创建可重复使用的容器,这些容器将在以后组合。

【讨论】:

  • 与其他答案相同:不要以这种方式使用reduce。函数不允许修改它们的参数。正确的用法是.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)。见Mutable reduction
  • @Holger:谢谢,这是真的。答案已更新。
  • 这不是关于效率,而是关于正确性。以这种方式使用reduce 违反了合同,必须被视为违反合同,即使在某些情况下它可能会做预期的事情。最值得注意的是,当使用并行流时,它肯定会中断。​​
  • 你解决连接字符的顺序还是构建器的状态?
  • 是关于StringBuilder的修改。订单没有问题。
【解决方案2】:

您要查找的方法是java.util.Stream.reduce,尤其是具有三个参数的重载,标识、累加器和二进制函数。这与 Scala 的 foldLeft 正确等价。

但是,不允许以这种方式使用 Java 的 reduce,也不允许使用 Scala 的 foldLeft。请改用collect

【讨论】:

  • 虽然我喜欢你的回答,但“你不被允许”似乎有点不对劲。你能改写一下吗?
  • 如果 Java 的类型系统的表达能力足以表达该约束,那将是一个类型错误。但事实并非如此,该约束仅在 JavaDocs 中提及。 JavaDocs 说明允许您传递哪些类型的对象,而 OP 传递的对象不满足这些约束,因此不允许她调用reduce。否则你会怎么说?
  • 嗯,类型没有限制,只有你如何使用对象。如果您使用像 (a,b) -&gt; new StringBuilder().append(a).append(b) 这样的累加器和合并器函数,与 collect 解决方案相比,这将是一种合法的用法,尽管效率不高。
  • @SeanPatrickFloyd:除了它只是一个反模式。方法文档根本不允许这样做。反模式可能会也可能不会导致代码难以维护。 OP的代码被破坏了。卡普特。没用。
  • 这个响应是完全错误的。 Stream 的reduce(identity, accumulator, combiner) 需要一个关联 组合器函数,这不是foldLeft 的要求,因此,并非每个foldLeft 构造都可以重写为reduce。举个例子,减法和除法不相关:val ops = List(('+', 1), ('*', 4), ('-', 2), ('/', 5)) val fun: (Int, (Char, Int)) =&gt; Int = { case (x, ('+', y)) =&gt; x + y case (x, ('-', y)) =&gt; x - y case (x, ('*', y)) =&gt; x * y case (x, ('/', y)) =&gt; x / y } ops.foldLeft(2)(fun) // ((2 + 1) * 4 - 2) / 5
【解决方案3】:

Java 8 的 Stream API 中没有 foldLeft 的等价物。正如其他人所指出的,reduce(identity, accumulator, combiner) 很接近,但它与 foldLeft 不等价,因为它需要生成的类型 B 与自身结合并具有关联性(换句话说,类似于幺半群)。类型有。

对此还有一个增强请求:add Stream.foldLeft() terminal operation

要了解为什么 reduce 不起作用,请考虑以下代码,您打算在其中执行从给定数字开始的一系列算术运算:

val arithOps = List(('+', 1), ('*', 4), ('-', 2), ('/', 5))
val fun: (Int, (Char, Int)) => Int = {
  case (x, ('+', y)) => x + y
  case (x, ('-', y)) => x - y
  case (x, ('*', y)) => x * y
  case (x, ('/', y)) => x / y
}
val number = 2
arithOps.foldLeft(number)(fun) // ((2 + 1) * 4 - 2) / 5

如果您尝试编写reduce(2, fun, combine),您可以传递什么组合器函数来组合两个数字?将这两个数字加在一起显然不能解决问题。此外,2 的值显然不是 identity 元素。

请注意,任何需要顺序执行的操作都不能用reduce 表示。 foldLeft 实际上比reduce 更通用:你可以用foldLeft 实现reduce,但你不能用reduce 实现foldLeft

【讨论】:

    【解决方案4】:

    其他都是正确的,但没有等价的。这是一个接近的实用程序-

    <U, T> U foldLeft(Collection<T> sequence, U identity, BiFunction<U, ? super T, U> accumulator) {
        U result = identity;
        for (T element : sequence)
            result = accumulator.apply(result, element);
        return result;
    }
    

    您使用上述方法的情况如下-

    public String concatenate(List<Character> chars) {
        return foldLeft(chars, new StringBuilder(""), StringBuilder::append).toString();
    }
    

    或者没有 lambda 方法参考糖,

    public String concatenate(List<Character> chars) {
        return foldLeft(chars, new StringBuilder(""), (stringBuilder, character) -> stringBuilder.append(character)).toString();
    }
    

    【讨论】:

      【解决方案5】:

      这可以通过使用收集器来完成:

      public static <A, B> Collector<A, ?, B> foldLeft(final B init, final BiFunction<? super B, ? super A, ? extends B> f) {
          return Collectors.collectingAndThen(
                  Collectors.reducing(Function.<B>identity(), a -> b -> f.apply(b, a), Function::andThen),
                  endo -> endo.apply(init)
          );
      }
      

      使用示例:

      IntStream.rangeClosed(1, 100).boxed().collect(foldLeft(50, (a, b) -> a - b));  // Output = -5000
      

      对于你的问题,这就是你想要的:

      public String concatenate(List<Character> chars) {
              return chars.stream()
                      .collect(foldLeft(new StringBuilder(), StringBuilder::append)).toString();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-12
        • 1970-01-01
        • 1970-01-01
        • 2016-01-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多