web-dev-qa-db-ja.com

Java 8関数型プログラミングの 'reduce'関数の3番目の引数の目的

'reduce'の3番目の引数はどのような状況でJava 8ストリームで呼び出されますか?

以下のコードは、文字列のリストをトラバースし、それぞれの最初の文字のコードポイント値を合計しようとします。最後のラムダによって返される値は使用されていないようであり、printlnを挿入すると、呼び出されていないように見えます。ドキュメントには「コンバイナー」と記載されていますが、詳細がわかりません...

int result =
  data.stream().reduce(0, (total,s) -> total + s.codePointAt(0), (a,b) -> 1000000); 
29
Garth Gilmour

あなたは この関数 について話しているのですか?

reduce <U> U reduce(U identity,
             BiFunction<U,? super T,U> accumulator,
             BinaryOperator<U> combiner) 

提供されたID、累積、および結合機能を使用して、このストリームの要素の削減を実行します。これは次と同等です。

 U result = identity;
 for (T element : this stream)
     result = accumulator.apply(result, element)
 return result;   

ただし、順次実行するように制約されていません。 ID値は、コンバイナー関数のIDである必要があります。これは、すべてのuについて、combiner(identity、u)がuに等しいことを意味します。さらに、コンバイナ関数はアキュムレータ関数と互換性がある必要があります。すべてのuとtについて、以下が成り立つ必要があります。

 combiner.apply(u, accumulator.apply(identity, t)) == 
     accumulator.apply(u, t)   

これは端末操作です。

API注:このフォームを使用した多くのリダクションは、map操作とreduce操作の明示的な組み合わせによってより簡単に表すことができます。アキュムレータ関数は、マッパーとアキュムレータの融合として機能します。これは、以前に削減された値を知っているために計算を回避できる場合など、個別のマッピングと削減よりも効率的な場合があります。タイプパラメーター:U-結果のタイプパラメーター:identity-コンバイナー関数アキュムレーターのID値-結果コンバイナーに追加の要素を組み込むための連想的、非干渉的、ステートレス関数-連想的、非干渉的、ステートレスアキュムレータ関数と互換性がなければならない2つの値を組み合わせるための関数戻り値:削減の結果関連項目:reduce(BinaryOperator)、reduce(Object、BinaryOperator)

その目的は並列計算を可能にすることだと思いますので、削減が並列で実行される場合にのみ使用されると思います。順次実行する場合は、combinerを使用する必要はありません。これは確かにわかりません。「[...]は順次実行するように制約されていません」というドキュメントのコメントと、コメント内の「並列実行」に関する他の多くの言及に基づいて推測しています。

18
Matt Fenwick

削減操作 _Java.util.stream_パッケージの要約の段落で質問に答えられると思います。ここで最も重要な部分を引用させてください:


より一般的な形式では、タイプ_<T>_の要素に対してreduce操作を実行して、タイプ_<U>_の結果を生成するには、次の3つのパラメーターが必要です。

_<U> U reduce(U identity,
              BiFunction<U, ? super T, U> accumulator,
              BinaryOperator<U> combiner);
_

ここで、ID要素は、削減の初期シード値であり、入力要素がない場合のデフォルトの結果でもあります。アキュムレータ関数は、部分的な結果と次の要素を受け取り、新しい部分的な結果を生成します。コンバイナ関数は、2つの部分的な結果を組み合わせて、新しい部分的な結果を生成します。 (コンバイナーは、入力がパーティション化され、パーティションごとに部分的な累積が計算され、部分的な結果が結合されて最終結果が生成される並列削減で必要です。)より正式には、ID値はコンバイナーのIDである必要があります。関数。これは、すべてのuについて、combiner.apply(identity, u)uに等しいことを意味します。さらに、コンバイナ関数は結合法則であり、アキュムレータ関数と互換性がある必要があります。すべてのuおよびtについて、combiner.apply(u, accumulator.apply(identity, t))equals()である必要があります。 accumulator.apply(u, t)

3引数形式は、2引数形式を一般化したものであり、マッピングステップを累積ステップに組み込んでいます。次のように、より一般的な形式を使用して、単純な重みの合計の例を再キャストできます。

_ int sumOfWeights = widgets.stream()
                           .reduce(0,
                                   (sum, b) -> sum + b.getWeight())
                                   Integer::sum);
_

ただし、明示的なmap-reduce形式の方が読みやすいため、通常は優先する必要があります。一般化された形式は、マッピングとリダクションを1つの関数に組み合わせることで、重要な作業を最適化できる場合に提供されます。


言い換えれば、私が理解している限り、3つの引数の形式は2つの場合に役立ちます。

  1. 並列実行が重要な場合。
  2. マッピングと累積の手順を組み合わせることで、パフォーマンスを大幅に最適化できる場合。それ以外の場合は、より単純で読みやすい明示的なmap-reduceフォームを使用できます。

明示的な形式については、同じドキュメントで前述しています。

_int sumOfWeights = widgets.parallelStream()
        .filter(b -> b.getColor() == RED)
        .mapToInt(b -> b.getWeight())
        .sum();
_
9
Andrii Polunin

コンバイナーの使用法を確認するための簡単なテストコード:

String[] strArray = {"abc", "mno", "xyz"};
List<String> strList = Arrays.asList(strArray);

System.out.println("stream test");
int streamResult = strList.stream().reduce(
        0, 
        (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, 
        (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "]"); return 1000000;}
    );
System.out.println("streamResult: " + streamResult);

System.out.println("parallelStream test");
int parallelStreamResult = strList.parallelStream().reduce(
        0, 
        (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, 
        (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "]"); return 1000000;}
    );
System.out.println("parallelStreamResult: " + parallelStreamResult);

System.out.println("parallelStream test2");
int parallelStreamResult2 = strList.parallelStream().reduce(
        0, 
        (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, 
        (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "] a+b[" + (a+b) + "]"); return a+b;}
    );
System.out.println("parallelStreamResult2: " + parallelStreamResult2);

出力:

stream test
accumulator: total[0] s[abc] s.codePointAt(0)[97]
accumulator: total[97] s[mno] s.codePointAt(0)[109]
accumulator: total[206] s[xyz] s.codePointAt(0)[120]
streamResult: 326
parallelStream test
accumulator: total[0] s[mno] s.codePointAt(0)[109]
accumulator: total[0] s[abc] s.codePointAt(0)[97]
accumulator: total[0] s[xyz] s.codePointAt(0)[120]
combiner: a[109] b[120]
combiner: a[97] b[1000000]
parallelStreamResult: 1000000
parallelStream test2
accumulator: total[0] s[mno] s.codePointAt(0)[109]
accumulator: total[0] s[xyz] s.codePointAt(0)[120]
accumulator: total[0] s[abc] s.codePointAt(0)[97]
combiner: a[109] b[120] a+b[229]
combiner: a[97] b[229] a+b[326]
parallelStreamResult2: 326
4
Lok