Java 8中Scala的foldLeft的等价物

GA1*_*GA1 22 java reduce java-8 foldleft

Scala foldLeft在Java 8中的优势是什么?

我很想想它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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

因此,任何人都可以指出我正确的foldLeft/修复我的代码?

dzs*_*dzs 18

foldLeft在Java 8的Stream API中没有相应的东西.正如其他人所指出的那样,reduce(identity, accumulator, combiner)它是接近的,但它并不等同,foldLeft因为它要求结果类型B与自身结合并且是关联的(换句话说,类似于monoid),这是一种并非每种类型都具有的属性.

还有一个增强请求:添加Stream.foldLeft()终端操作

要了解为什么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
Run Code Online (Sandbox Code Playgroud)

如果你尝试写作reduce(2, fun, combine),你可以通过哪种组合函数组合两个数字?将两个数字加在一起显然无法解决问题.此外,该值2显然不是一个标识元素.

请注意,不需要按顺序表示需要顺序执行的操作reduce.foldLeft实际上是更通用比reduce:你可以实现reducefoldLeft,但你无法实现foldLeftreduce.


Lac*_*lev 13

更新:

以下是修复代码的初步尝试:

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

它使用以下reduce方法:

<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);
Run Code Online (Sandbox Code Playgroud)

这可能听起来令人困惑,但如果你看一下javadocs,有一个很好的解释可以帮助你快速掌握细节.减少量相当于以下代码:

U result = identity;
for (T element : this stream)
     result = accumulator.apply(result, element)
return result;
Run Code Online (Sandbox Code Playgroud)

有关更深入的说明,请查看此来源.

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

正如下面的评论所指出的,正确的选择是使用如下减少:

return chars.stream().collect(
     StringBuilder::new, 
     StringBuilder::append, 
     StringBuilder::append).toString();
Run Code Online (Sandbox Code Playgroud)

供应商StringBuilder::new将用于创建可重复使用的容器,稍后将合并.

  • 与其他答案相同:*不要*以这种方式使用`reduce`.这些函数不允许修改它们的参数.正确的用法是`.collect(StringBuilder :: new,StringBuilder :: append,StringBuilder :: append)`.参见[可变减少](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#MutableReduction). (10认同)
  • 这不是关于效率,而是关于正确性.以这种方式使用`reduce`会违反合同,并且必须被视为破坏,即使它在某些情况下可以执行预期的操作.最值得注意的是,当使用并行流时,它肯定会中断. (3认同)
  • 这是关于`StringBuilder`的修改.订单没有问题. (2认同)

Jör*_*tag 6

您正在寻找的方法java.util.Stream.reduce,特别是具有三个参数的重载,标识,累加器和二进制函数.这是Scala的正确等价物foldLeft.

但是,你是不是允许使用Java的reduce这种方式,也没有Scala的foldLeft对这一问题.请collect改用.

  • @SeanPatrickFloyd:除了它不是*只是一个反模式.方法文档根本不允许这样做.反模式可能会也可能不会导致难以维护代码.OP的代码简直就是破解了.过时了.不行. (6认同)
  • 这种反应是完全错误的.Stream的`reduce(identity,accumulator,combiner)`需要一个*associative*combiner函数,这不是`foldLeft`的要求,因此,不是每个`foldLeft`结构都可以重写为`reduce`.采用以下示例,其中减法和除法不是关联的:```val ops = 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} ops.foldLeft(2)(fun)// ((2 + 1)*4 - 2)/ 5``` (5认同)
  • While I like your answer, "you are not allowed" seems a bit wrong. Can you rephrase that? (4认同)
  • 如果Java的类型系统表达足以表达该约束,那将是一种类型错误.但事实并非如此,约束只在JavaDocs中提到过.JavaDocs说你允许传递哪种对象,OP传递的对象不满足这些约束,因此不允许她调用`reduce`.你怎么说这个呢? (2认同)
  • 好吧,对类型没有限制,只限于你如何使用这些对象.如果使用累加器和组合函数,如`(a,b) - > new StringBuilder().append(a).append(b)`,与`collect`相比,它将是合法的用法,虽然不是很有效.解. (2认同)

yi-*_*-ji 6

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

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)
    );
}
Run Code Online (Sandbox Code Playgroud)

使用示例:

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

对于你的问题,这符合你的要求:

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