Split out _.sortedIndex and _.sortedLastIndex into _.sortedIndexBy and _.sortedLastIndexBy.

This commit is contained in:
jdalton
2015-07-06 08:11:19 -07:00
committed by John-David Dalton
parent bc7cabde4e
commit 41f248f54d
2 changed files with 94 additions and 53 deletions

View File

@@ -5044,10 +5044,30 @@
/** /**
* Uses a binary search to determine the lowest index at which `value` should * Uses a binary search to determine the lowest index at which `value` should
* be inserted into `array` in order to maintain its sort order. If an iteratee * be inserted into `array` in order to maintain its sort order.
* function is provided it's invoked for `value` and each element of `array` *
* to compute their sort ranking. The iteratee is invoked with one argument: * @static
* (value). * @memberOf _
* @category Array
* @param {Array} array The sorted array to inspect.
* @param {*} value The value to evaluate.
* @returns {number} Returns the index at which `value` should be inserted into `array`.
* @example
*
* _.sortedIndex([30, 50], 40);
* // => 1
*
* _.sortedIndex([4, 5], 4);
* // => 0
*/
function sortedIndex(array, value) {
return binaryIndex(array, value);
}
/**
* This method is like `_.sortedIndex` except that it accepts an iteratee
* which is invoked for `value` and each element of `array` to compute their
* sort ranking. The iteratee is invoked with one argument: (value).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -5058,27 +5078,17 @@
* @returns {number} Returns the index at which `value` should be inserted into `array`. * @returns {number} Returns the index at which `value` should be inserted into `array`.
* @example * @example
* *
* _.sortedIndex([30, 50], 40);
* // => 1
*
* _.sortedIndex([4, 4, 5, 5], 5);
* // => 2
*
* var dict = { 'thirty': 30, 'forty': 40, 'fifty': 50 }; * var dict = { 'thirty': 30, 'forty': 40, 'fifty': 50 };
* *
* // using an iteratee function * _.sortedIndexBy(['thirty', 'fifty'], 'forty', _.propertyOf(dict));
* _.sortedIndex(['thirty', 'fifty'], 'forty', _.propertyOf(dict));
* // => 1 * // => 1
* *
* // using the `_.property` callback shorthand * // using the `_.property` callback shorthand
* _.sortedIndex([{ 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x'); * _.sortedIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');
* // => 1 * // => 0
*/ */
function sortedIndex(array, value, iteratee) { function sortedIndexBy(array, value, iteratee) {
var toIteratee = getIteratee(); return binaryIndexBy(array, value, getIteratee(iteratee));
return (iteratee == null && toIteratee === baseIteratee)
? binaryIndex(array, value)
: binaryIndexBy(array, value, toIteratee(iteratee));
} }
/** /**
@@ -5091,18 +5101,36 @@
* @category Array * @category Array
* @param {Array} array The sorted array to inspect. * @param {Array} array The sorted array to inspect.
* @param {*} value The value to evaluate. * @param {*} value The value to evaluate.
* @returns {number} Returns the index at which `value` should be inserted into `array`.
* @example
*
* _.sortedLastIndex([4, 5], 4);
* // => 1
*/
function sortedLastIndex(array, value) {
return binaryIndex(array, value, true);
}
/**
* This method is like `_.sortedLastIndex` except that it accepts an iteratee
* which is invoked for `value` and each element of `array` to compute their
* sort ranking. The iteratee is invoked with one argument: (value).
*
* @static
* @memberOf _
* @category Array
* @param {Array} array The sorted array to inspect.
* @param {*} value The value to evaluate.
* @param {Function|Object|string} [iteratee=_.identity] The function invoked per iteration. * @param {Function|Object|string} [iteratee=_.identity] The function invoked per iteration.
* @returns {number} Returns the index at which `value` should be inserted into `array`. * @returns {number} Returns the index at which `value` should be inserted into `array`.
* @example * @example
* *
* _.sortedLastIndex([4, 4, 5, 5], 5); * // using the `_.property` callback shorthand
* // => 4 * _.sortedLastIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');
* // => 1
*/ */
function sortedLastIndex(array, value, iteratee) { function sortedLastIndexBy(array, value, iteratee) {
var toIteratee = getIteratee(); return binaryIndexBy(array, value, getIteratee(iteratee), true);
return (iteratee == null && toIteratee === baseIteratee)
? binaryIndex(array, value, true)
: binaryIndexBy(array, value, toIteratee(iteratee), true);
} }
/** /**
@@ -11526,7 +11554,9 @@
lodash.snakeCase = snakeCase; lodash.snakeCase = snakeCase;
lodash.some = some; lodash.some = some;
lodash.sortedIndex = sortedIndex; lodash.sortedIndex = sortedIndex;
lodash.sortedIndexBy = sortedIndexBy;
lodash.sortedLastIndex = sortedLastIndex; lodash.sortedLastIndex = sortedLastIndex;
lodash.sortedLastIndexBy = sortedLastIndexBy;
lodash.startCase = startCase; lodash.startCase = startCase;
lodash.startsWith = startsWith; lodash.startsWith = startsWith;
lodash.sum = sum; lodash.sum = sum;

View File

@@ -8739,12 +8739,12 @@
} }
}); });
test('`_.sortedIndex` should use `_.iteratee` internally', 1, function() { test('`_.sortedIndexBy` should use `_.iteratee` internally', 1, function() {
if (!isModularize) { if (!isModularize) {
var objects = [{ 'a': 30 }, { 'a': 50 }]; var objects = [{ 'a': 30 }, { 'a': 50 }];
_.iteratee = getPropA; _.iteratee = getPropA;
strictEqual(_.sortedIndex(objects, { 'a': 40 }), 1); strictEqual(_.sortedIndexBy(objects, { 'a': 40 }), 1);
_.iteratee = iteratee; _.iteratee = iteratee;
} }
else { else {
@@ -8752,12 +8752,12 @@
} }
}); });
test('`_.sortedLastIndex` should use `_.iteratee` internally', 1, function() { test('`_.sortedLastIndexBy` should use `_.iteratee` internally', 1, function() {
if (!isModularize) { if (!isModularize) {
var objects = [{ 'a': 30 }, { 'a': 50 }]; var objects = [{ 'a': 30 }, { 'a': 50 }];
_.iteratee = getPropA; _.iteratee = getPropA;
strictEqual(_.sortedLastIndex(objects, { 'a': 40 }), 1); strictEqual(_.sortedLastIndexBy(objects, { 'a': 40 }), 1);
_.iteratee = iteratee; _.iteratee = iteratee;
} }
else { else {
@@ -14323,13 +14323,12 @@
QUnit.module('sortedIndex methods'); QUnit.module('sortedIndex methods');
_.each(['sortedIndex', 'sortedLastIndex'], function(methodName) { _.each(['sortedIndex', 'sortedLastIndex'], function(methodName) {
var array = [30, 50], var func = _[methodName],
func = _[methodName], isSortedIndex = methodName == 'sortedIndex';
isSortedIndex = methodName == 'sortedIndex',
objects = [{ 'x': 30 }, { 'x': 50 }];
test('`_.' + methodName + '` should return the insert index', 1, function() { test('`_.' + methodName + '` should return the insert index', 1, function() {
var values = [30, 40, 50], var array = [30, 50],
values = [30, 40, 50],
expected = isSortedIndex ? [0, 1, 1] : [1, 1, 2]; expected = isSortedIndex ? [0, 1, 1] : [1, 1, 2];
var actual = _.map(values, function(value) { var actual = _.map(values, function(value) {
@@ -14361,21 +14360,6 @@
deepEqual(actual, expected); deepEqual(actual, expected);
}); });
test('`_.' + methodName + '` should provide the correct `iteratee` arguments', 1, function() {
var args;
func(array, 40, function() {
args || (args = slice.call(arguments));
});
deepEqual(args, [40]);
});
test('`_.' + methodName + '` should work with a "_.property" style `iteratee`', 1, function() {
var actual = func(objects, { 'x': 40 }, 'x');
strictEqual(actual, 1);
});
test('`_.' + methodName + '` should align with `_.sortBy`', 10, function() { test('`_.' + methodName + '` should align with `_.sortBy`', 10, function() {
var expected = [1, '2', {}, null, undefined, NaN, NaN]; var expected = [1, '2', {}, null, undefined, NaN, NaN];
@@ -14390,6 +14374,32 @@
strictEqual(func(expected, NaN), isSortedIndex ? 5 : 7); strictEqual(func(expected, NaN), isSortedIndex ? 5 : 7);
}); });
}); });
});
/*--------------------------------------------------------------------------*/
QUnit.module('sortedIndexBy methods');
_.each(['sortedIndexBy', 'sortedLastIndexBy'], function(methodName) {
var func = _[methodName],
isSortedIndexBy = methodName == 'sortedIndexBy';
test('`_.' + methodName + '` should provide the correct `iteratee` arguments', 1, function() {
var args;
func([30, 50], 40, function() {
args || (args = slice.call(arguments));
});
deepEqual(args, [40]);
});
test('`_.' + methodName + '` should work with a "_.property" style `iteratee`', 1, function() {
var objects = [{ 'x': 30 }, { 'x': 50 }],
actual = func(objects, { 'x': 40 }, 'x');
strictEqual(actual, 1);
});
test('`_.' + methodName + '` should support arrays larger than `MAX_ARRAY_LENGTH / 2`', 12, function() { test('`_.' + methodName + '` should support arrays larger than `MAX_ARRAY_LENGTH / 2`', 12, function() {
_.each([Math.ceil(MAX_ARRAY_LENGTH / 2), MAX_ARRAY_LENGTH], function(length) { _.each([Math.ceil(MAX_ARRAY_LENGTH / 2), MAX_ARRAY_LENGTH], function(length) {
@@ -14402,7 +14412,7 @@
var steps = 0, var steps = 0,
actual = func(array, value, function(value) { steps++; return value; }); actual = func(array, value, function(value) { steps++; return value; });
var expected = (isSortedIndex ? !_.isNaN(value) : _.isFinite(value)) var expected = (isSortedIndexBy ? !_.isNaN(value) : _.isFinite(value))
? 0 ? 0
: Math.min(length, MAX_ARRAY_INDEX); : Math.min(length, MAX_ARRAY_INDEX);
@@ -17347,7 +17357,7 @@
var args = arguments, var args = arguments,
array = [1, 2, 3, 4, 5, 6]; array = [1, 2, 3, 4, 5, 6];
test('should work with `arguments` objects', 27, function() { test('should work with `arguments` objects', 28, function() {
function message(methodName) { function message(methodName) {
return '`_.' + methodName + '` should work with `arguments` objects'; return '`_.' + methodName + '` should work with `arguments` objects';
} }
@@ -17374,6 +17384,7 @@
deepEqual(_.lastIndexOf(args, 1), 0, message('lastIndexOf')); deepEqual(_.lastIndexOf(args, 1), 0, message('lastIndexOf'));
deepEqual(_.rest(args, 4), [null, [3], null, 5], message('rest')); deepEqual(_.rest(args, 4), [null, [3], null, 5], message('rest'));
deepEqual(_.sortedIndex(args, 6), 5, message('sortedIndex')); deepEqual(_.sortedIndex(args, 6), 5, message('sortedIndex'));
deepEqual(_.sortedLastIndex(args, 6), 5, message('sortedLastIndex'));
deepEqual(_.take(args, 2), [1, null], message('take')); deepEqual(_.take(args, 2), [1, null], message('take'));
deepEqual(_.takeRight(args, 1), [5], message('takeRight')); deepEqual(_.takeRight(args, 1), [5], message('takeRight'));
deepEqual(_.takeRightWhile(args, _.identity), [5], message('takeRightWhile')); deepEqual(_.takeRightWhile(args, _.identity), [5], message('takeRightWhile'));
@@ -17514,7 +17525,7 @@
var acceptFalsey = _.difference(allMethods, rejectFalsey); var acceptFalsey = _.difference(allMethods, rejectFalsey);
test('should accept falsey arguments', 209, function() { test('should accept falsey arguments', 211, function() {
var emptyArrays = _.map(falsey, _.constant([])); var emptyArrays = _.map(falsey, _.constant([]));
_.each(acceptFalsey, function(methodName) { _.each(acceptFalsey, function(methodName) {