From 4e38f70e0e7a6344b0bdb6bc52efc9fa5999e033 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Fri, 22 Apr 2016 20:45:16 -0700 Subject: [PATCH] Update cache implementations. --- lodash.js | 489 ++++++++++++++++++++++++++---------------------- test/test-fp.js | 10 +- 2 files changed, 269 insertions(+), 230 deletions(-) diff --git a/lodash.js b/lodash.js index d379fdc14..85472bba2 100644 --- a/lodash.js +++ b/lodash.js @@ -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 * that is not found in the character symbols. @@ -1661,64 +1673,202 @@ * * @private * @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. * - * @private + * @name delete + * @memberOf Hash * @param {Object} hash The hash to modify. * @param {string} key The key of the value to remove. * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ - function hashDelete(hash, key) { - return hashHas(hash, key) && delete hash[key]; + function hashDelete(key) { + return this.has(key) && delete this.__data__[key]; } /** * Gets the hash value for `key`. * - * @private - * @param {Object} hash The hash to query. + * @name get + * @memberOf Hash * @param {string} key The key of the value to get. * @returns {*} Returns the entry value. */ - function hashGet(hash, key) { + function hashGet(key) { + var data = this.__data__; if (nativeCreate) { - var result = hash[key]; + var result = data[key]; 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. * - * @private - * @param {Object} hash The hash to query. + * @name has + * @memberOf Hash * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ - function hashHas(hash, key) { - return nativeCreate ? hash[key] !== undefined : hasOwnProperty.call(hash, key); + function hashHas(key) { + var data = this.__data__; + return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key); } /** * Sets the hash `key` to `value`. * - * @private - * @param {Object} hash The hash to modify. + * @name set + * @memberOf Hash * @param {string} key The key of the value to set. * @param {*} value The value to set. + * @returns {Object} Returns the hash instance. */ - function hashSet(hash, key, value) { - hash[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; + function hashSet(key, value) { + var data = this.__data__; + data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; + return this; } - // Avoid inheriting from `Object.prototype` when possible. - Hash.prototype = nativeCreate ? nativeCreate(null) : objectProto; + // Add methods to `Hash`. + 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 * @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, - length = values ? values.length : 0; + length = entries ? entries.length : 0; this.clear(); while (++index < length) { - var entry = values[index]; + var entry = entries[index]; this.set(entry[0], entry[1]); } } @@ -1743,14 +1893,13 @@ /** * Removes all key-value entries from the map. * - * @private * @name clear * @memberOf MapCache */ - function mapClear() { + function mapCacheClear() { this.__data__ = { 'hash': new Hash, - 'map': Map ? new Map : [], + 'map': new (Map || ListCache), 'string': new Hash }; } @@ -1764,82 +1913,60 @@ * @param {string} key The key of the value to remove. * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ - function mapDelete(key) { - var data = this.__data__; - if (isKeyable(key)) { - return hashDelete(typeof key == 'string' ? data.string : data.hash, key); - } - return Map ? data.map['delete'](key) : assocDelete(data.map, key); + function mapCacheDelete(key) { + return getMapData(this, key)['delete'](key); } /** * Gets the map value for `key`. * - * @private * @name get * @memberOf MapCache * @param {string} key The key of the value to get. * @returns {*} Returns the entry value. */ - function mapGet(key) { - var data = this.__data__; - if (isKeyable(key)) { - return hashGet(typeof key == 'string' ? data.string : data.hash, key); - } - return Map ? data.map.get(key) : assocGet(data.map, key); + function mapCacheGet(key) { + return getMapData(this, key).get(key); } /** * Checks if a map value for `key` exists. * - * @private * @name has * @memberOf MapCache * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ - function mapHas(key) { - var data = this.__data__; - if (isKeyable(key)) { - return hashHas(typeof key == 'string' ? data.string : data.hash, key); - } - return Map ? data.map.has(key) : assocHas(data.map, key); + function mapCacheHas(key) { + return getMapData(this, key).has(key); } /** * Sets the map `key` to `value`. * - * @private * @name set * @memberOf MapCache * @param {string} key The key of the value to set. * @param {*} value The value to set. * @returns {Object} Returns the map cache instance. */ - function mapSet(key, value) { - var data = this.__data__; - 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); - } + function mapCacheSet(key, value) { + getMapData(this, key).set(key, value); return this; } // Add methods to `MapCache`. - MapCache.prototype.clear = mapClear; - MapCache.prototype['delete'] = mapDelete; - MapCache.prototype.get = mapGet; - MapCache.prototype.has = mapHas; - MapCache.prototype.set = mapSet; + MapCache.prototype.clear = mapCacheClear; + MapCache.prototype['delete'] = mapCacheDelete; + MapCache.prototype.get = mapCacheGet; + MapCache.prototype.has = mapCacheHas; + MapCache.prototype.set = mapCacheSet; /*------------------------------------------------------------------------*/ /** * - * Creates a set cache object to store unique values. + * Creates an array cache object to store unique values. * * @private * @constructor @@ -1851,52 +1978,39 @@ this.__data__ = new MapCache; while (++index < length) { - this.push(values[index]); + this.add(values[index]); } } /** - * Checks if `value` is in `cache`. + * Adds `value` to the array cache. * - * @private - * @param {Object} cache The set cache to search. + * @name add + * @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. * @returns {number} Returns `true` if `value` is found, else `false`. */ - function cacheHas(cache, value) { - var map = cache.__data__; - 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); - } + function setCacheHas(value) { + return this.__data__.has(value); } // Add methods to `SetCache`. - SetCache.prototype.push = cachePush; + SetCache.prototype.add = SetCache.prototype.push = setCacheAdd; + SetCache.prototype.has = setCacheHas; /*------------------------------------------------------------------------*/ @@ -1905,82 +2019,61 @@ * * @private * @constructor - * @param {Array} [values] The values to cache. + * @param {Array} [entries] The key-value pairs to cache. */ - function Stack(values) { - var index = -1, - length = values ? values.length : 0; - - this.clear(); - while (++index < length) { - var entry = values[index]; - this.set(entry[0], entry[1]); - } + function Stack(entries) { + this.__data__ = new ListCache(entries); } /** * Removes all key-value entries from the stack. * - * @private * @name clear * @memberOf Stack */ function stackClear() { - this.__data__ = { 'array': [], 'map': null }; + this.__data__ = new ListCache; } /** * Removes `key` and its value from the stack. * - * @private * @name delete * @memberOf Stack * @param {string} key The key of the value to remove. * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function stackDelete(key) { - var data = this.__data__, - array = data.array; - - return array ? assocDelete(array, key) : data.map['delete'](key); + return this.__data__['delete'](key); } /** * Gets the stack value for `key`. * - * @private * @name get * @memberOf Stack * @param {string} key The key of the value to get. * @returns {*} Returns the entry value. */ function stackGet(key) { - var data = this.__data__, - array = data.array; - - return array ? assocGet(array, key) : data.map.get(key); + return this.__data__.get(key); } /** * Checks if a stack value for `key` exists. * - * @private * @name has * @memberOf Stack * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ function stackHas(key) { - var data = this.__data__, - array = data.array; - - return array ? assocHas(array, key) : data.map.has(key); + return this.__data__.has(key); } /** * Sets the stack `key` to `value`. * - * @private * @name set * @memberOf Stack * @param {string} key The key of the value to set. @@ -1988,21 +2081,11 @@ * @returns {Object} Returns the stack cache instance. */ function stackSet(key, value) { - var data = this.__data__, - array = data.array; - - 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); + var cache = this.__data__; + if (cache instanceof ListCache && cache.__data__.length == LARGE_ARRAY_SIZE) { + cache = this.__data__ = new MapCache(cache.__data__); } + cache.set(key, value); 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. * @@ -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 * by `iteratee` and values set by `setter`. @@ -4957,9 +4974,7 @@ * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. */ function equalArrays(array, other, equalFunc, customizer, bitmask, stack) { - var index = -1, - isPartial = bitmask & PARTIAL_COMPARE_FLAG, - isUnordered = bitmask & UNORDERED_COMPARE_FLAG, + var isPartial = bitmask & PARTIAL_COMPARE_FLAG, arrLength = array.length, othLength = other.length; @@ -4971,7 +4986,10 @@ if (stacked) { return stacked == other; } - var result = true; + var index = -1, + result = true, + seen = (bitmask & UNORDERED_COMPARE_FLAG) ? new SetCache : undefined; + stack.set(array, other); // Ignore non-index properties. @@ -5259,6 +5277,21 @@ */ 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`. * diff --git a/test/test-fp.js b/test/test-fp.js index 739ceed40..d550aa335 100644 --- a/test/test-fp.js +++ b/test/test-fp.js @@ -901,7 +901,7 @@ var iteration = 0, 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]; args = undefined; @@ -913,10 +913,12 @@ })(objects[0])(objects[1]); args[5] = _.omitBy(args[5], _.isFunction); + args[5].__data__ = _.omitBy(args[5].__data__, _.isFunction); + assert.deepEqual(args, expected, 'fp.isEqualWith'); args = undefined; - stack = { '__data__': { 'array': [], 'map': null } }; + stack = { '__data__': { '__data__': [] } }; expected = [2, 1, 'a', objects[1], objects[0], stack]; fp.isMatchWith(function() { @@ -924,6 +926,8 @@ })(objects[0])(objects[1]); args[5] = _.omitBy(args[5], _.isFunction); + args[5].__data__ = _.omitBy(args[5].__data__, _.isFunction); + assert.deepEqual(args, expected, 'fp.isMatchWith'); args = undefined; @@ -935,6 +939,8 @@ })(value)({ 'a': [2, 3] }); args[5] = _.omitBy(args[5], _.isFunction); + args[5].__data__ = _.omitBy(args[5].__data__, _.isFunction); + assert.deepEqual(args, expected, 'fp.mergeWith'); args = undefined;