Update cache implementations.

This commit is contained in:
John-David Dalton
2016-04-22 20:45:16 -07:00
parent e2c86dac63
commit 4e38f70e0e
2 changed files with 269 additions and 230 deletions

489
lodash.js
View File

@@ -920,6 +920,18 @@
}); });
} }
/**
* Checks if a cache value for `key` exists.
*
* @private
* @param {Object} cache The cache to query.
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function cacheHas(cache, key) {
return cache.has(key);
}
/** /**
* Used by `_.trim` and `_.trimStart` to get the index of the first string symbol * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
* that is not found in the character symbols. * that is not found in the character symbols.
@@ -1661,64 +1673,202 @@
* *
* @private * @private
* @constructor * @constructor
* @returns {Object} Returns the new hash object. * @param {Array} [entries] The key-value pairs to cache.
*/ */
function Hash() {} function Hash(entries) {
var index = -1,
length = entries ? entries.length : 0;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the hash.
*
* @name clear
* @memberOf Hash
*/
function hashClear() {
this.__data__ = nativeCreate ? nativeCreate(null) : {};
}
/** /**
* Removes `key` and its value from the hash. * Removes `key` and its value from the hash.
* *
* @private * @name delete
* @memberOf Hash
* @param {Object} hash The hash to modify. * @param {Object} hash The hash to modify.
* @param {string} key The key of the value to remove. * @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`. * @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/ */
function hashDelete(hash, key) { function hashDelete(key) {
return hashHas(hash, key) && delete hash[key]; return this.has(key) && delete this.__data__[key];
} }
/** /**
* Gets the hash value for `key`. * Gets the hash value for `key`.
* *
* @private * @name get
* @param {Object} hash The hash to query. * @memberOf Hash
* @param {string} key The key of the value to get. * @param {string} key The key of the value to get.
* @returns {*} Returns the entry value. * @returns {*} Returns the entry value.
*/ */
function hashGet(hash, key) { function hashGet(key) {
var data = this.__data__;
if (nativeCreate) { if (nativeCreate) {
var result = hash[key]; var result = data[key];
return result === HASH_UNDEFINED ? undefined : result; return result === HASH_UNDEFINED ? undefined : result;
} }
return hasOwnProperty.call(hash, key) ? hash[key] : undefined; return hasOwnProperty.call(data, key) ? data[key] : undefined;
} }
/** /**
* Checks if a hash value for `key` exists. * Checks if a hash value for `key` exists.
* *
* @private * @name has
* @param {Object} hash The hash to query. * @memberOf Hash
* @param {string} key The key of the entry to check. * @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/ */
function hashHas(hash, key) { function hashHas(key) {
return nativeCreate ? hash[key] !== undefined : hasOwnProperty.call(hash, key); var data = this.__data__;
return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key);
} }
/** /**
* Sets the hash `key` to `value`. * Sets the hash `key` to `value`.
* *
* @private * @name set
* @param {Object} hash The hash to modify. * @memberOf Hash
* @param {string} key The key of the value to set. * @param {string} key The key of the value to set.
* @param {*} value The value to set. * @param {*} value The value to set.
* @returns {Object} Returns the hash instance.
*/ */
function hashSet(hash, key, value) { function hashSet(key, value) {
hash[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; var data = this.__data__;
data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
return this;
} }
// Avoid inheriting from `Object.prototype` when possible. // Add methods to `Hash`.
Hash.prototype = nativeCreate ? nativeCreate(null) : objectProto; Hash.prototype.clear = hashClear;
Hash.prototype['delete'] = hashDelete;
Hash.prototype.get = hashGet;
Hash.prototype.has = hashHas;
Hash.prototype.set = hashSet;
/*------------------------------------------------------------------------*/
/**
* Creates an list cache object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function ListCache(entries) {
var index = -1,
length = entries ? entries.length : 0;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the list cache.
*
* @name clear
* @memberOf ListCache
*/
function listCacheClear() {
this.__data__ = [];
}
/**
* Removes `key` and its value from the list cache.
*
* @name delete
* @memberOf ListCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function listCacheDelete(key) {
var data = this.__data__,
index = assocIndexOf(data, key);
if (index < 0) {
return false;
}
var lastIndex = data.length - 1;
if (index == lastIndex) {
data.pop();
} else {
splice.call(data, index, 1);
}
return true;
}
/**
* Gets the list cache value for `key`.
*
* @name get
* @memberOf ListCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function listCacheGet(key) {
var data = this.__data__,
index = assocIndexOf(data, key);
return index < 0 ? undefined : data[index][1];
}
/**
* Checks if a list cache value for `key` exists.
*
* @name has
* @memberOf ListCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function listCacheHas(key) {
return assocIndexOf(this.__data__, key) > -1;
}
/**
* Sets the list cache `key` to `value`.
*
* @name set
* @memberOf ListCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the list cache instance.
*/
function listCacheSet(key, value) {
var data = this.__data__,
index = assocIndexOf(data, key);
if (index < 0) {
data.push([key, value]);
} else {
data[index][1] = value;
}
return this;
}
// Add methods to `ListCache`.
ListCache.prototype.clear = listCacheClear;
ListCache.prototype['delete'] = listCacheDelete;
ListCache.prototype.get = listCacheGet;
ListCache.prototype.has = listCacheHas;
ListCache.prototype.set = listCacheSet;
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
@@ -1727,15 +1877,15 @@
* *
* @private * @private
* @constructor * @constructor
* @param {Array} [values] The values to cache. * @param {Array} [entries] The key-value pairs to cache.
*/ */
function MapCache(values) { function MapCache(entries) {
var index = -1, var index = -1,
length = values ? values.length : 0; length = entries ? entries.length : 0;
this.clear(); this.clear();
while (++index < length) { while (++index < length) {
var entry = values[index]; var entry = entries[index];
this.set(entry[0], entry[1]); this.set(entry[0], entry[1]);
} }
} }
@@ -1743,14 +1893,13 @@
/** /**
* Removes all key-value entries from the map. * Removes all key-value entries from the map.
* *
* @private
* @name clear * @name clear
* @memberOf MapCache * @memberOf MapCache
*/ */
function mapClear() { function mapCacheClear() {
this.__data__ = { this.__data__ = {
'hash': new Hash, 'hash': new Hash,
'map': Map ? new Map : [], 'map': new (Map || ListCache),
'string': new Hash 'string': new Hash
}; };
} }
@@ -1764,82 +1913,60 @@
* @param {string} key The key of the value to remove. * @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`. * @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/ */
function mapDelete(key) { function mapCacheDelete(key) {
var data = this.__data__; return getMapData(this, key)['delete'](key);
if (isKeyable(key)) {
return hashDelete(typeof key == 'string' ? data.string : data.hash, key);
}
return Map ? data.map['delete'](key) : assocDelete(data.map, key);
} }
/** /**
* Gets the map value for `key`. * Gets the map value for `key`.
* *
* @private
* @name get * @name get
* @memberOf MapCache * @memberOf MapCache
* @param {string} key The key of the value to get. * @param {string} key The key of the value to get.
* @returns {*} Returns the entry value. * @returns {*} Returns the entry value.
*/ */
function mapGet(key) { function mapCacheGet(key) {
var data = this.__data__; return getMapData(this, key).get(key);
if (isKeyable(key)) {
return hashGet(typeof key == 'string' ? data.string : data.hash, key);
}
return Map ? data.map.get(key) : assocGet(data.map, key);
} }
/** /**
* Checks if a map value for `key` exists. * Checks if a map value for `key` exists.
* *
* @private
* @name has * @name has
* @memberOf MapCache * @memberOf MapCache
* @param {string} key The key of the entry to check. * @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/ */
function mapHas(key) { function mapCacheHas(key) {
var data = this.__data__; return getMapData(this, key).has(key);
if (isKeyable(key)) {
return hashHas(typeof key == 'string' ? data.string : data.hash, key);
}
return Map ? data.map.has(key) : assocHas(data.map, key);
} }
/** /**
* Sets the map `key` to `value`. * Sets the map `key` to `value`.
* *
* @private
* @name set * @name set
* @memberOf MapCache * @memberOf MapCache
* @param {string} key The key of the value to set. * @param {string} key The key of the value to set.
* @param {*} value The value to set. * @param {*} value The value to set.
* @returns {Object} Returns the map cache instance. * @returns {Object} Returns the map cache instance.
*/ */
function mapSet(key, value) { function mapCacheSet(key, value) {
var data = this.__data__; getMapData(this, key).set(key, value);
if (isKeyable(key)) {
hashSet(typeof key == 'string' ? data.string : data.hash, key, value);
} else if (Map) {
data.map.set(key, value);
} else {
assocSet(data.map, key, value);
}
return this; return this;
} }
// Add methods to `MapCache`. // Add methods to `MapCache`.
MapCache.prototype.clear = mapClear; MapCache.prototype.clear = mapCacheClear;
MapCache.prototype['delete'] = mapDelete; MapCache.prototype['delete'] = mapCacheDelete;
MapCache.prototype.get = mapGet; MapCache.prototype.get = mapCacheGet;
MapCache.prototype.has = mapHas; MapCache.prototype.has = mapCacheHas;
MapCache.prototype.set = mapSet; MapCache.prototype.set = mapCacheSet;
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
/** /**
* *
* Creates a set cache object to store unique values. * Creates an array cache object to store unique values.
* *
* @private * @private
* @constructor * @constructor
@@ -1851,52 +1978,39 @@
this.__data__ = new MapCache; this.__data__ = new MapCache;
while (++index < length) { while (++index < length) {
this.push(values[index]); this.add(values[index]);
} }
} }
/** /**
* Checks if `value` is in `cache`. * Adds `value` to the array cache.
* *
* @private * @name add
* @param {Object} cache The set cache to search. * @memberOf SetCache
* @alias push
* @param {*} value The value to cache.
* @returns {Object} Returns the cache instance.
*/
function setCacheAdd(value) {
this.__data__.set(value, HASH_UNDEFINED);
return this;
}
/**
* Checks if `value` is in the array cache.
*
* @name has
* @memberOf SetCache
* @param {*} value The value to search for. * @param {*} value The value to search for.
* @returns {number} Returns `true` if `value` is found, else `false`. * @returns {number} Returns `true` if `value` is found, else `false`.
*/ */
function cacheHas(cache, value) { function setCacheHas(value) {
var map = cache.__data__; return this.__data__.has(value);
if (isKeyable(value)) {
var data = map.__data__,
hash = typeof value == 'string' ? data.string : data.hash;
return hash[value] === HASH_UNDEFINED;
}
return map.has(value);
}
/**
* Adds `value` to the set cache.
*
* @private
* @name push
* @memberOf SetCache
* @param {*} value The value to cache.
*/
function cachePush(value) {
var map = this.__data__;
if (isKeyable(value)) {
var data = map.__data__,
hash = typeof value == 'string' ? data.string : data.hash;
hash[value] = HASH_UNDEFINED;
}
else {
map.set(value, HASH_UNDEFINED);
}
} }
// Add methods to `SetCache`. // Add methods to `SetCache`.
SetCache.prototype.push = cachePush; SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
SetCache.prototype.has = setCacheHas;
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
@@ -1905,82 +2019,61 @@
* *
* @private * @private
* @constructor * @constructor
* @param {Array} [values] The values to cache. * @param {Array} [entries] The key-value pairs to cache.
*/ */
function Stack(values) { function Stack(entries) {
var index = -1, this.__data__ = new ListCache(entries);
length = values ? values.length : 0;
this.clear();
while (++index < length) {
var entry = values[index];
this.set(entry[0], entry[1]);
}
} }
/** /**
* Removes all key-value entries from the stack. * Removes all key-value entries from the stack.
* *
* @private
* @name clear * @name clear
* @memberOf Stack * @memberOf Stack
*/ */
function stackClear() { function stackClear() {
this.__data__ = { 'array': [], 'map': null }; this.__data__ = new ListCache;
} }
/** /**
* Removes `key` and its value from the stack. * Removes `key` and its value from the stack.
* *
* @private
* @name delete * @name delete
* @memberOf Stack * @memberOf Stack
* @param {string} key The key of the value to remove. * @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`. * @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/ */
function stackDelete(key) { function stackDelete(key) {
var data = this.__data__, return this.__data__['delete'](key);
array = data.array;
return array ? assocDelete(array, key) : data.map['delete'](key);
} }
/** /**
* Gets the stack value for `key`. * Gets the stack value for `key`.
* *
* @private
* @name get * @name get
* @memberOf Stack * @memberOf Stack
* @param {string} key The key of the value to get. * @param {string} key The key of the value to get.
* @returns {*} Returns the entry value. * @returns {*} Returns the entry value.
*/ */
function stackGet(key) { function stackGet(key) {
var data = this.__data__, return this.__data__.get(key);
array = data.array;
return array ? assocGet(array, key) : data.map.get(key);
} }
/** /**
* Checks if a stack value for `key` exists. * Checks if a stack value for `key` exists.
* *
* @private
* @name has * @name has
* @memberOf Stack * @memberOf Stack
* @param {string} key The key of the entry to check. * @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/ */
function stackHas(key) { function stackHas(key) {
var data = this.__data__, return this.__data__.has(key);
array = data.array;
return array ? assocHas(array, key) : data.map.has(key);
} }
/** /**
* Sets the stack `key` to `value`. * Sets the stack `key` to `value`.
* *
* @private
* @name set * @name set
* @memberOf Stack * @memberOf Stack
* @param {string} key The key of the value to set. * @param {string} key The key of the value to set.
@@ -1988,21 +2081,11 @@
* @returns {Object} Returns the stack cache instance. * @returns {Object} Returns the stack cache instance.
*/ */
function stackSet(key, value) { function stackSet(key, value) {
var data = this.__data__, var cache = this.__data__;
array = data.array; if (cache instanceof ListCache && cache.__data__.length == LARGE_ARRAY_SIZE) {
cache = this.__data__ = new MapCache(cache.__data__);
if (array) {
if (array.length < (LARGE_ARRAY_SIZE - 1)) {
assocSet(array, key, value);
} else {
data.array = null;
data.map = new MapCache(array);
}
}
var map = data.map;
if (map) {
map.set(key, value);
} }
cache.set(key, value);
return this; return this;
} }
@@ -2015,90 +2098,6 @@
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
/**
* Removes `key` and its value from the associative array.
*
* @private
* @param {Array} array The array to modify.
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function assocDelete(array, key) {
var index = assocIndexOf(array, key);
if (index < 0) {
return false;
}
var lastIndex = array.length - 1;
if (index == lastIndex) {
array.pop();
} else {
splice.call(array, index, 1);
}
return true;
}
/**
* Gets the associative array value for `key`.
*
* @private
* @param {Array} array The array to query.
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function assocGet(array, key) {
var index = assocIndexOf(array, key);
return index < 0 ? undefined : array[index][1];
}
/**
* Checks if an associative array value for `key` exists.
*
* @private
* @param {Array} array The array to query.
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function assocHas(array, key) {
return assocIndexOf(array, key) > -1;
}
/**
* Gets the index at which the `key` is found in `array` of key-value pairs.
*
* @private
* @param {Array} array The array to search.
* @param {*} key The key to search for.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function assocIndexOf(array, key) {
var length = array.length;
while (length--) {
if (eq(array[length][0], key)) {
return length;
}
}
return -1;
}
/**
* Sets the associative array `key` to `value`.
*
* @private
* @param {Array} array The array to modify.
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
*/
function assocSet(array, key, value) {
var index = assocIndexOf(array, key);
if (index < 0) {
array.push([key, value]);
} else {
array[index][1] = value;
}
}
/*------------------------------------------------------------------------*/
/** /**
* Used by `_.defaults` to customize its `_.assignIn` use. * Used by `_.defaults` to customize its `_.assignIn` use.
* *
@@ -2151,6 +2150,24 @@
} }
} }
/**
* Gets the index at which the `key` is found in `array` of key-value pairs.
*
* @private
* @param {Array} array The array to search.
* @param {*} key The key to search for.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function assocIndexOf(array, key) {
var length = array.length;
while (length--) {
if (eq(array[length][0], key)) {
return length;
}
}
return -1;
}
/** /**
* Aggregates elements of `collection` on `accumulator` with keys transformed * Aggregates elements of `collection` on `accumulator` with keys transformed
* by `iteratee` and values set by `setter`. * by `iteratee` and values set by `setter`.
@@ -4957,9 +4974,7 @@
* @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
*/ */
function equalArrays(array, other, equalFunc, customizer, bitmask, stack) { function equalArrays(array, other, equalFunc, customizer, bitmask, stack) {
var index = -1, var isPartial = bitmask & PARTIAL_COMPARE_FLAG,
isPartial = bitmask & PARTIAL_COMPARE_FLAG,
isUnordered = bitmask & UNORDERED_COMPARE_FLAG,
arrLength = array.length, arrLength = array.length,
othLength = other.length; othLength = other.length;
@@ -4971,7 +4986,10 @@
if (stacked) { if (stacked) {
return stacked == other; return stacked == other;
} }
var result = true; var index = -1,
result = true,
seen = (bitmask & UNORDERED_COMPARE_FLAG) ? new SetCache : undefined;
stack.set(array, other); stack.set(array, other);
// Ignore non-index properties. // Ignore non-index properties.
@@ -5259,6 +5277,21 @@
*/ */
var getLength = baseProperty('length'); var getLength = baseProperty('length');
/**
* Gets the data for `map`.
*
* @private
* @param {Object} map The map to query.
* @param {string} key The reference key.
* @returns {*} Returns the map data.
*/
function getMapData(map, key) {
var data = map.__data__;
return isKeyable(key)
? data[typeof key == 'string' ? 'string' : 'hash']
: data.map;
}
/** /**
* Gets the property names, values, and compare flags of `object`. * Gets the property names, values, and compare flags of `object`.
* *

View File

@@ -901,7 +901,7 @@
var iteration = 0, var iteration = 0,
objects = [{ 'a': 1 }, { 'a': 2 }], objects = [{ 'a': 1 }, { 'a': 2 }],
stack = { '__data__': { 'array': [[objects[0], objects[1]]], 'map': null } }, stack = { '__data__': { '__data__': [objects] } },
expected = [1, 2, 'a', objects[0], objects[1], stack]; expected = [1, 2, 'a', objects[0], objects[1], stack];
args = undefined; args = undefined;
@@ -913,10 +913,12 @@
})(objects[0])(objects[1]); })(objects[0])(objects[1]);
args[5] = _.omitBy(args[5], _.isFunction); args[5] = _.omitBy(args[5], _.isFunction);
args[5].__data__ = _.omitBy(args[5].__data__, _.isFunction);
assert.deepEqual(args, expected, 'fp.isEqualWith'); assert.deepEqual(args, expected, 'fp.isEqualWith');
args = undefined; args = undefined;
stack = { '__data__': { 'array': [], 'map': null } }; stack = { '__data__': { '__data__': [] } };
expected = [2, 1, 'a', objects[1], objects[0], stack]; expected = [2, 1, 'a', objects[1], objects[0], stack];
fp.isMatchWith(function() { fp.isMatchWith(function() {
@@ -924,6 +926,8 @@
})(objects[0])(objects[1]); })(objects[0])(objects[1]);
args[5] = _.omitBy(args[5], _.isFunction); args[5] = _.omitBy(args[5], _.isFunction);
args[5].__data__ = _.omitBy(args[5].__data__, _.isFunction);
assert.deepEqual(args, expected, 'fp.isMatchWith'); assert.deepEqual(args, expected, 'fp.isMatchWith');
args = undefined; args = undefined;
@@ -935,6 +939,8 @@
})(value)({ 'a': [2, 3] }); })(value)({ 'a': [2, 3] });
args[5] = _.omitBy(args[5], _.isFunction); args[5] = _.omitBy(args[5], _.isFunction);
args[5].__data__ = _.omitBy(args[5].__data__, _.isFunction);
assert.deepEqual(args, expected, 'fp.mergeWith'); assert.deepEqual(args, expected, 'fp.mergeWith');
args = undefined; args = undefined;