9

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 < 3, Stream.of( 0, 1, 2, 3, 0, 1, 2, 3, 4 ) )
.forEach( System.out::println );
3   
0
1
2
3
4

That is the equivalent of Scala dropWhile.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
mtlx
  • 218
  • 2
  • 8

2 Answers2

20

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • A little late with this, but did you consider using an `AtomicBoolean` rather than the `MutableBoolean` class? – Henrik Aasted Sørensen Feb 17 '17 at 12:59
  • 2
    @Henrik: an `AtomicBoolean` would pretend a thread safety that isn’t there for this operation, as the order of the predicate evaluation is undefined. Further, you are then potentially wasting performance using a thread safe construct for an operation that doesn’t work in parallel anyway. The locally used `MutableBoolean`, on the other hand, clearly shows what you can expect. – Holger Feb 17 '17 at 13:12
  • Great explanation! Thanks. – Henrik Aasted Sørensen Feb 17 '17 at 13:13
  • @Holger I find `boolean[] flag = { false };` makes a reasonable non thread safe mutable boolean. – Peter Lawrey Sep 11 '18 at 09:20
  • 2
    @PeterLawrey creating a dedicated class or using an array, is mostly a matter of coding style. – Holger Sep 11 '18 at 09:26
11

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);
Community
  • 1
  • 1
Tunaki
  • 132,869
  • 46
  • 340
  • 423