Refactor lazy evaluation.

This commit is contained in:
John-David Dalton
2014-09-30 23:35:00 -07:00
parent 072a5de1c5
commit fbd80746a5

278
lodash.js
View File

@@ -31,6 +31,10 @@
var HOT_COUNT = 150, var HOT_COUNT = 150,
HOT_SPAN = 16; HOT_SPAN = 16;
var LAZY_FILTER_FLAG = 1,
LAZY_MAP_FLAG = 2,
LAZY_WHILE_FLAG = 3;
/** Used as the TypeError message for "Functions" methods */ /** Used as the TypeError message for "Functions" methods */
var FUNC_ERROR_TEXT = 'Expected a function'; var FUNC_ERROR_TEXT = 'Expected a function';
@@ -4640,8 +4644,8 @@
*/ */
function wrapperValueOf() { function wrapperValueOf() {
var result = this.__wrapped__; var result = this.__wrapped__;
if (result instanceof lazyWrapper) { if (result instanceof LazyWrapper) {
return result.value(); result = result.value();
} }
var index = -1, var index = -1,
queue = this.__queue__, queue = this.__queue__,
@@ -4650,148 +4654,118 @@
while (++index < length) { while (++index < length) {
var args = [result], var args = [result],
data = queue[index], data = queue[index],
object = data[1]; object = data.object;
push.apply(args, data[2]); push.apply(args, data.args);
result = object[data[0]].apply(object, args); result = object[data.name].apply(object, args);
} }
return result; return result;
} }
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
function limitSource(operators, source) {
var len = operators.length,
min = 0,
max = source.length - 1;
for(var i = 0; i < len; i++) {
var op = operators[i];
switch(op.name) {
case 'take': max = Math.min(max, min + (op.count - 1)); break;
}
}
return {
min: min,
max: max
};
}
function resolveLazyWrapper(wrapper, source, sourceRange) {
var resultIndex = 0,
dir = wrapper.dir,
loops = sourceRange.max - sourceRange.min + 1,
resultLimit = Math.min(wrapper.limit, loops),
sourceIndex = (dir == 1 ? sourceRange.min : sourceRange.max) - dir,
result = [],
type = wrapper.type,
iterators = wrapper.iterators,
num = type.length;
lazy:
while (loops-- && resultIndex < resultLimit) {
sourceIndex += dir;
val = source[sourceIndex];
for(i = 0; i < num; i++) {
iterator = iterators[i];
switch(type[i]) {
// LazyWrapper.MAP_FLAG
case 1: val = iterator(val, sourceIndex, source); break;
// LazyWrapper.FILTER_FLAG
case 2: if(!iterator(val, sourceIndex, source)) { continue lazy; }
// LazyWrapper.WHILE_FLAG
case 3: if(!iterator(val, sourceIndex, source)) { break lazy; }
}
}
result[resultIndex++] = val;
}
return result;
}
function LazyWrapper(value) { function LazyWrapper(value) {
this.value = value; if (value instanceof LazyWrapper) {
this.queue = []; this.dir = value.dir;
this.iteratees = baseSlice(value.iteratees);
this.views = baseSlice(value.views);
this.wrapped = value.wrapped;
this.limit = POSITIVE_INFINITY; } else {
this.filterApplied = false; this.dir = 1;
this.dir = 1; this.iteratees = [];
} this.views = [];
this.wrapped = value;
LodashWrapper(value, chainAll, queue) { }
this.__chain__ = !!chainAll;
this.__queue__ = queue || [];
this.__wrapped__ = value;
}
/*
this.type = [];
this.iterators = [];
this.limitActions = [];
limitActions = [];
*/
function lazyClone() {
var clone = new LazyWrapper(this.source);
clone.type = this.type.concat();
clone.iterators = this.iterators.concat();
clone.limit = this.limit;
clone.limitActions = this.limitActions.concat();
clone.filterApplied = this.filterApplied;
clone.dir = this.dir;
return clone;
} }
function lazyFilter(predicate, thisArg) { function lazyFilter(predicate, thisArg) {
predicate = getCallback(predicate, thisArg, 3); predicate = getCallback(predicate, thisArg, 3);
var fork = this.clone(); var result = new LazyWrapper(this);
fork.filterApplied = true; result.iteratees.push({ 'type': LAZY_FILTER_FLAG, 'iteratee': predicate });
[methodName, object, arguments] return result;
fork.type.push(LazyWrapper.FILTER_FLAG);
fork.iterators.push(predicate);
return fork;
} }
function lazyFirst() { function lazyFirst() {
return this.take(1).value()[0]; return this.take(1).value()[0];
} }
function lazyMap(iterator, thisArg) { function lazyMap(iteratee, thisArg) {
iterator = getCallback(iterator, thisArg, 3); iteratee = getCallback(iteratee, thisArg, 3);
var result = this.clone(); var result = new LazyWrapper(this);
result.queue.push([iterator); result.iteratees.push({ 'type': LAZY_MAP_FLAG, 'iteratee': iteratee });
fork.type.push(LazyWrapper.MAP_FLAG); return result;
return fork;
} }
function lazyTake(n) { function lazyTake(n) {
var fork = this.clone(); n = n == null ? 1 : n;
n = (n == null) ? 1 : n;
if(fork.filterApplied) { var result = new LazyWrapper(this);
fork.limit = n; result.views.push({ 'type': 'take', 'size': n });
return new LazyWrapper(fork); return result;
}
fork.limitActions.push(getLimitAction('take', n, this.dir));
return fork;
} }
function lazyValue() { function lazyValue() {
var source = this.source, var array = this.wrapped;
sourceLimit; if (array instanceof LodashWrapper || array instanceof LazyWrapper) {
array = array.value();
if (source instanceof LazyWrapper) {
source = source.value();
} }
sourceLimit = limitSource(this.limitActions, source); var start = 0,
console.log('sourceLimit', sourceLimit) end = array.length,
return resolveLazyWrapper(this, source, sourceLimit); length = end,
views = this.views,
viewsLength = views.length;
while (viewsLength--) {
var view = views[viewsLength],
size = view.size;
switch (view.type) {
case 'take': end = nativeMin(end, start + size); break;
case 'takeRight': start = nativeMax(start, end - size); break;
case 'drop': start += size; break;
case 'dropRight': end -= size; break;
}
}
var dir = this.dir,
index = (dir == 1 ? start : end) - dir,
iteratees = this.iteratees,
iterateesLength = iteratees.length,
resLimit = end - start,
result = [];
outer:
while (length-- && result.length < resLimit) {
index += dir;
var iterateesIndex = -1,
value = array[index];
while (++iterateesIndex < iterateesLength) {
var data = iteratees[iterateesIndex],
iteratee = data.iteratee;
switch (data.type) {
case LAZY_FILTER_FLAG:
if (!iteratee(value, index, array)) {
continue outer;
}
break;
case LAZY_MAP_FLAG:
value = iteratee(value, index, array);
break;
case LAZY_WHILE_FLAG:
if (!iteratee(value, index, array)) {
break outer;
}
}
}
result.push(value);
}
return result;
} }
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
@@ -9259,8 +9233,8 @@
return function() { return function() {
if (chain || this.__chain__) { if (chain || this.__chain__) {
var result = object(this.__wrapped__); var result = object(this.__wrapped__);
result.__chain__ = this.__chain__; result.__chain__ = true;
(result.__queue__ = baseSlice(this.__queue__)).push([methodName, object, arguments]); (result.__queue__ = baseSlice(this.__queue__)).push({ 'name': methodName, 'object': object, 'args': arguments });
return result; return result;
} }
var args = [this.value()]; var args = [this.value()];
@@ -9849,24 +9823,47 @@
lodash[methodName].placeholder = lodash; lodash[methodName].placeholder = lodash;
}); });
// ensure `new LodashWrapper` is an instance of `lodash`
LodashWrapper.prototype = lodash.prototype
// add functions to the memoize cache
MemCache.prototype.get = memGet;
MemCache.prototype.has = memHas;
MemCache.prototype.set = memSet;
memoize.Cache = MemCache;
// add functions to the lazy wrapper
LazyWrapper.prototype.filter = lazyFilter;
LazyWrapper.prototype.first = lazyFirst;
LazyWrapper.prototype.map = lazyMap;
LazyWrapper.prototype.take = lazyTake;
LazyWrapper.prototype.value = lazyValue;
// add `LazyWrapper` functions // add `LazyWrapper` functions
arrayEach(['map', 'filter', 'first', 'take'], function(methodName) { arrayEach(['map', 'filter', 'take'], function(methodName) {
var func = LazyWrapper.prototype[methodName]; var func = LazyWrapper.prototype[methodName];
var overriddenFunc = lodash.prototype[methodName];
lodash.prototype[methodName] = function() { lodash.prototype[methodName] = function() {
var isLazy = wrapped instanceof LazyWrapper, var value = this.__wrapped__,
value = this.__wrapped__; isLazy = value instanceof LazyWrapper;
var result = isLazy if (!isLazy && !isArray(value)) {
? overriddenFunc.apply(this, arguments) var args = [this.value()];
: func.apply(inLazyChain ? wrapped : new LazyWrapper(wrapped), arguments); push.apply(args, arguments);
value = lodash[methodName].apply(lodash, args);
return (wrapped instanceof LazyWrapper || this.__chain__) } else {
? new LodashWrapper(wrapped, this.__chain__) value = func.apply(isLazy ? value : new LazyWrapper(this), arguments);
: wrapped; isLazy = true;
}
return (isLazy || this.__chain__) ? new LodashWrapper(value, true) : value;
}; };
}); });
// add "Chaining" functions to the lodash wrapper
lodash.prototype.chain = wrapperChain;
lodash.prototype.toString = wrapperToString;
lodash.prototype.toJSON = lodash.prototype.value = lodash.prototype.valueOf = wrapperValueOf;
// add `Array.prototype` functions // add `Array.prototype` functions
arrayEach(['concat', 'join', 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(methodName) { arrayEach(['concat', 'join', 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
var arrayFunc = arrayProto[methodName], var arrayFunc = arrayProto[methodName],
@@ -9895,31 +9892,10 @@
}; };
}); });
// add functions to the lazy wrapper
LazyWrapper.prototype.clone = lazyClone;
LazyWrapper.prototype.first = lazyFirst;
LazyWrapper.prototype.map = lazyMap;
LazyWrapper.prototype.take = lazyTake;
LazyWrapper.prototype.value = lazyValue;
// ensure `new LodashWrapper` is an instance of `lodash`
LodashWrapper.prototype = lodash.prototype;
// add functions to the memoize cache
MemCache.prototype.get = memGet;
MemCache.prototype.has = memHas;
MemCache.prototype.set = memSet;
memoize.Cache = MemCache;
// add function aliases to the lodash wrapper // add function aliases to the lodash wrapper
lodash.prototype.collect = lodash.prototype.map; lodash.prototype.collect = lodash.prototype.map;
lodash.prototype.select = lodash.prototype.filter; lodash.prototype.select = lodash.prototype.filter;
// add "Chaining" functions to the lodash wrapper
lodash.prototype.chain = wrapperChain;
lodash.prototype.toString = wrapperToString;
lodash.prototype.toJSON = lodash.prototype.value = lodash.prototype.valueOf = wrapperValueOf;
return lodash; return lodash;
} }