長らく鎖国を続けてきた名詞の王国も
ついに動詞を受け入れるみたいですね~
こんにちわ、ちかです
さて
今回は
reduce という関数を紹介しつつ
好き勝手に言いたいことを言うつもりですw
今回の記事は王国の事情とは何の関係もなく書いたものですが
reduce も王国に入るらしいので結果オーライ!
はじめに
というわけで
畳み込み関数 reduce を紹介しようと思います。
reduce は多くのプログラミング言語の標準ライブラリにひっそりと組み込まれている関数です。
ライブラリによって関数名は reduce じゃなかったりします。
Python | reduce() |
JavaScript | reduce() |
C++/STL | std::accumulate() |
.NET Framework/LINQ | Enumerable.Aggregate() |
などなど。
導入
reduce は 3 つの引数を取ります。
2 引数関数 | callback(previousResult, currentValue) |
リスト | values = [values[0], values[1], ... , values[n]] |
初期値 | initialValue |
で
たとえば
↓こんな計算↓ をします。
式 | callback 関数定義 | 結果 (を計算する式) |
---|---|---|
reduce(add, [2,3,4,5,6], 0) | add(previousResult, currentValue) = previousResult + currentValue |
(((((0+2)+3)+4)+5)+6) |
reduce(multiply, [2,3,4,5,6], 1) | multiply(previousResult, currentValue) = previousResult * currentValue |
(((((1*2)*3)*4)*5)*6) |
reduce(either, [flag1,flag2,flag3], false) | either(previousResult, currentValue) = previousResult or currentValue |
(((false or flag1) or flag2) or flag3) |
reduce(both, [flag1,flag2,flag3], true) | both(previousResult, currentValue) = previousResult and currentValue |
(((true and flag1) and flag2) and flag3) |
reduce がやっていることはこんな感じです:
・ リストを走査して callback を呼びまくる。
- currentValue には現在の要素を渡す。
- previousResult には前回の callback の戻り値を渡す。ただし初回は initialValue を渡す。
・ 最後の callback の戻り値を返す。
表現を変えれば:
・ 再帰的定義
reduce(callback, [], initialValue) = initialValue reduce(callback, [values[0], ... ], initialValue) = reduce(callback, [ ... ], callback(initialValue, values[0]))
・ 展開した式
reduce(callback, [values[0], values[1], ... , values[n-1], values[n]], initialValue) = callback( callback( ... callback( callback(initialValue, values[0]), values[1]), ... , values[n-1]), values[n])
・ ループによる実装
def reduce(callback, values, initialValue): result = initialValue for value in values: result = callback(result, value) return result
つかってみる
reduce はブラウザ上でも試すことができます。※IE9以上
JavaScript の reduce は配列 Array のメソッドになっていて
values.reduce(callback, initialValue) という形で呼び出します。
ということで
アドレスバーに次のように入力します。
※ブラウザによってはコピペすると最初の "javascript:" が消えるので書き足す必要があります。
javascript:alert([2,3,4,5,6].reduce(function(previousResult, currentValue){ return previousResult * currentValue; }, 1));
(1 * 2 * 3 * 4 * 5 * 6) の計算結果 720 が表示されたでしょうか??
reduce は文字列の連結にも使えます。
次は文字列配列の要素をダブルクォートで括って & 区切りで連結する例です。
javascript:alert(['abc','de','fghi'].reduce(function(previousResult, currentValue){ return previousResult + '&"' + currentValue + '"'; }, '').slice(1));
引数 initialValue を省略した場合は『 ... callback(callback(values[0], values[1]), values[2]), ... 』が返ります。
次は最大値を求める例です。
javascript:alert([4,7,3,1,6].reduce(function(previousResult, currentValue){ return previousResult < currentValue ? currentValue : previousResult; }));
つかえそう??
だんだん見えてきたでしょうか??
おもしろそうに見えてきたでしょうか??
⇒ どこが便利なの?? なにが嬉しいの?? と思うひとへ。
あなたの感覚は全く正しいです。
この記事はあなたには必要なさそうです。
⇒ おもしろそうに見えてきたひとへ。
じゃあ次は
reduce で (12 + 22 + ... + 102) を求めてみましょっか。
javascript:alert([1,2,3,4,5,6,7,8,9,10].reduce(function(previousResult, currentValue){ ここを変えましょう }, 0));
できました??
昔よく見かけた「選択すると答えが浮かび上がるよ」を用意しましたよ。
javascript:alert([1,2,3,4,5,6,7,8,9,10].reduce(function(previousResult, currentValue){ return previousResult + (currentValue * currentValue); }, 0));
いい頭の体操になったのではないでしょうか??
結論
reduce を紹介してきましたが
reduce の紹介はこの記事の目的ではありません。
この記事の目的は 私が好き勝手に言いたいことを言う ことです。
ふぅ。
さて。
言います。
reduce は使わずループで書くべし!
聞こえました??
勝手に紹介しておいて使うなとはナニゴトじゃ!
というあなたのお怒りのも当然ですが
私は
reduce を読めるようにはなってほしいけど
reduce を書いてほしいわけじゃありません。
ソースコードを眺めていく中で
return values.reduce(function(previousResult, currentValue){ return previousResult + (currentValue * currentValue); }, 0);
とか
return std::accumulate(values.begin(), values.end(), 0.0, [](const double& previousResult, const double& currentValue){ return previousResult + (currentValue * currentValue); } );
とかが出てきたら
パッと見では何をしているのか分からないひとが多いと思います。
reduce は頭の体操であり、ボケ防止であり、
採用時の「ふるい」には使えるかもしれないけど、
みんなが読むコードに入れるのは微妙です。
分散コンピューティングの世界では必要不可欠なのかもしれませんが
業務アプリのちょっとしたループ処理を reduce で記述するのは可読性を落とすだけかと思います。
ループで書いちゃえばいいんです。
なんにも難しいことはないでしょう。
var sumOfSquares = 0; for (var i = 0; i < values.length; i++) { sumOfSquares += values[i] * values[i]; } return sumOfSquares;
どうしても使いたいなら
最低でもコメントは書きましょうね。。
// values の平方和 (values[0]^2 + values[1]^2 + ・・・ + values[n]^2) を返す return values.reduce(function(previousResult, currentValue){ return previousResult + (currentValue * currentValue); }, 0);
むかしばなし
実は昔
私自身
「ループを reduce で書きたくなる」症候群に陥ったことがあります。
なぜ関数プログラミングは重要かという論文を読むと
再帰万歳! reduce 万歳! 関数型言語万歳! みたいな気持ちになるんです。
たぶん、どうやら、なんとなく、
ちょっと頭がよくなったような気がするんですよね、
関数プログラミングとか reduce とかって。
でもそれはカンチガイです。
頭がよくなったのではなく
調子に乗っただけのことです。
さいごに
ポール・グレアムが(LISP は) 技術じゃなくて数学だったって言ってるのと同じ意味で
reduce は数学なのだろうと思います。
技術は必要に応じて数学を取り入れますが、数学に縛られたりはしません。
クリスマスプレゼントに金槌をもらった子どもは、何でも叩きたがる。
(金槌の法則 ―― ジェラルド・M・ワインバーグ)
これはおもに私への戒めですが
武器を新しく手に入れても
やたらめったら使わずに
ちゃんと用途を考えましょう。
でわでわ
p.s. (←しつこい)
reduce について理解できたひと!
こんな会社が採用活動してますよ!
https://www.facebook.com/park.corp.recruit