web-dev-qa-db-ja.com

Java 8でストリームを複製できますか?

ストリームに対して一連の操作を実行し、結果のストリームを他の操作で2つの異なる方法で処理したい場合があります。

共通の初期操作を2回指定することなくこれを実行できますか?

たとえば、次のようなdup()メソッドが存在することを期待しています。

Stream [] desired_streams = IntStream.range(1, 100).filter(n -> n % 2 == 0).dup();
Stream stream14 = desired_streams[0].filter(n -> n % 7 == 0); // multiples of 14
Stream stream10 = desired_streams[1].filter(n -> n % 5 == 0); // multiples of 10
50
necromancer

一般的には不可能です。

入力ストリームまたは入力反復子を複製する場合、2つのオプションがあります。

A.すべてをコレクションに入れて、_List<>_と言う

ストリームを2つのストリーム_s1_および_s2_に複製するとします。 _n1_および_s1_要素内の_n2_要素に_s2_の高度な要素がある場合、ペースを保つために_|n2 - n1|_要素をメモリに保持する必要があります。ストリームが無限の場合、必要なストレージの上限はないかもしれません。

Pythonの tee() を見て、必要なものを確認してください。

このitertoolには、大量の補助ストレージが必要になる場合があります(一時データをどれだけ保存する必要があるかによって異なります)。一般に、あるイテレーターが別のイテレーターが開始する前にほとんどまたはすべてのデータを使用する場合、list()の代わりにtee()を使用する方が高速です。

B.可能な場合:要素を作成するジェネレーターの状態をコピーします

このオプションが機能するためには、おそらくストリームの内部の仕組みにアクセスする必要があります。言い換えると、ジェネレーター-要素を作成する部分-は、最初の場所でコピーをサポートする必要があります。 [OP:これを参照してください great answer 、質問の例でこれを行う方法の例として]

「外の世界」全体の状態をコピーする必要があるため、ユーザーからの入力では機能しません。 JavaのStreamはコピーをサポートしていません。ファイル、ネットワーク、キーボード、センサー、ランダム性などを扱うために可能な限り一般的になるように設計されているためです。[OP:温度を読み取るストリームオンデマンドのセンサー。測定値のコピーを保存せずに複製することはできません]

これはJavaの場合だけではありません。これは一般的なルールです。この理由から、C++の _std::istream_ は移動セマンティクスのみをサポートし、コピーセマンティクス(「コピーコンストラクター(削除済み)」)をサポートしていることがわかります(その他)。

26
Elazar

この方法でストリームを複製することはできません。ただし、共通部分をメソッドまたはラムダ式に移動することにより、コードの重複を回避できます。

Supplier<IntStream> supplier = () ->
    IntStream.range(1, 100).filter(n -> n % 2 == 0);
supplier.get().filter(...);
supplier.get().filter(...);
44
nosid

1つの複製で消費した要素をバッファリングしているが、まだ他の要素ではバッファリングしていない場合は可能です。

jOOQ の統合テストを改善するために作成したオープンソースライブラリである jOOλ のストリームにduplicate()メソッドを実装しました。基本的に、あなたは書くことができます:

Tuple2<Seq<Integer>, Seq<Integer>> desired_streams = Seq.seq(
    IntStream.range(1, 100).filter(n -> n % 2 == 0).boxed()
).duplicate();

(注:IntSeqをまだ実装していないため、現在ストリームをボックス化する必要があります)

内部的には、1つのストリームからは消費されているが他のストリームからは消費されていないすべての値を格納するLinkedListバッファーがあります。これはおそらく、2つのストリームがほぼ同じ速度で消費される場合と同じくらい効率的です。

アルゴリズムの仕組みは次のとおりです。

static <T> Tuple2<Seq<T>, Seq<T>> duplicate(Stream<T> stream) {
    final LinkedList<T> gap = new LinkedList<>();
    final Iterator<T> it = stream.iterator();

    @SuppressWarnings("unchecked")
    final Iterator<T>[] ahead = new Iterator[] { null };

    class Duplicate implements Iterator<T> {
        @Override
        public boolean hasNext() {
            if (ahead[0] == null || ahead[0] == this)
                return it.hasNext();

            return !gap.isEmpty();
        }

        @Override
        public T next() {
            if (ahead[0] == null)
                ahead[0] = this;

            if (ahead[0] == this) {
                T value = it.next();
                gap.offer(value);
                return value;
            }

            return gap.poll();
        }
    }

    return Tuple(seq(new Duplicate()), seq(new Duplicate()));
}

その他のソースコードはこちら

実際、 jOOλ を使用すると、次のように完全なワンライナーを記述できます。

