Equivalent of Scala dropWhile

后端 未结 2 1767
佛祖请我去吃肉
佛祖请我去吃肉 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  Stream dropWhile(Predicate p, Stream 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  Stream dropWhile(Predicate p, Stream s) {
        Spliterator sp = s.spliterator();
        return StreamSupport.stream(new Spliterators.AbstractSpliterator(
                sp.estimateSize(), sp.characteristics() & ~Spliterator.SIZED) {
            boolean dropped;
            public boolean tryAdvance(Consumer 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 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.

提交回复
热议问题