술어에 의해 스트림 제한


187

(잠재적으로 무한)을 제한하는 Java 8 스트림 작업이 있습니까? Stream첫 번째 요소가 술어와 일치하지 않을 때까지 있습니까?

Java 9에서는 takeWhile아래 예와 같이 10 미만의 모든 숫자를 인쇄 할 수 있습니다 .

IntStream
    .iterate(1, n -> n + 1)
    .takeWhile(n -> n < 10)
    .forEach(System.out::println);

Java 8에는 그러한 작업이 없으므로 일반적인 방법으로 구현하는 가장 좋은 방법은 무엇입니까?


1
아마도 유용한 정보 : stackoverflow.com/q/19803058/248082
nobeh


건축가 가이 사용 사례를 거치지 않고 어떻게 "실제로 이것을 사용할 수 있습니까?" 자바 8로 스트림은 기존의 데이터 구조체에 실제로 도움이됩니다 : - /
Thorbjørn Ravn 안데르센


Java 9를 사용하면보다 쉽게 ​​작성할 수 있습니다IntStream.iterate(1, n->n<10, n->n+1).forEach(System.out::print);
Marc Dzaebel

답변:


81

이러한 작업 은 Java 8 로 가능 해야 Stream하지만 효율적으로 수행 할 수있는 것은 아닙니다. 예를 들어, 요소를 순서대로 살펴보아야하므로 이러한 작업을 병렬화 할 필요는 없습니다.

API는이를 수행하는 쉬운 방법을 제공하지 않지만 아마도 가장 간단한 방법은을 수행 하고 "취약"구현을 갖도록 Stream.iterator()랩한 다음 a Iterator로 이동 Spliterator하는 것 Stream입니다. 또는 아마도이 Spliterator구현에서 더 이상 나눌 수는 없지만를 감싸십시오 .

다음의 검증되지 않은 구현의 takeWhileA의는 Spliterator:

static <T> Spliterator<T> takeWhile(
    Spliterator<T> splitr, Predicate<? super T> predicate) {
  return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
    boolean stillGoing = true;
    @Override public boolean tryAdvance(Consumer<? super T> consumer) {
      if (stillGoing) {
        boolean hadNext = splitr.tryAdvance(elem -> {
          if (predicate.test(elem)) {
            consumer.accept(elem);
          } else {
            stillGoing = false;
          }
        });
        return hadNext && stillGoing;
      }
      return false;
    }
  };
}

static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> predicate) {
   return StreamSupport.stream(takeWhile(stream.spliterator(), predicate), false);
}

8
이론적으로는 상태 비 저장 술어와 병렬 처리가 쉽습니다. 병렬 배치로 조건을 평가하십시오 (몇 번의 추가 실행으로 술어가 발생하지 않거나 부작용이 없다고 가정). 문제는 스트림이 사용하는 재귀 적 분해 (포크 / 조인 프레임 워크)의 맥락에서이를 수행하는 것입니다. 정말 비효율적 인 스트림입니다.
Aleksandr Dubinsky

91
스트림이 자동 병렬 처리에 너무 몰두하지 않았다면 스트림이 훨씬 나았을 것입니다. 스트림을 사용할 수있는 아주 작은 부분에서만 병렬 처리가 필요합니다. 또한 오라클이 성능에 대해 많은 관심을 기울인다면 개발자를 방해하지 않고 JVM JIT를 자동 벡터화하고 훨씬 더 큰 성능을 향상시킬 수있었습니다. 이것이 바로 자동 병렬 처리입니다.
Aleksandr Dubinsky

Java 9가 릴리스되었으므로이 답변을 업데이트해야합니다.
Radiodef

4
아니요, @Radiodef. 이 질문은 특히 Java 8 솔루션을 요구합니다.
Renato Back

145

운영 takeWhiledropWhileJDK 9. 예제 코드에 추가되었습니다

IntStream
    .iterate(1, n -> n + 1)
    .takeWhile(n -> n < 10)
    .forEach(System.out::println);

JDK 9에서 컴파일하고 실행할 때 예상대로 정확하게 작동합니다.

JDK 9가 릴리스되었습니다. http://jdk.java.net/9/ 에서 다운로드 할 수 있습니다.



1
그들이 호출하는 이유 어떤 이유가 있나요 takeWhiledropWhile보다는 limitWhileskipWhileAPI를 기존과 일관성을 위해,?
Lukas Eder

