173

Java 8 ストリームで表されるデータセットがあります。

Stream<T> stream = ...;

それをフィルタリングしてランダムなサブセットを取得する方法を確認できます-たとえば

Random r = new Random();
PrimitiveIterator.OfInt coin = r.ints(0, 2).iterator();   
Stream<T> heads = stream.filter((x) -> (coin.nextInt() == 0));

また、このストリームを縮小して、たとえば、データ セットの 2 つのランダムな半分を表す 2 つのリストを取得し、それらをストリームに戻す方法もわかります。しかし、最初のストリームから 2 つのストリームを生成する直接的な方法はありますか? 何かのようなもの

(heads, tails) = stream.[some kind of split based on filter]

洞察をありがとう。

4

11 に答える 11

352

これにはコレクターを使用できます。

  • 2 つのカテゴリの場合は、Collectors.partitioningBy()factory を使用します。

これにより が作成Map<Boolean, List>され、 に基づいていずれかのリストにアイテムが配置されますPredicate

注: ストリームは全体を消費する必要があるため、これは無限ストリームでは機能しません。いずれにせよストリームは消費されるため、このメソッドは、メモリを使用する新しいストリームを作成する代わりに、単純にそれらをリストに入れます。出力としてストリームが必要な場合は、これらのリストをいつでもストリーミングできます。

また、あなたが提供したヘッドのみの例でさえ、イテレータは必要ありません。

  • バイナリ分割は次のようになります。
Random r = new Random();

Map<Boolean, List<String>> groups = stream
    .collect(Collectors.partitioningBy(x -> r.nextBoolean()));

System.out.println(groups.get(false).size());
System.out.println(groups.get(true).size());
  • より多くのカテゴリについては、Collectors.groupingBy()ファクトリを使用してください。
Map<Object, List<String>> groups = stream
    .collect(Collectors.groupingBy(x -> r.nextInt(3)));
System.out.println(groups.get(0).size());
System.out.println(groups.get(1).size());
System.out.println(groups.get(2).size());

ストリームが ではなくStream、 のようなプリミティブ ストリームの 1 つである場合IntStream、この.collect(Collectors)メソッドは使用できません。コレクタ ファクトリを使用せずに手動で行う必要があります。その実装は次のようになります。

[2020-04-16 以降の例 2.0]

    IntStream    intStream = IntStream.iterate(0, i -> i + 1).limit(100000).parallel();
    IntPredicate predicate = ignored -> r.nextBoolean();

    Map<Boolean, List<Integer>> groups = intStream.collect(
            () -> Map.of(false, new ArrayList<>(100000),
                         true , new ArrayList<>(100000)),
            (map, value) -> map.get(predicate.test(value)).add(value),
            (map1, map2) -> {
                map1.get(false).addAll(map2.get(false));
                map1.get(true ).addAll(map2.get(true ));
            });

この例では、初期コレクションのフル サイズで ArrayLists を初期化します (これがわかっている場合)。これにより、最悪のシナリオでもサイズ変更イベントが発生しなくなりますが、2 N T スペース (N = 初期要素数、T = スレッド数) を消費する可能性があります。スペースと速度をトレードオフするには、スペースを除外するか、1 つのパーティションで予想される要素の最大数 (通常、バランスの取れた分割の場合は N/2 をわずかに超える) など、経験に基づいた最善の推測を使用できます。

Java 9 メソッドを使用して誰かを怒らせないことを願っています。Java 8 バージョンについては、編集履歴を参照してください。

于 2015-05-07T20:17:55.353 に答える
24

私はこの質問に出くわし、フォークされたストリームには有効であると証明できるいくつかのユースケースがあると感じています. 以下のコードはコンシューマーとして記述したため、何も実行されませんが、関数やその他のあらゆるものに適用できます。

class PredicateSplitterConsumer<T> implements Consumer<T>
{
  private Predicate<T> predicate;
  private Consumer<T>  positiveConsumer;
  private Consumer<T>  negativeConsumer;

  public PredicateSplitterConsumer(Predicate<T> predicate, Consumer<T> positive, Consumer<T> negative)
  {
    this.predicate = predicate;
    this.positiveConsumer = positive;
    this.negativeConsumer = negative;
  }

  @Override
  public void accept(T t)
  {
    if (predicate.test(t))
    {
      positiveConsumer.accept(t);
    }
    else
    {
      negativeConsumer.accept(t);
    }
  }
}

コードの実装は次のようになります。

personsArray.forEach(
        new PredicateSplitterConsumer<>(
            person -> person.getDateOfBirth().isPresent(),
            person -> System.out.println(person.getName()),
            person -> System.out.println(person.getName() + " does not have Date of birth")));
于 2015-07-24T09:51:11.470 に答える
20

残念ながら、あなたが求めるものは、 Streamの JavaDoc で直接眉をひそめています:

ストリームの操作 (中間または端末ストリーム操作の呼び出し) は 1 回だけにしてください。これにより、たとえば、同じソースが 2 つ以上のパイプラインにフィードする「フォークされた」ストリーム、または同じストリームの複数のトラバーサルが除外されます。

peekこのタイプの動作が本当に必要な場合は、または他の方法を使用してこれを回避できます。この場合、フォーク フィルターを使用して同じ元のストリーム ソースから 2 つのストリームをバックアップしようとする代わりに、ストリームを複製し、それぞれの複製を適切にフィルター処理する必要があります。

ただし、 aStreamがユース ケースに適した構造であるかどうかを再検討することをお勧めします。

于 2013-11-12T22:27:49.020 に答える
9

ではない正確に。Stream1 つから 2 つの s を取得することはできません。これは意味がありません。同時にもう一方を生成する必要なく、一方を反復するにはどうすればよいでしょうか。ストリームは一度しか操作できません。

ただし、それらをリストなどにダンプしたい場合は、次のことができます

stream.forEach((x) -> ((x == 0) ? heads : tails).add(x));
于 2013-11-12T21:38:08.197 に答える
7

これは、Stream の一般的なメカニズムに反します。ストリーム S0 を必要に応じて Sa と Sb に分割できるとします。Saに対して端末操作を実行するとcount()、必然的に S0 のすべての要素が「消費」されます。したがって、Sb はデータ ソースを失いました。

以前の Stream にはtee()、ストリームを 2 つに複製するメソッドがあったと思います。現在は削除されています。

Stream には peek() メソッドがありますが、それを使用して要件を達成できる場合があります。

于 2013-11-12T21:40:39.007 に答える
6

正確ではありませんが、 を呼び出すことで必要なことを達成できる場合がありますCollectors.groupingBy()。新しいコレクションを作成すると、その新しいコレクションでストリームをインスタンス化できます。

于 2013-11-13T18:33:28.267 に答える