述語でストリームを制限する


187

Stream最初の要素が述語と一致しないまで(潜在的に無限)を制限するJava 8ストリーム操作はありますか?

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


建築家は、このユースケースに遭遇することなく、「これを実際に何のために使用できるのか」をどのようにして乗り越えられるのでしょうか。Javaの8のとおりストリームは、既存のデータ構造のために実際に有用である: - /
するThorbjörnRavnアンデルセン


Java 9を使用すると、記述が容易になりますIntStream.iterate(1, n->n<10, n->n+1).forEach(System.out::print);
Marc Dzaebel

回答:


81

このような操作はJava 8 で可能であるはずStreamですが、必ずしも効率的に実行できるとは限りません。たとえば、要素を順番に調べる必要があるため、そのような操作を必ずしも並列化することはできません。

APIはそれを行う簡単な方法を提供していませんが、おそらく最も簡単な方法は、を取得しStream.iterator()、をラップIteratorして「take-while」実装を実装し、次にに戻ってからを実行SpliteratorすることStreamです。または-多分-をラップしSpliteratorますが、この実装では実際には分割できません。

ここでのテストされていない実装のtakeWhile上には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
理論的には、ステートレス述語を使用してtakeWhileを並列化するのは簡単です。条件を並列バッチで評価します(述語がスローされないか、追加で数回実行しても副作用がないと仮定します)。問題は、Streamsが使用する再帰的分解(fork / joinフレームワーク)のコンテキストでそれを行うことです。本当に、それはひどく非効率的なのはストリームです。
Aleksandr Dubinsky

91
ストリームは、自動並列処理にそれほど夢中にならなければ、はるかに優れていたでしょう。並列処理は、Streamsを使用できるほんの一部の場所でのみ必要です。その上、Oracleがパフォーマンスにそれほど注意を払っていれば、開発者の手を煩わせることなく、JVM JITを自動ベクトル化し、パフォーマンスを大幅に向上させることができたでしょう。これが自動並列処理です。
Aleksandr Dubinsky

Java 9がリリースされたので、今すぐこの回答を更新してください。
Radiodef 2018

4
いいえ、@ Radiodef。この質問は、特にJava 8ソリューションを求めています。
Renato戻る

145

操作takeWhileおよびdropWhileJDK 9に追加されました。サンプルコード

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

JDK 9でコンパイルして実行すると、期待どおりに動作します。

JDK 9がリリースされました。こちらからダウンロードできます:http : //jdk.java.net/9/


3
takeWhile/ を使用したJDK9ストリームのプレビュードキュメントへの直接リンクdropWhiledownload.java.net/jdk9/docs/api/java/util/stream/Stream.html
Miles

1
既存のAPIとの一貫性のために、それらがtakeWhileand dropWhileではなくlimitWhileandと呼ばれる理由はありskipWhileますか?
Lukas Eder

10
@LukasEder takeWhiledropWhileスカラ座やPython、Groovyの、ルビー、ハスケル、およびClojureの中で発生し、かなり普及しています。非対称性skipとはlimit残念です。たぶんskipとはlimit呼ばれているべきdroptakeしていますが、すでにハスケルに精通していない限り、これらは、直感的なようではありません。
スチュワートマークス

3
@StuartMarks:私はそれを理解しdropXXXtakeXXXより人気のある用語ですが、個人的にはよりSQL風のlimitXXXとで生活できますskipXXX。この新しい非対称性は、個々の用語の選択よりもはるかに混乱しています... :)(ところで:Scalaにもありdrop(int)take(int)
Lukas Eder

1
はい、本番環境でJdk 9にアップグレードします。多くの開発者がまだJdk8を使用しています。そのような機能は最初からStreamsに含まれているはずです。
wilmol

50

allMatch()はショートサーキット機能ですので、処理を中止することができます。主な欠点は、テストを2回行う必要があることです。1回目は処理する必要があるかどうかを確認し、もう1回は続行するかどうかを確認します。

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

5
これは最初は(メソッド名を考えると)直感的ではないように見えましたが、ドキュメントStream.allMatch()は、これが短絡操作であること確認しています。したがって、これはのような無限のストリームでも完了しますIntStream.iterate()。もちろん、振り返ってみると、これは賢明な最適化です。
ベイリーパーカー

3
これはすばらしいですが、その意図がの本体であることを十分に伝えているとは思いませんpeek。来月出会ったとしたら、プログラマーが私の前にプログラマーがなぜallMatch答えをチェックして無視したのか疑問に思うかもしれません。
Joshua Goldberg

10
このソリューションの欠点は、ブール値を返すため、通常のようにストリームの結果を収集できないことです。
neXus

35

@StuartMarks回答のフォローアップとして。私のStreamExライブラリには、takeWhile現在のJDK-9実装と互換性のある操作があります。JDK-9で実行している場合は、JDK実装に委譲されます(MethodHandle.invokeExactこれは非常に高速です)。JDK-8で実行する場合、「ポリフィル」実装が使用されます。私のライブラリを使用すると、問題は次のように解決できます:

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

StreamExクラスに実装しないのはなぜですか?
-Someguy

@Someguy私はそれを実装しました。
Tagir Valeev

14

takeWhileは、protonpackライブラリによって提供される関数の1つです。

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にtakeWhileメソッドStream追加されました

ハッキングやその他のソリューションは必要ありません。ちょうどそれを使用してください!


私はこれが大幅に改善できると確信しています:(誰かがそれをスレッドセーフにするかもしれません)

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つの方法があります。

コンソールに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

まだ誰もanyMatchについて言及していません。これがこの投稿の理由です。


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

これはintで行われたバージョンです-質問で尋ねられたように。

使用法:

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に設定して、後続のすべてのものが値に関係なくフィルターで除外されるようにすることができます。ただし、そうでない場合でも、処理したくないストリームから値をフィルタリングすることで、処理の多く(すべてではないにしても)を節約できます。


一部の匿名の評価者が理由を言わずに私の回答を低く評価したのはラメです。だから私も他の読者も私の答えのどこが悪いのか知りません。彼らの正当性がない場合、私は彼らの批判は無効であり、投稿された私の答えは正しいと考えます。
マシュー

答えは、無限ストリームを処理しているOPの問題を解決しないことです。また、map()を使用せずにfilter()呼び出し自体に条件を記述できるため、必要以上に複雑になるようです。質問にはすでにサンプルコードがあります。そのコードに回答を適用してみてください。プログラムが永久にループすることがわかります。
SenoCtar 2017

0

私も同様の要件を持っていました-Webサービスを呼び出し、失敗した場合は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
これは著者の質問に答えるかもしれませんが、説明する言葉やドキュメントへのリンクが欠けています。生のコードスニペットは、周りにいくつかのフレーズがないとあまり役に立ちません。また、良い答えの書き方も非常に役立ちます。回答を編集してください。
2018

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)。それは「汚れた」よりもはるかに悪いです。
チャーリー2016年

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.