10
@LukasEder takeWhiledropWhile스칼라, 파이썬, 그루비, 루비, 하스켈, 그리고 Clojure의 발생, 꽤 널리 퍼져 있습니다. 와 비대칭 skiplimit불행한 일이다. 어쩌면 skiplimit호출을 주어야한다고 대다수가 생각 drop하고 take,하지만 당신은 이미 하스켈에 익숙하지 않는 사람들은 직관적으로하지 않습니다.
스튜어트 마크

3
@StuartMarks : 나는 이해 dropXXX하고 takeXXX더 인기있는 용어입니다하지만 난 더 SQL - 억양과 개인적으로 살 수 limitXXXskipXXX. (: 스칼라도있다 BTW 나는 ... :)이 새로운 비대칭이 더 많은 용어의 개인의 선택보다 더 혼란을 발견 drop(int)하고 take(int))
루카스 에더

1
예, 프로덕션에서 Jdk 9로 업그레이드하겠습니다. 많은 개발자들이 여전히 Jdk8을 사용하고 있으며 이러한 기능은 처음부터 Streams에 포함되어 있어야합니다.
wilmol

50

allMatch()는 단락 기능이므로 처리를 중지하는 데 사용할 수 있습니다. 가장 큰 단점은 테스트를 두 번 수행해야한다는 것입니다. 한 번 처리해야하는지 확인하고 계속 진행해야하는지 확인해야합니다.

IntStream
    .iterate(1, n -> n + 1)
    .peek(n->{if (n<10) System.out.println(n);})
    .allMatch(n->n < 10);

5
이것은 처음에는 나에게 직관적이지 않은 것처럼 보였지만 (메소드 이름이 주어짐) 문서Stream.allMatch()단락 작업 임을 확인 합니다 . 따라서 이것은와 같은 무한한 스트림에서도 완료됩니다 IntStream.iterate(). 물론,이 점을 감안하면 현명한 최적화입니다.
Bailey Parker

3
이것은 깔끔하지만 그 의도가의 본문이라는 것을 잘 전달한다고 생각하지 않습니다 peek. 다음 달에이 문제가 발생하면 프로그래머가 왜 검사를했는지 확인한 allMatch다음 답을 무시 한지 잠시 시간이 걸릴 것입니다.
Joshua Goldberg

10
이 솔루션의 단점은 부울을 반환하므로 일반적으로 스트림의 결과를 수집 할 수 없다는 것입니다.
neXus

35

@StuartMarks에 대한 후속 조치로 . 내 StreamEx 라이브러리에는 takeWhile현재 JDK-9 구현과 호환되는 작업이 있습니다. JDK-9에서 실행될 때 JDK 구현에 위임 할 것 MethodHandle.invokeExact입니다 (실제로 빠릅니다). JDK-8에서 실행될 때는 "polyfill"구현이 사용됩니다. 따라서 내 라이브러리를 사용하면 다음과 같이 문제를 해결할 수 있습니다.

IntStreamEx.iterate(1, n -> n + 1)
           .takeWhile(n -> n < 10)
           .forEach(System.out::println);

StreamEx 클래스 용으로 구현하지 않은 이유는 무엇입니까?
Someguy

@ Soomeguy 구현했습니다.
Tagir Valeev

14

takeWhileprotonpack 라이브러리가 제공하는 기능 중 하나입니다 .

Stream<Integer> infiniteInts = Stream.iterate(0, i -> i + 1);
Stream<Integer> finiteInts = StreamUtils.takeWhile(infiniteInts, i -> i < 10);

assertThat(finiteInts.collect(Collectors.toList()),
           hasSize(10));

11

업데이트 : Java 9에는 Stream이제 takeWhile 이 제공됩니다. 메소드 .

해킹이나 다른 솔루션이 필요하지 않습니다. 그냥 사용하십시오!


나는 이것이 크게 향상 될 수 있다고 확신한다 : (누군가 그것을 스레드로부터 안전하게 만들 수있다)

Stream<Integer> stream = Stream.iterate(0, n -> n + 1);

TakeWhile.stream(stream, n -> n < 10000)
         .forEach(n -> System.out.print((n == 0 ? "" + n : "," + n)));

확실한 해킹은 ... 우아하지는 않지만 작동하지만 ~ : D

class TakeWhile<T> implements Iterator<T> {

