Эквивалент Scala dropWhile

Я изо всех сил пытаюсь найти способ пропустить некоторые элементы в начале потока в зависимости от предиката.

Что-то вроде этого:

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.


person mtlx    schedule 29.08.2014    source источник
comment
Связано: ограничить поток предикатом   -  person charlie    schedule 28.06.2016


Ответы (2)


Этот тип операции не является предполагаемым вариантом использования для Streams, поскольку он включает зависимость между элементами. Поэтому решение может выглядеть не очень элегантно, поскольку вам нужно ввести переменную с полным состоянием для вашего предиката:

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, но он будет работать даже с параллельными потоками, хотя и не так эффективно, как хотелось бы.

person Holger    schedule 29.08.2014
comment
Немного поздно с этим, но вы рассматривали возможность использования класса AtomicBoolean, а не класса MutableBoolean? - person Henrik Aasted Sørensen; 17.02.2017
comment
@Henrik: AtomicBoolean будет притворяться потокобезопасным, которого нет для этой операции, поскольку порядок оценки предиката не определен. Кроме того, вы потенциально теряете производительность, используя поточно-безопасную конструкцию для операции, которая в любом случае не работает параллельно. С другой стороны, локально используемый MutableBoolean ясно показывает, чего вы можете ожидать. - person Holger; 17.02.2017
comment
Отличное объяснение! Спасибо. - person Henrik Aasted Sørensen; 17.02.2017
comment
@Holger Я считаю, что boolean[] flag = { false }; делает разумное непоточное изменчивое логическое значение. - person Peter Lawrey; 11.09.2018
comment
@PeterLawrey, создающий выделенный класс или использующий массив, в основном зависит от стиля кодирования. - person Holger; 11.09.2018

К сожалению, единственный способ сделать это с 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);
person Tunaki    schedule 29.01.2016