Tuple2<Seq<Integer>, Seq<Integer>> desired_streams = Seq.seq(
    IntStream.range(1, 100).filter(n -> n % 2 == 0).boxed()
).duplicate()
 .map1(s -> s.filter(n -> n % 7 == 0))
 .map2(s -> s.filter(n -> n % 5 == 0));

// This will yield 14, 28, 42, 56...
desired_streams.v1.forEach(System.out::println)

// This will yield 10, 20, 30, 40...
desired_streams.v2.forEach(System.out::println);
6
Lukas Eder

ストリーム生成を、このストリームを返す別のメソッド/関数に移動して、2回呼び出すこともできます。

4
Tomasz Górka

どちらか、

  • 初期化をメソッドに移動し、メソッドを再度呼び出すだけです

これには、実行していることを明示するという利点があり、無限ストリームでも機能します。

  • ストリームを収集してから再ストリームする

あなたの例では:

final int[] arr = IntStream.range(1, 100).filter(n -> n % 2 == 0).toArray();

それから

final IntStream s = IntStream.of(arr);
3

更新:これしない動作します。元の回答のテキストの後の以下の説明を参照してください。

なんてばかげている。私がする必要があるのは次のとおりです。

Stream desired_stream = IntStream.range(1, 100).filter(n -> n % 2 == 0);
Stream stream14 = desired_stream.filter(n -> n % 7 == 0); // multiples of 14
Stream stream10 = desired_stream.filter(n -> n % 5 == 0); // multiples of 10

これが機能しない理由の説明:

コードを作成して両方のストリームを収集しようとすると、最初のストリームは問題なく収集されますが、2番目のストリームをストリーミングしようとすると例外Java.lang.IllegalStateException: stream has already been operated upon or closedがスローされます。

詳述すると、ストリームはステートフルオブジェクトです(これは、リセットまたは巻き戻しできません)。これらはイテレータと考えることができます。イテレータはポインタのようなものです。したがって、stream14stream10は、同じポインターへの参照と考えることができます。最初のストリームを最後まで消費すると、ポインターは「終わりを過ぎて」移動します。 2番目のストリームを消費しようとすることは、すでに「終わりを過ぎた」ポインターにアクセスしようとするようなもので、これは当然のこととして違法な操作です。

受け入れられた答えが示すように、ストリームを作成するコードは2回実行する必要がありますが、Supplierラムダまたは同様の構造に分割できます。

完全なテストコード:Foo.Javajavac Foo.JavaJava Fooの順に保存

import Java.util.stream.IntStream;

public class Foo {
  public static void main (String [] args) {
    IntStream s = IntStream.range(0, 100).filter(n -> n % 2 == 0);
    IntStream s1 = s.filter(n -> n % 5 == 0);
    s1.forEach(n -> System.out.println(n));
    IntStream s2 = s.filter(n -> n % 7 == 0);
    s2.forEach(n -> System.out.println(n));
  }
}

出力:

$ javac Foo.Java
$ Java Foo
0
10
20
30
40
50
60
70
80
90
Exception in thread "main" Java.lang.IllegalStateException: stream has already been operated upon or closed
    at Java.util.stream.AbstractPipeline.<init>(AbstractPipeline.Java:203)
    at Java.util.stream.IntPipeline.<init>(IntPipeline.Java:91)
    at Java.util.stream.IntPipeline$StatelessOp.<init>(IntPipeline.Java:592)
    at Java.util.stream.IntPipeline$9.<init>(IntPipeline.Java:332)
    at Java.util.stream.IntPipeline.filter(IntPipeline.Java:331)
    at Foo.main(Foo.Java:8)
2
necromancer

非無限ストリームの場合、ソースにアクセスできる場合は、次のように簡単です:

@Test
public void testName() throws Exception {
    List<Integer> integers = Arrays.asList(1, 2, 4, 5, 6, 7, 8, 9, 10);
    Stream<Integer> stream1 = integers.stream();
    Stream<Integer> stream2 = integers.stream();

    stream1.forEach(System.out::println);
    stream2.forEach(System.out::println);
}

プリント

1 2 4 5 6 7 8 9 10

1 2 4 5 6 7 8 9 10

あなたの場合:

Stream originalStream = IntStream.range(1, 100).filter(n -> n % 2 == 0)

List<Integer> listOf = originalStream.collect(Collectors.toList())

Stream stream14 = listOf.stream().filter(n -> n % 7 == 0);
Stream stream10 = listOf.stream().filter(n -> n % 5 == 0);

パフォーマンスなどについては、他の誰かの答えを読んでください;)

0
Blundell