Java 8:停止检查所有流元素的缩减操作

2022-09-02 21:33:32

我试图了解是否有一种方法可以在不检查整个流的情况下终止约简操作,并且我无法找到方法。

用例大致如下:让有一长串需要折叠成.每个元素检查都有潜在的昂贵,因此在 中,我对传入的元素执行检查,以查看我们是否需要执行昂贵的操作 - 如果我们不需要,那么我只需返回累加器。IntegerAccumulatorAccumulatorAccumulator

对于小型列表来说,这显然是一个很好的解决方案,但是大型列表会产生不必要的流元素访问成本,我想避免。

这是一个代码草图 - 假设只有串行减少。

class Accumulator {
    private final Set<A> setA = new HashSet<>;
    private final Set<B> setB = new HashSet<>;
}

class ResultSupplier implements Supplier<Result> {

    private final List<Integer> ids;

    @Override
    public Result get() {
        Accumulator acc = ids.stream().reduce(new Accumulator(), f(), (x, y) -> null);

        return (acc.setA.size > 1) ? Result.invalid() : Result.valid(acc.setB);
    }

    private static BiFunction<Accumulator, Integer, Accumulator> f() {
        return (acc, element) -> {
            if (acc.setA.size() <= 1) {
                // perform expensive ops and accumulate results
            }
            return acc;
        };
    }
}

除了必须遍历整个,还有另一个事实我不喜欢 - 我必须检查相同的条件两次(即大小检查)。StreamsetA

我已经考虑过和操作,但它们似乎更像是一样的,并没有发现它们在实质上改变了这样一个事实,即如果不检查整个流,我就无法完成折叠操作。map()collect()

此外,我的想法是,虚构的流API通讯员也不会给我们带来任何好处,因为终止条件取决于累加器,而不是流元素本身。takeWhile(p : (A) => boolean)

请记住,我是FP的相对新手,所以 - 有没有办法让它像我预期的那样工作?我是否不正确地设置了整个问题,或者这种限制是设计使然?


答案 1

而不是从你可以开始ids.stream()

  1. ids.spliterator()
  2. 将生成的拆分器包装到具有易失性布尔标志的自定义拆分器中
  3. 如果标志已更改,则自定义拆分器的返回值为 falsetryAdvance
  4. 将您的自定义拆分器转换为流StreamSupport.stream(Spliterator<T>, boolean)
  5. 像以前一样继续流管道
  6. 当累加器已满时,通过切换布尔值来关闭流

添加一些静态帮助程序方法以保持其功能。

生成的 API 可以查看此内容

Accumulator acc = terminateableStream(ids, (stream, terminator) ->
   stream.reduce(new Accumulator(terminator), f(), (x, y) -> null));

此外,我的想法是,imaginary takeWhile(p : (A) => boolean) Stream API correspondent 也不会给我们带来任何好处。

如果条件依赖于累加器状态而不是流成员,则它确实有效。这基本上就是我上面概述的方法。

在 JDK 提供的 a 中可能会禁止它,但是使用拆分器的自定义实现可以自由地采用有状态方法。takeWhile


答案 2

当然,会有一个有趣的,纯粹的FP答案,可能有助于以您预期的方式解决此问题。

同时,当简单的解决方案在实际要求上是必不可少的,并且您的原始数据源无论如何都是一个已经完全实现时,为什么要使用FP,并且您将使用串行减少,而不是并行减少。改为写这个:List

@Override
public Result get() {
    Accumulator acc = new Accumulator();

    for (Integer id : ids) {
        if (acc.setA.size() <= 1) {
            // perform expensive ops and accumulate results
        }

        // Easy:
        if (enough)
            break;
    }

    return (acc.setA.size > 1) ? Result.invalid() : Result.valid(acc.setB);
}