underscoreに詳しくないので、勉強半分でソースコードを読む。
(lodashも読もうと思っていたもののアドベントカレンダー風に1日で2つを比較して読む能力がなかったため方針変更)
利用するバージョン
reduceとは
underscorejs.orgのreduce
こんな説明。
####reduce_.reduce(list, iteratee, [memo], [context]) Aliases: inject, foldl
Also known as inject and foldl,
reduce boils down a list of values into a single value.
Memo is the initial state of the reduction, and each successive step of it should be returned by iteratee.
The iteratee is passed four arguments: the memo,then the value and index (or key) of the iteration, and finally a reference to the entire list.
If no memo is passed to the initial invocation of reduce, the iteratee is not invoked on the first element of the list.
The first element is instead passed as the memo in the invocation of the iteratee on the next element in the list.
var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0);
=> 6
injectやfoldlにエイリアスが張られている。
reduceはリストの値を1つの値に集約して返してくれる関数である。
Memoはreductionの初期状態である(?) //初期値をiterateeに持っていけるってこと? そして各ステップ(//たぶんiterateeを回すごと)において、iterateeによってreturnされるべきだ
iterateeには4つの値が渡される(//たぶんいつもわたしている3つ+memo?)iterateeはリストの最初のelementの場合は呼びだされません。
最初のelementは単にpassするのではなく、memoとして次のelementのiterateeで渡されます。
つまり
・reduceはarray/objectを1つの値にまとめて返却してくれる関数
・returnする値の初期値はmemoとして渡せる
・最初の値に対してiterateeは適応されないが、最初の値はmemoとして利用される
ということ?
//??? contextはどこにきえた
なので
こうすると
'use strict';
var _ = require( "underscore" );
var arr = [ 1, 3, 5 ];
var memo = 5;
//arr = { "a": 1, "b": 2, "c": 3 };
var result = _.reduce( arr, function( _memo, num ) {
console.log( "memo:" + memo );
console.log( "_memo:" + _memo );
console.log( "num:" + num );
console.log( "--------------------" );
_memo = _memo + num;
return _memo;
}, memo );
console.log( result );
こうなる
node reduce.js
memo:5
_memo:5
num:1
--------------------
memo:5
_memo:6
num:3
--------------------
memo:5
_memo:9
num:5
--------------------
14
underscore.reduce
コード的にはこのあたり。
_.reduce = _.foldl = _.inject = createReduce(1);
そのまますぐ上のここ
// Create a reducing function iterating left or right.
function createReduce(dir) {
// Optimized iterator function as using arguments.length
// in the main function will deoptimize the, see #1991.
function iterator(obj, iteratee, memo, keys, index, length) {
for (; index >= 0 && index < length; index += dir) {
var currentKey = keys ? keys[index] : index;
memo = iteratee(memo, obj[currentKey], currentKey, obj);
}
return memo;
}
return function(obj, iteratee, memo, context) {
iteratee = optimizeCb(iteratee, context, 4);
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length,
index = dir > 0 ? 0 : length - 1;
// Determine the initial value if none is provided.
if (arguments.length < 3) {
memo = obj[keys ? keys[index] : index];
index += dir;
}
return iterator(obj, iteratee, memo, keys, index, length);
};
}
1与えているのは、reduceRightとの使い分けのため
reduceRightでは
_.reduceRight = _.foldr = createReduce(-1);
と与えている。
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length,
index = dir > 0 ? 0 : length - 1;
のあたりは他のcollection系の関数と同様に、arrayだったらfalse,objectだったらkeyの配列を返し、その配列の長さを代入、
indexは0 //引数のdirが1なため reduceRightの場合は length -1
if (arguments.length < 3) {
memo = obj[keys ? keys[index] : index];
index += dir;
}
この部分は、配列の引数が2以下だった場合(つまりmemo = undefinedだった場合)、初期値のもととなるmemoが存在しないため、これを決定する。
配列だった場合はindexを、配列じゃなかった場合はindex[0]を初期値にする
初期値決定後はindex += dirする(reduceの場合はindex++)
その後、iteratorに引き継いで回す