Add _.differenceWith, _.intersectionWith, _.unionWith, _.uniqWith, and _.xorWith.

This commit is contained in:
John-David Dalton
2015-10-30 00:40:41 -07:00
parent 96a22aaec0
commit 3d25b2fd9b
2 changed files with 231 additions and 91 deletions

320
lodash.js
View File

@@ -723,6 +723,22 @@
return -1;
}
function baseIncludes(array, value) {
return baseIndexOf(array, value, 0) > -1;
}
function includesWith(array, value, comparator) {
var index = -1,
length = array ? array.length : 0;
while (++index < length) {
if (comparator(value, array[index])) {
return true;
}
}
return false;
}
/**
* The base implementation of `_.pairs` and `_.pairsIn` which creates an array
* of key-value pairs for `object` corresponding to the property names of `props`.
@@ -821,6 +837,12 @@
return result;
}
function baseUnary(func) {
return function(value) {
return func(value);
};
}
/**
* The base implementation of `_.values` and `_.valuesIn` which creates an
* array of `object` property values corresponding to the property names
@@ -1881,23 +1903,22 @@
}
/**
* Checks if `value` is in `cache` mimicking the return signature of
* `_.indexOf` by returning `0` if `value` is found, else `-1`.
* Checks if `value` is in `cache`.
*
* @private
* @param {Object} cache The set cache to search.
* @param {*} value The value to search for.
* @returns {number} Returns `0` if `value` is found, else `-1`.
* @returns {number} Returns `true` if `value` is found, else `false`.
*/
function cacheIndexOf(cache, value) {
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 ? 0 : -1;
return hash[value] === HASH_UNDEFINED;
}
return map.has(value) ? 0 : -1;
return map.has(value);
}
/**
@@ -2267,21 +2288,8 @@
}
/**
* The base implementation of `_.difference` without support for excluding
* multiple arrays of values.
*
* @private
* @param {Array} array The array to inspect.
* @param {Array} values The values to exclude.
* @returns {Array} Returns the new array of filtered values.
*/
function baseDifference(array, values) {
return baseDifferenceBy(array, values);
}
/**
* The base implementation of `_.differenceBy` without support for callback
* shorthands.
* The base implementation of methods like `_.difference` without support for
* excluding multiple arrays or callback shorthands.
*
* @private
* @param {Array} array The array to inspect.
@@ -2289,9 +2297,9 @@
* @param {Function} [iteratee] The function invoked per element.
* @returns {Array} Returns the new array of filtered values.
*/
function baseDifferenceBy(array, values, iteratee) {
function baseDifference(array, values, iteratee, comparator) {
var index = -1,
indexOf = baseIndexOf,
includes = baseIncludes,
isCommon = true,
length = array.length,
result = [],
@@ -2301,10 +2309,14 @@
return result;
}
if (iteratee) {
values = arrayMap(values, function(value) { return iteratee(value); });
values = arrayMap(values, baseUnary(iteratee));
}
if (values.length >= LARGE_ARRAY_SIZE) {
indexOf = cacheIndexOf;
if (comparator) {
includes = includesWith;
isCommon = false;
}
else if (values.length >= LARGE_ARRAY_SIZE) {
includes = cacheHas;
isCommon = false;
values = new SetCache(values);
}
@@ -2322,7 +2334,7 @@
}
result.push(value);
}
else if (indexOf(values, computed, 0) < 0) {
else if (!includes(values, computed, comparator)) {
result.push(value);
}
}
@@ -2573,28 +2585,17 @@
}
/**
* The base implementation of `_.intersection` that accepts an array of arrays
* to inspect.
*
* @private
* @param {Array} arrays The arrays to inspect.
* @returns {Array} Returns the new array of shared values.
*/
function baseIntersection(arrays) {
return baseIntersectionBy(arrays);
}
/**
* The base implementation of `_.intersectionBy` without support for callback
* shorthands.
* The base implementation of methods like `_.intersection`, without support
* for callback shorthands, that accepts an array of arrays to inspect.
*
* @private
* @param {Array} arrays The arrays to inspect.
* @param {Function} [iteratee] The function invoked per element.
* @returns {Array} Returns the new array of shared values.
*/
function baseIntersectionBy(arrays, iteratee) {
var othLength = arrays.length,
function baseIntersection(arrays, iteratee, comparator) {
var includes = comparator ? includesWith : baseIncludes,
othLength = arrays.length,
othIndex = othLength,
caches = Array(othLength),
result = [];
@@ -2602,9 +2603,11 @@
while (othIndex--) {
var array = arrays[othIndex];
if (othIndex && iteratee) {
array = arrayMap(array, function(value) { return iteratee(value); });
array = arrayMap(array, baseUnary(iteratee));
}
caches[othIndex] = (iteratee || array.length >= 120) ? new SetCache(othIndex && array) : null;
caches[othIndex] = !comparator && (iteratee || array.length >= 120)
? new SetCache(othIndex && array)
: undefined;
}
array = arrays[0];
@@ -2617,11 +2620,11 @@
var value = array[index],
computed = iteratee ? iteratee(value) : value;
if ((seen ? cacheIndexOf(seen, computed) : baseIndexOf(result, computed, 0)) < 0) {
if (!(seen ? cacheHas(seen, computed) : includes(result, computed, comparator))) {
var othIndex = othLength;
while (--othIndex) {
var cache = caches[othIndex];
if ((cache ? cacheIndexOf(cache, computed) : baseIndexOf(arrays[othIndex], computed, 0)) < 0) {
if (!(cache ? cacheHas(cache, computed) : includes(arrays[othIndex], computed, comparator))) {
continue outer;
}
}
@@ -3321,18 +3324,6 @@
return isArray(value) ? value : stringToPath(value);
}
/**
* The base implementation of `_.uniq`.
*
* @private
* @param {Array} array The array to inspect.
* @param {Function} [iteratee] The function invoked per iteration.
* @returns {Array} Returns the new duplicate free array.
*/
function baseUniq(array) {
return baseUniqBy(array);
}
/**
* The base implementation of `_.uniqBy` without support for callback shorthands.
*
@@ -3341,18 +3332,25 @@
* @param {Function} [iteratee] The function invoked per element.
* @returns {Array} Returns the new duplicate free array.
*/
function baseUniqBy(array, iteratee) {
var index = -1,
indexOf = baseIndexOf,
function baseUniq(array, iteratee, comparator) {
var seen,
index = -1,
includes = baseIncludes,
length = array.length,
isCommon = true,
seen = length >= LARGE_ARRAY_SIZE ? new SetCache : null,
result = [];
result = [],
seen = result;
if (seen) {
indexOf = cacheIndexOf;
if (comparator) {
isCommon = false;
} else {
includes = includesWith;
}
else if (length >= LARGE_ARRAY_SIZE) {
isCommon = false;
includes = cacheHas;
seen = new SetCache;
}
else {
seen = iteratee ? [] : result;
}
outer:
@@ -3372,7 +3370,7 @@
}
result.push(value);
}
else if (indexOf(seen, computed, 0) < 0) {
else if (!includes(seen, computed, comparator)) {
if (seen !== result) {
seen.push(computed);
}
@@ -3441,34 +3439,27 @@
}
/**
* The base implementation of `_.xor` that accepts an array of arrays to inspect.
*
* @private
* @param {Array} arrays The arrays to inspect.
* @returns {Array} Returns the new array of values.
*/
function baseXor(arrays) {
return baseXorBy(arrays);
}
/**
* The base implementation of `_.xorBy` without support for callback shorthands.
* The base implementation of methods like `_.xor`, without support for
* callback shorthands, that accepts an array of arrays to inspect.
*
* @private
* @param {Array} arrays The arrays to inspect.
* @param {Function} [iteratee] The function invoked per element.
* @returns {Array} Returns the new array of values.
*/
function baseXorBy(arrays, iteratee) {
function baseXor(arrays, iteratee, comparator) {
var index = -1,
length = arrays.length;
while (++index < length) {
var result = result
? arrayPush(baseDifferenceBy(result, arrays[index], iteratee), baseDifferenceBy(arrays[index], result, iteratee))
? arrayPush(
baseDifference(result, arrays[index], iteratee, comparator),
baseDifference(arrays[index], result, iteratee, comparator)
)
: arrays[index];
}
return (result && result.length) ? baseUniqBy(result, iteratee) : [];
return (result && result.length) ? baseUniq(result, iteratee, comparator) : [];
}
/**
@@ -5101,7 +5092,36 @@
iteratee = undefined;
}
return isArrayLikeObject(array)
? baseDifferenceBy(array, baseFlatten(values, false, true), getIteratee(iteratee))
? baseDifference(array, baseFlatten(values, false, true), getIteratee(iteratee))
: [];
});
/**
* This method is like `_.difference` except that it accepts `comparator`
* which is invoked to compare elements of `array` to `values`. The comparator
* is invoked with two arguments: (arrVal, othVal).
*
* @static
* @memberOf _
* @category Array
* @param {Array} array The array to inspect.
* @param {...Array} [values] The values to exclude.
* @param {Function} [comparator] The function invoked per element.
* @returns {Array} Returns the new array of filtered values.
* @example
*
* var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
*
* _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
* // => [{ 'x': 2, 'y': 1 }]
*/
var differenceWith = rest(function(array, values) {
var comparator = last(values);
if (isArrayLikeObject(comparator)) {
comparator = undefined;
}
return isArrayLikeObject(array)
? baseDifference(array, baseFlatten(values, false, true), undefined, comparator)
: [];
});
@@ -5533,7 +5553,40 @@
mapped.pop();
}
return (mapped.length && mapped[0] === arrays[0])
? baseIntersectionBy(mapped, getIteratee(iteratee))
? baseIntersection(mapped, getIteratee(iteratee))
: [];
});
/**
* This method is like `_.intersection` except that it accepts `comparator`
* which is invoked to compare elements of `arrays`. The comparator is invoked
* with two arguments: (arrVal, othVal).
*
* @static
* @memberOf _
* @category Array
* @param {...Array} [arrays] The arrays to inspect.
* @param {Function} [comparator] The function invoked per element.
* @returns {Array} Returns the new array of shared values.
* @example
*
* var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
* var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
*
* _.intersectionWith(objects, others, _.isEqual);
* // => [{ 'x': 1, 'y': 2 }]
*/
var intersectionWith = rest(function(arrays) {
var comparator = last(arrays),
mapped = arrayMap(arrays, toArrayLikeObject);
if (comparator === last(mapped)) {
comparator = undefined;
} else {
mapped.pop();
}
return (mapped.length && mapped[0] === arrays[0])
? baseIntersection(mapped, undefined, comparator)
: [];
});
@@ -5662,7 +5715,7 @@
*
* var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
*
* _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], function(o) { return o.x; });
* _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
* console.log(array);
* // => [{ 'x': 2 }]
*/
@@ -6198,7 +6251,34 @@
if (isArrayLikeObject(iteratee)) {
iteratee = undefined;
}
return baseUniqBy(baseFlatten(arrays, false, true), getIteratee(iteratee));
return baseUniq(baseFlatten(arrays, false, true), getIteratee(iteratee));
});
/**
* This method is like `_.union` except that it accepts `comparator` which
* is invoked to compare elements of `arrays`. The comparator is invoked
* with two arguments: (arrVal, othVal).
*
* @static
* @memberOf _
* @category Array
* @param {...Array} [arrays] The arrays to inspect.
* @param {Function} [comparator] The function invoked per element.
* @returns {Array} Returns the new array of combined values.
* @example
*
* var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
* var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
*
* _.unionWith(objects, others, _.isEqual);
* // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
*/
var unionWith = rest(function(arrays) {
var comparator = last(arrays);
if (isArrayLikeObject(comparator)) {
iteratee = undefined;
}
return baseUniq(baseFlatten(arrays, false, true), undefined, comparator);
});
/**
@@ -6245,7 +6325,31 @@
*/
function uniqBy(array, iteratee) {
return (array && array.length)
? baseUniqBy(array, getIteratee(iteratee))
? baseUniq(array, getIteratee(iteratee))
: [];
}
/**
* This method is like `_.uniq` except that it accepts `comparator` which
* is invoked to compare elements of `array`. The comparator is invoked with
* two arguments: (arrVal, othVal).
*
* @static
* @memberOf _
* @category Array
* @param {Array} array The array to inspect.
* @param {Function} [comparator] The function invoked per element.
* @returns {Array} Returns the new duplicate free array.
* @example
*
* var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
*
* _.uniqWith(objects, _.isEqual);
* // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
*/
function uniqWith(array, comparator) {
return (array && array.length)
? baseUniq(array, undefined, comparator)
: [];
}
@@ -6381,7 +6485,34 @@
if (isArrayLikeObject(iteratee)) {
iteratee = undefined;
}
return baseXorBy(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee));
return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee));
});
/**
* This method is like `_.xor` except that it accepts `comparator` which is
* invoked to compare elements of `arrays`. The comparator is invoked with
* two arguments: (arrVal, othVal).
*
* @static
* @memberOf _
* @category Array
* @param {...Array} [arrays] The arrays to inspect.
* @param {Function} [comparator] The function invoked per element.
* @returns {Array} Returns the new array of values.
* @example
*
* var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
* var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
*
* _.xorWith(objects, others, _.isEqual);
* // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
*/
var xorWith = rest(function(arrays) {
var comparator = last(arrays);
if (isArrayLikeObject(comparator)) {
comparator = undefined;
}
return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator);
});
/**
@@ -7062,7 +7193,7 @@
* @param {*} target The value to search for.
* @param {number} [fromIndex=0] The index to search from.
* @param- {Object} [guard] Enables use as an iteratee for functions like `_.reduce`.
* @returns {boolean} Returns `true` if a matching element is found, else `false`.
* @returns {boolean} Returns `true` if `target` is found, else `false`.
* @example
*
* _.includes([1, 2, 3], 1);
@@ -8615,6 +8746,10 @@
return partial(wrapper, value);
}
function unary(func) {
return ary(func, 1);
}
/*------------------------------------------------------------------------*/
/**
@@ -13120,6 +13255,7 @@
lodash.delay = delay;
lodash.difference = difference;
lodash.differenceBy = differenceBy;
lodash.differenceWith = differenceWith;
lodash.disj = disj;
lodash.drop = drop;
lodash.dropRight = dropRight;
@@ -13138,6 +13274,7 @@
lodash.initial = initial;
lodash.intersection = intersection;
lodash.intersectionBy = intersectionBy;
lodash.intersectionWith = intersectionWith;
lodash.invert = invert;
lodash.invoke = invoke;
lodash.iteratee = iteratee;
@@ -13207,8 +13344,10 @@
lodash.transform = transform;
lodash.union = union;
lodash.unionBy = unionBy;
lodash.unionWith = unionWith;
lodash.uniq = uniq;
lodash.uniqBy = uniqBy;
lodash.uniqWith = uniqWith;
lodash.unset = unset;
lodash.unzip = unzip;
lodash.unzipWith = unzipWith;
@@ -13219,6 +13358,7 @@
lodash.wrap = wrap;
lodash.xor = xor;
lodash.xorBy = xorBy;
lodash.xorWith = xorWith;
lodash.zip = zip;
lodash.zipObject = zipObject;
lodash.zipWith = zipWith;

View File

@@ -22028,7 +22028,7 @@
var acceptFalsey = lodashStable.difference(allMethods, rejectFalsey);
QUnit.test('should accept falsey arguments', function(assert) {
assert.expect(267);
assert.expect(272);
var emptyArrays = lodashStable.map(falsey, lodashStable.constant([]));