您的问题是Java 流没有真正的list fold operation。使用真正的折叠操作,将函数编写为左折叠并不难。例如,在 Haskell 中:
import Data.List (foldl')
-- A custom struct to represent the state of the fold.
data MooreState a = MooreState { candidate :: a, count :: !Int }
findCandidate :: Eq a => [a] -> Maybe a
findCandidate (first:rest) = Just result
where
Moore result _ = foldl' combiner (MooreState first 1) rest
combiner :: Eq a => MooreState a -> a -> MooreState a
combiner (Moore candidate count) current
| count == 0 = MooreState current 1
| candidate == current = MooreState candidate (count + 1)
| otherwise = MooreState candidate (count - 1)
-- The empty list has no candidates.
findCandidate [] = Nothing
Java 的 reduce() 方法最接近真正的左折叠,但如果您查看 the Javadoc for the reduce() method that you're using,您会注意到它说:
- 它“不限于顺序执行”;
- 要求累加函数关联。
这个文档真的很难解释,但我阅读它的方式是这样的。即使它可能会乱序处理元素:
-
如果您的累积函数是关联的那么它保证产生与您按顺序处理流一样的结果;
- 如果您的累积函数不是关联函数,那么它可能会产生不同于简单顺序过程的结果。
为什么这很重要?嗯,首先,你正在改变一个外部变量的事实意味着你使用count 的方式被打破了。据你所知,流的元素 #7 可能在元素 #5 之前处理。
更隐蔽的是,上述 Haskell 版本中的 combine 操作结合了不同类型的输入(Moore a 和 a),但您使用的 Java reduce 方法基于 BinaryOperator<T>,它组合两个相同类型的对象。有another overload of reduce that uses a BiFunction<U, T, U>,但这需要您提供BinaryOperator<U> combiner 及其U identity。这是因为 Java 的 reduce 方法被设计成可以:
- 将输入流分成连续的块;
- 并行处理多个块;
- 在完成时按顺序组合相邻块的结果。
因此,关联性和身份要求是为了保证这种并行处理产生的结果与您按顺序执行的结果相同。但这意味着虽然该算法有一个简单的功能实现,但没有直接的方法可以使用 Java 的 Stream 类型来编写它。 (有一种不直截了当的方法,但这会涉及到一些魔法,这将(a)非常复杂,(b)在 Java 中非常慢。)
所以我个人会接受 Java 不是一种出色的函数式编程语言,不要管它足够好,并按原样使用命令式版本。但是如果出于某种奇怪的原因我真的坚持要在功能上这样做,我会选择像jOOλ 这样提供true left folds in Java 的库。然后你可以像 Haskell 解决方案一样做(未经测试的伪代码):
import org.jooq.lambda.Seq;
import org.jooq.lambda.tuple.Tuple2;
class MooreState<A> {
private final A candidate;
private final int count;
// ...constructors and getters...
}
public static Optional<A> findCandidate(Seq<A> elements) {
Tuple2<Optional<A>, Seq<A>> split = elements.splitAtHead();
return split.v1().map(first -> {
Seq<A> rest = split.v2();
return rest.foldLeft(
new MooreState<>(first, 1),
(state, current) -> {
if (state.getCount() == 0) {
return new MooreState<>(current, 1);
} else if (state.getCandidate().equals(current) {
return new MooreState<>(state.getCandidate(),
state.getCount() + 1);
} else {
return new MooreState<>(state.getCandidate(),
state.getCount() - 1);
}
}
);
});
}
...这可能非常慢。