Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equivalent of Scala dropWhile

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.

like image 960
mtlx Avatar asked Aug 29 '14 13:08

mtlx


People also ask

What is the dropWhile method in Scala?

As per the Scala documentation, the definition of the dropWhile method is as follows: The dropWhile method is a member of the IterableLike trait. 1. How to initialize a Sequence of donuts The code below shows how to initialize a Sequence of Donut elements of type String.

What is the correct way to name a method in Scala?

The correct technical way to think about this is that a Scala method name that ends with the : character is right-associative, meaning that the method comes from the variable on the right side of the expression. Therefore, with +: and ++:, these methods comes from the Seq that’s on the right of the method name.

How does the dropWhile method work in Python?

The dropWhile method takes a predicate function parameter that will be used to drop certain elements in a collection which satisfies the predicate function. The "dropping" process, or removal of elements, will stop as soon as it encounters an element that does not match the predicate function.

How to add elements to an existing seq in Scala?

Because the Scala Seq is immutable, you can’t add elements to an existing Seq. The way you work with Seq is to modify the elements it contains as you assign the results to a new Seq. These examples show how to use the append and prepend methods: Note that during these operations the : character is always next to the old (original) sequence.


2 Answers

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);
like image 105
Tunaki Avatar answered Oct 01 '22 10:10

Tunaki


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.

like image 36
Holger Avatar answered Oct 01 '22 11:10

Holger