Scala dropWhile的等价实现

9
我在寻找一种方法,根据谓词跳过流的一些元素,但我遇到了困难。
类似于这样的代码:
dropWhile( n -> n < 3, Stream.of( 0, 1, 2, 3, 0, 1, 2, 3, 4 ) )
.forEach( System.out::println );

3   
0
1
2
3
4

那相当于 Scala 中的 dropWhile

相关:通过谓词限制流 - charlie
2个回答

20

这种操作并不是 Stream 的预期使用场景,因为它涉及到元素之间的依赖关系。因此,解决方案可能看起来不太优雅,因为您需要为谓词引入一个有状态的变量:

class MutableBoolean { boolean b; }
MutableBoolean inTail = new MutableBoolean();

IntStream.of(0, 1, 2, 3, 0, 1, 2, 3, 4)
         .filter(i -> inTail.b || i >= 3 && (inTail.b = true))
         .forEach(System.out::println);

请注意,与您的示例相比,条件必须被反转。
当然,您可以将令人讨厌的细节隐藏在一个方法中:
public static void main(String... arg) {
    dropWhile(n -> n < 3, Stream.of(0, 1, 2, 3, 0, 1, 2, 3, 4))
      .forEach(System.out::println);
}
static <T> Stream<T> dropWhile(Predicate<T> p, Stream<T> s) {
    class MutableBoolean { boolean b; }
    MutableBoolean inTail = new MutableBoolean();
    return s.filter(i -> inTail.b || !p.test(i) && (inTail.b = true));
}

一种更复杂、但更清晰且可能更高效的方法是深入到底层,即使用 Spliterator 接口:

static <T> Stream<T> dropWhile(Predicate<T> p, Stream<T> s) {
    Spliterator<T> sp = s.spliterator();
    return StreamSupport.stream(new Spliterators.AbstractSpliterator<T>(
            sp.estimateSize(), sp.characteristics() & ~Spliterator.SIZED) {
        boolean dropped;
        public boolean tryAdvance(Consumer<? super T> action) {
            if(dropped) return sp.tryAdvance(action);
            do {} while(!dropped && sp.tryAdvance(t -> {
                if(!p.test(t)) {
                    dropped=true;
                    action.accept(t);
                }
            }));
            return dropped;
        }
        public void forEachRemaining(Consumer<? super T> action) {
            while(!dropped) if(!tryAdvance(action)) return;
            sp.forEachRemaining(action);
        }
    }, s.isParallel());
}

这种方法可以像第一个dropWhile方法一样使用,但是它即使在并行流情况下也可以工作,尽管不像您希望的那样高效。


有点晚了,但是你考虑过使用AtomicBoolean而不是MutableBoolean类吗? - Henrik Aasted Sørensen
2
@Henrik:使用AtomicBoolean会假装这个操作是线程安全的,但实际上谓词评估的顺序是未定义的。此外,您可能会浪费性能,因为对于无法并行工作的操作,使用线程安全构造是没有意义的。另一方面,局部使用的MutableBoolean清楚地显示了您可以期望什么。 - Holger
很好的解释!谢谢。 - Henrik Aasted Sørensen
@Holger 我发现 boolean[] flag = { false }; 可以用作一个合理的非线程安全可变布尔值。 - Peter Lawrey
2
@PeterLawrey 创建一个专用类或使用数组,主要是编码风格的问题。 - Holger

11

很遗憾,使用Java 8唯一的方法是使用Holger提供的解决方案。

然而,操作dropWhile(predicate)已经被添加到Java 9中,因此从JDK 9开始,您可以简单地执行以下操作:

Stream.of(0, 1, 2, 3, 0, 1, 2, 3, 4).dropWhile(n -> n < 3).forEach(System.out::println);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接