31

foldLeftScala在 Java 8中的伟大之处是什么?

我很想它是reduce,但 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 我的代码的正确等价物吗?

4

5 回答 5

32

foldLeftJava 8 的 Stream API 中没有等价物。正如其他人指出的那样,reduce(identity, accumulator, combiner)接近,但它不等同于,foldLeft因为它要求结果类型B与自身结合并具有关联性(换句话说,是类幺半群),并非每种类型都具有的属性。

对此还有一个增强请求:添加 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

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

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

于 2017-05-14T09:32:54.017 回答
18

更新:

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

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

它使用以下reduce 方法

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

如需更深入的解释,请查看此来源

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

正如下面的评论所指出的,一个正确的选项是使用如下的缩减:

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

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

于 2016-12-20T10:55:11.757 回答
7

您正在寻找的方法是java.util.Stream.reduce,特别是具有三个参数的重载,身份,累加器和二进制函数。这与 Scala 的foldLeft.

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

于 2016-12-20T11:07:27.203 回答
3

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

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();
}
于 2021-05-14T09:56:55.513 回答
0

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

<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();
}
于 2018-11-27T12:30:31.843 回答