reduce で集約する
合計から一歩でも外れると、手が止まる
数値の Stream には sum() が用意されているので、合計を出すだけなら悩みません。困るのは、そこから少し外れたときです。全部の文字列を 1 本につなぎたい。いちばん大きい値だけ欲しい。そういう「まとめ方」に、専用のメソッドは用意されていません。
かといって for に戻ると、また入れ物の変数から書き直しです。
Java
String all = "";
for (String tag : tags) {
all = all + tag;
}reduce は、この「変数を 1 つ持って、要素ごとに更新していく」形をそのままメソッドにしたものです。
Java
String all = tags.stream().reduce("", (acc, tag) -> acc + tag);引数は「始まりの値」と「2 つを 1 つにする式」
reduce に渡すものは 2 つです。1 つ目が始まりの値、2 つ目が「2 つ受け取って 1 つ返す式」です。
上の例では、acc に「ここまでの結果」が入り、tag に「次の要素」が入ります。式が返した値が、次の呼び出しの acc になります。tags が ["a", "b", "c"] なら、"" + "a" で "a"、"a" + "b" で "ab"、"ab" + "c" で "abc" という順に育っていきます。
同じ形で、最大値も書けます。
Java
int max = IntStream.of(scores)
.reduce(Integer.MIN_VALUE, (a, b) -> Math.max(a, b));a が「ここまでの最大」、b が「次の点数」で、大きい方を返し続ければ、最後に残るのが全体の最大です。合計も積も連結も最大値も、変わるのは式の中身だけで、外側の形はどれも同じです。
始まりの値は「通しても変わらない値」を選ぶ
始まりの値は、好きな数を入れてよいわけではありません。その演算にとって「通しても結果が変わらない値」を選びます。足し算なら 0 です。0 を足しても答えは変わらないからです。文字列の連結なら空文字列です。空文字列をつないでも中身は変わりません。
ここを外すと、結果が丸ごとずれます。連結の始まりの値に "-" を入れると、先頭に余計な - が付いた文字列が返ってきます。誰もその - を足していないのに、です。
この選び方には、もう 1 つ得があります。要素が 1 つも無いとき、reduce は始まりの値をそのまま返します。「通しても変わらない値」を選んでおけば、空の Stream でも自然に正しい答えになるので、空かどうかを調べる if が要りません。
どんな値を始まりに置くべきか迷ったら、要素が 1 個だけのときを考えてください。始まりの値とその 1 個を式に通して、その 1 個がそのまま返るなら正解です。
やってみよう
Solution.product(int[] arr) を完成させて、配列の全要素を掛け合わせた値を返してください。
Arrays.stream(arr) または IntStream.of(arr) で IntStream を作り、reduce に始まりの値と、2 つの数を掛ける式を渡します。for や自前の累積変数は使わないでください。
始まりの値は、掛け算にとって「通しても結果が変わらない値」を選びます。空配列を渡したときにその値がそのまま返るので、テストケースの期待値と見比べれば、選び方が合っているかどうかもすぐ分かります。
要件
Arrays.stream(arr)かIntStream.of(arr)でint[]をIntStreamに変換することreduce(1, (a, b) -> a * b)のようにidentityに1を渡して畳み込むこと (空配列でも1が返ることを保証する)forループや手動の累積変数を使わず、Stream API のreduceを使って書くこと
入出力例
product([1,2,3,4]) → 24
product([5]) → 5
product([]) → 1
product([2,3]) → 6
product([1,1,1,1,1]) → 1
product([2,5,10]) → 100