    private final Iterator<T> iterator;
    private final Predicate<T> predicate;
    private volatile T next;
    private volatile boolean keepGoing = true;

    public TakeWhile(Stream<T> s, Predicate<T> p) {
        this.iterator = s.iterator();
        this.predicate = p;
    }

    @Override
    public boolean hasNext() {
        if (!keepGoing) {
            return false;
        }
        if (next != null) {
            return true;
        }
        if (iterator.hasNext()) {
            next = iterator.next();
            keepGoing = predicate.test(next);
            if (!keepGoing) {
                next = null;
            }
        }
        return next != null;
    }

    @Override
    public T next() {
        if (next == null) {
            if (!hasNext()) {
                throw new NoSuchElementException("Sorry. Nothing for you.");
            }
        }
        T temp = next;
        next = null;
        return temp;
    }

    public static <T> Stream<T> stream(Stream<T> s, Predicate<T> p) {
        TakeWhile tw = new TakeWhile(s, p);
        Spliterator split = Spliterators.spliterator(tw, Integer.MAX_VALUE, Spliterator.ORDERED);
        return StreamSupport.stream(split, false);
    }

}

8

java8 + rxjava를 사용할 수 있습니다 .

import java.util.stream.IntStream;
import rx.Observable;


// Example 1)
IntStream intStream  = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
    .takeWhile(n ->
          {
                System.out.println(n);
                return n < 10;
          }
    ).subscribe() ;


// Example 2
IntStream intStream  = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
    .takeWhile(n -> n < 10)
    .forEach( n -> System.out.println(n));

6

실제로 추가 라이브러리없이 또는 Java 9를 사용하여 Java 8에서 두 가지 방법이 있습니다.

콘솔에서 2에서 20까지의 숫자를 인쇄하려면 다음을 수행하십시오.

IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).allMatch(i -> i < 20);

또는

IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).anyMatch(i -> i >= 20);

결과는 두 경우 모두에 있습니다.

2
4
6
8
10
12
14
16
18
20

아직 아무도 언급 하지 않았습니다. 이것이이 게시물의 이유입니다.


5

이것은 JDK 9 java.util.stream.Stream.takeWhile (Predicate)에서 복사 한 소스입니다. JDK 8로 작업하기위한 약간의 차이.

static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> p) {
    class Taking extends Spliterators.AbstractSpliterator<T> implements Consumer<T> {
        private static final int CANCEL_CHECK_COUNT = 63;
        private final Spliterator<T> s;
        private int count;
        private T t;
        private final AtomicBoolean cancel = new AtomicBoolean();
        private boolean takeOrDrop = true;

        Taking(Spliterator<T> s) {
            super(s.estimateSize(), s.characteristics() & ~(Spliterator.SIZED | Spliterator.SUBSIZED));
            this.s = s;
        }

        @Override
        public boolean tryAdvance(Consumer<? super T> action) {
            boolean test = true;
            if (takeOrDrop &&               // If can take
                    (count != 0 || !cancel.get()) && // and if not cancelled
                    s.tryAdvance(this) &&   // and if advanced one element
                    (test = p.test(t))) {   // and test on element passes
                action.accept(t);           // then accept element
                return true;
            } else {
                // Taking is finished
                takeOrDrop = false;
                // Cancel all further traversal and splitting operations
                // only if test of element failed (short-circuited)
                if (!test)
                    cancel.set(true);
                return false;
            }
        }

        @Override
        public Comparator<? super T> getComparator() {
            return s.getComparator();
        }

        @Override
        public void accept(T t) {
            count = (count + 1) & CANCEL_CHECK_COUNT;
            this.t = t;
        }

        @Override
        public Spliterator<T> trySplit() {
            return null;
        }
    }
    return StreamSupport.stream(new Taking(stream.spliterator()), stream.isParallel()).onClose(stream::close);
}

4

다음은 ints에서 수행 된 버전입니다-질문에 나와 있습니다.

용법:

StreamUtil.takeWhile(IntStream.iterate(1, n -> n + 1), n -> n < 10);

StreamUtil의 코드는 다음과 같습니다.

import java.util.PrimitiveIterator;
import java.util.Spliterators;
import java.util.function.IntConsumer;
import java.util.function.IntPredicate;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;

