Equivalent of Scala dropWhile

后端 未结 2 1763
佛祖请我去吃肉
佛祖请我去吃肉 2020-11-28 16:21

I\'m struggling to find a way to skip some elements at the beginning of a stream depending on a predicate.

Something like this:

dropWhile( n -> n          


        
相关标签:
2条回答
  • 2020-11-28 16:56

    This kind of operation is not an intended use case for Streams as it incorporates a dependency between the elements. Therefore the solution might not look elegant as you have to introduce a state-full variable for your predicate:

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

    Note that the condition had to be reversed compared to your example.

    Of course, you can hide the nasty details in a method:

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

    A more complex, but cleaner and potentially more efficient way is to go down to the metal, i.e the Spliterator interface:

    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());
    }
    

    this method can be used the same way as the first dropWhile method, but it will work even with parallel streams, though not as efficient as you might wish.

    0 讨论(0)
  • 2020-11-28 17:12

    Unfortunately, the only way to do that with Java 8 is with the solution provided by Holger.

    However, the operation dropWhile(predicate) has been added to Java 9 so starting from JDK 9, you can simply have:

    Stream.of(0, 1, 2, 3, 0, 1, 2, 3, 4).dropWhile(n -> n < 3).forEach(System.out::println);
    
    0 讨论(0)
提交回复
热议问题