実装についてあまりfor文に頼ると少しコードが汚くなるなぁと感じていました。
高階関数のreduce使えばいくらかマシになったのでreduceについてまとめました。
for文をreduce関数で置き換える
以下のコードはある時系列データから指数平滑移動平均 1 を計算するコードです。
5日の平均として計算しています
var data = [1,2,3,4,5,6,7,8,9,10];
var n = 5;
function before(){
var res = [],
ema = 0,
sum = 0,
closeData = 0,
length = data.length;
for(var i = 0; i < length; i++) {
closeData = data[i];
if (closeData) {
sum += closeData;
if (i < n - 1) {
res.push(null);
} else if (i === n - 1) {
ema = sum / n;
res.push(ema);
} else {
ema = ema + 2 * (closeData - ema) / (n + 1);
res.push(ema);
}
} else {
res.push(null);
}
}
return res;
}
うううう汚い。
今までmap や filterの高階関数は使っていたのですが、上記のように前の計算を受け継ぐ様なロジックでは適用出来ないなぁと思い込んでいました。
しかしreduceの汎用性の高さがそれを乗り越えてくれる!!!
ということでreduce関数でfor文をなくしてみる
function after() {
return data.reduce(function (memo, num, index) {
var resValue = (function(){
if (num == null || index < n - 1) {
return null;
} else if (index == n - 1) {
return sum(data.slice(0, index + 1) ) / n;
} else {
var ema = memo[memo.length - 1];
return ema + 2 * (num - ema) / (n + 1);
}
})();
memo.push(resValue);
return memo;
}, []);
}
function sum(arr){
return arr.reduce(function(memo, num){return memo + num;}, 0);
}
一時変数が減ってぐっと分かりやすいコードになったかと思います。
上記のようにfor文より高階関数を適応することでコードの量はかなり減ります。(どやっ)
reduce 関数について
上記の例のようにreduce (他の言語ではfoldl(or foldr), injectなど)は汎用的で非常に強力な関数です。map や filterなどもreduceから作成する事ができます。
簡単に言えば一つ前の値と今の値を元に値を計算するという関数です。
ECMAScript5からは標準搭載のようです。IE8なくなりましたし丁度良し。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
参考
https://en.wikipedia.org/wiki/Fold_(higher-order_function)
reduce関数を定義通りに実装してみた
ここでは第一引数に対象の配列を入れます。
var reduce = function(array, f, initial){
switch (array.length) {
case 0:
return initial;
default:
var head = array[0];
var tail = array.slice(1);
return reduce(tail, f, f(initial, head));
}
};
ちなみに右から展開するreduceRightはこんな感じです
var reduceRight = function(array, f, initial){
switch (array.length) {
case 0:
return initial;
default:
var head = array[0];
var tail = array.slice(1);
return f(reduceRight(tail, f, initial), head); ←ここが違う!!
}
};
基本的に右から展開しようが、左から展開しようが関係ないです(恐らく・・・・)
ただし今回のようなテクニカルでは時系列に計算する必要が有るため必ず左から展開しなければなりません。
reduce関数からmapを作ってみる
var map = function(array, f){
return reduce(array, function(memo, value, index){
memo.push(f(value, index));
return memo
}, []);
};
まとめ
ついついfor文を使ってしまいがちですが、高階関数を使ったほうが無駄なコードを書かずに済みます。
またreduce関数の汎用性のおかげでほとんどの反復処理はreduceで処理できます。
ということでlet's 高階関数!!