public class StreamUtil
{
    public static IntStream takeWhile(IntStream stream, IntPredicate predicate)
    {
        return StreamSupport.intStream(new PredicateIntSpliterator(stream, predicate), false);
    }

    private static class PredicateIntSpliterator extends Spliterators.AbstractIntSpliterator
    {
        private final PrimitiveIterator.OfInt iterator;
        private final IntPredicate predicate;

        public PredicateIntSpliterator(IntStream stream, IntPredicate predicate)
        {
            super(Long.MAX_VALUE, IMMUTABLE);
            this.iterator = stream.iterator();
            this.predicate = predicate;
        }

        @Override
        public boolean tryAdvance(IntConsumer action)
        {
            if (iterator.hasNext()) {
                int value = iterator.nextInt();
                if (predicate.test(value)) {
                    action.accept(value);
                    return true;
                }
            }

            return false;
        }
    }
}

2

라이브러리 AbacusUtil로 이동 하십시오 . 원하는 정확한 API를 제공합니다.

IntStream.iterate(1, n -> n + 1).takeWhile(n -> n < 10).forEach(System.out::println);

선언 : 저는 AbacusUtil의 개발자입니다.


0

단락 터미널 조작을 제외하고는 스트림을 중단 할 수 없으며, 일부 스트림 값은 값에 관계없이 처리되지 않습니다. 그러나 스트림에서 작업을 피하려면 스트림에 변환 및 필터를 추가하십시오.

import java.util.Objects;

class ThingProcessor
{
    static Thing returnNullOnCondition(Thing thing)
    {    return( (*** is condition met ***)? null : thing);    }

    void processThings(Collection<Thing> thingsCollection)
    {
        thingsCollection.stream()
        *** regular stream processing ***
        .map(ThingProcessor::returnNullOnCondition)
        .filter(Objects::nonNull)
        *** continue stream processing ***
    }
} // class ThingProcessor

그러면 사물이 특정 조건을 충족 할 때 사물 스트림을 null로 변환 한 다음 null을 필터링합니다. 부작용에 기꺼이 만족한다면, 어떤 일이 발생하면 조건 값을 true로 설정할 수 있으므로 그 이후의 모든 일이 그 값에 관계없이 필터링됩니다. 그러나 그렇지 않은 경우에도 처리하지 않으려는 스트림에서 값을 필터링하여 많은 (아직은 아니지만) 처리를 절약 할 수 있습니다.


일부 익명의 평가자가 이유를 밝히지 않고 내 답변을 다운받은 것은 아쉬운 일입니다. 따라서 나도 다른 독자도 내 대답에 어떤 문제가 있는지 알지 못합니다. 그들의 타당성이 없으면 그들의 비판은 유효하지 않으며 내 대답은 올바르다 고 생각합니다.
Matthew

당신은 무한 스트림을 다루는 OP 문제를 해결하지 못한다고 대답합니다. 또한 map () 필요없이 filter () 호출 자체에 조건을 작성할 수 있으므로 불필요하게 복잡해 보입니다. 질문에는 이미 예제 코드가 있습니다. 해당 코드에 답을 적용하면 프로그램이 영원히 반복되는 것을 볼 수 있습니다.
SenoCtar

0

비슷한 요구 사항조차 있었지만 웹 서비스를 호출하면 실패하면 3 번 다시 시도하십시오. 여러 번의 시도 후에도 실패하면 이메일 알림을 보내십시오. 인터넷 검색을 많이 한 후에 anyMatch()구세주가되었습니다. 내 샘플 코드는 다음과 같습니다. 다음 예제 에서 첫 번째 반복 자체에서 webServiceCall 메소드가 true를 리턴 하면 을 호출 한대로 스트림이 더 이상 반복되지 않습니다 anyMatch(). 나는 이것이 당신이 찾고있는 것이라고 믿습니다.

import java.util.stream.IntStream;

import io.netty.util.internal.ThreadLocalRandom;

class TrialStreamMatch {

public static void main(String[] args) {        
    if(!IntStream.range(1,3).anyMatch(integ -> webServiceCall(integ))){
         //Code for sending email notifications
    }
}

public static boolean webServiceCall(int i){
    //For time being, I have written a code for generating boolean randomly
    //This whole piece needs to be replaced by actual web-service client code
    boolean bool = ThreadLocalRandom.current().nextBoolean();
    System.out.println("Iteration index :: "+i+" bool :: "+bool);

    //Return success status -- true or false
    return bool;
}

0

수행 될 정확한 양의 회개를 알고 있다면

IntStream
          .iterate(1, n -> n + 1)
          .limit(10)
          .forEach(System.out::println);

1
이것은 저자의 질문에 대답 할 수 있지만, 설명 할 단어와 문서에 대한 링크가 부족합니다. 원시 코드 스 니펫은 주변에 문구가 없으면별로 도움이되지 않습니다. 좋은 답변을 작성하는 방법이 매우 도움 이 될 수도 있습니다. 답을 수정하십시오.
hellow

0
    IntStream.iterate(1, n -> n + 1)
    .peek(System.out::println) //it will be executed 9 times
    .filter(n->n>=9)
    .findAny();

피크 대신 mapToObj를 사용하여 최종 객체 또는 메시지를 반환 할 수 있습니다

    IntStream.iterate(1, n -> n + 1)
    .mapToObj(n->{   //it will be executed 9 times
            if(n<9)
                return "";
            return "Loop repeats " + n + " times";});
    .filter(message->!message.isEmpty())
    .findAny()
    .ifPresent(System.out::println);

-2

다른 문제가있는 경우 다른 솔루션이 필요할 수 있지만 현재 문제에 대해서는 다음과 같이 진행하십시오.

IntStream
    .iterate(1, n -> n + 1)
    .limit(10)
    .forEach(System.out::println);

-2

약간 벗어난 주제 일지 모르지만 이것이 우리가 가진 List<T>것입니다.Stream<T> 입니다.

먼저 takeutil 메소드 가 필요합니다 . 이 방법은 첫 번째 n요소를 사용합니다.

static <T> List<T> take(List<T> l, int n) {
    if (n <= 0) {
        return newArrayList();
    } else {
        int takeTo = Math.min(Math.max(n, 0), l.size());
        return l.subList(0, takeTo);
    }
}

그냥 작동합니다 scala.List.take

    assertEquals(newArrayList(1, 2, 3), take(newArrayList(1, 2, 3, 4, 5), 3));
    assertEquals(newArrayList(1, 2, 3), take(newArrayList(1, 2, 3), 5));

    assertEquals(newArrayList(), take(newArrayList(1, 2, 3), -1));
    assertEquals(newArrayList(), take(newArrayList(1, 2, 3), 0));

이제는 다음을 takeWhile기반으로 메소드 를 작성하는 것이 매우 간단합니다 .take

static <T> List<T> takeWhile(List<T> l, Predicate<T> p) {
    return l.stream().
            filter(p.negate()).findFirst(). // find first element when p is false
            map(l::indexOf).        // find the index of that element
            map(i -> take(l, i)).   // take up to the index
            orElse(l);  // return full list if p is true for all elements
}

다음과 같이 작동합니다.

    assertEquals(newArrayList(1, 2, 3), takeWhile(newArrayList(1, 2, 3, 4, 3, 2, 1), i -> i < 4));

이 구현은 목록을 몇 번 부분적으로 반복하지만 추가 O(n^2)작업을 추가하지는 않습니다 . 그것이 허용되기를 바랍니다.


-3

나는 이것을 구현함으로써 또 다른 빠른 해결책을 가지고 있습니다 (실제로 부정확하지만 아이디어를 얻습니다).

public static void main(String[] args) {
    System.out.println(StreamUtil.iterate(1, o -> o + 1).terminateOn(15)
            .map(o -> o.toString()).collect(Collectors.joining(", ")));
}

static interface TerminatedStream<T> {
    Stream<T> terminateOn(T e);
}

static class StreamUtil {
    static <T> TerminatedStream<T> iterate(T seed, UnaryOperator<T> op) {
        return new TerminatedStream<T>() {
            public Stream<T> terminateOn(T e) {
                Builder<T> builder = Stream.<T> builder().add(seed);
                T current = seed;
                while (!current.equals(e)) {
                    current = op.apply(current);
                    builder.add(current);
                }
                return builder.build();
            }
        };
    }
}

2
전체 스트림을 미리 평가하고 있습니다 ! 그리고 current결코 그렇지 않으면 .equals(e)끝없는 루프를 얻게됩니다. 나중에 예를 들어 신청하더라도 둘 다 .limit(1). 그것은 'unclean' 보다 훨씬 나쁘다 .
Charlie Charlie

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.