mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-01-29 06:27:49 +00:00
Added _.sortedLastIndex and allow _.lastIndexOf to work with sorted arrays and _.sortedLastIndex.
This commit is contained in:
103
lodash.js
103
lodash.js
@@ -2279,6 +2279,37 @@
|
||||
return !!result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base implementation of `_.sortedIndex` and `_.sortedLastIndex` without
|
||||
* support for callback shorthands and `this` binding.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} array The array to inspect.
|
||||
* @param {*} value The value to evaluate.
|
||||
* @param {Function} iterator The function called per iteration.
|
||||
* @param {boolean} [retHighest=false] Specify returning the highest, instead
|
||||
* of the lowest, index at which a value should be inserted into `array`.
|
||||
* @returns {number} Returns the index at which `value` should be inserted
|
||||
* into `array`.
|
||||
*/
|
||||
function baseSortedIndex(array, value, iterator, retHighest) {
|
||||
var low = 0,
|
||||
high = array ? array.length : low;
|
||||
|
||||
value = iterator(value);
|
||||
while (low < high) {
|
||||
var mid = (low + high) >>> 1,
|
||||
computed = iterator(array[mid]);
|
||||
|
||||
if (retHighest ? computed <= value : computed < value) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
return high;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base implementation of `_.uniq` without support for callback shorthands
|
||||
* and `this` binding.
|
||||
@@ -3362,7 +3393,7 @@
|
||||
* // => 4
|
||||
*
|
||||
* // performing a binary search
|
||||
* _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
|
||||
* _.indexOf([4, 4, 5, 5, 6, 6], 5, true);
|
||||
* // => 2
|
||||
*/
|
||||
function indexOf(array, value, fromIndex) {
|
||||
@@ -3490,11 +3521,20 @@
|
||||
* // using `fromIndex`
|
||||
* _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
|
||||
* // => 1
|
||||
*
|
||||
* // performing a binary search
|
||||
* _.lastIndexOf([4, 4, 5, 5, 6, 6], 5, true);
|
||||
* // => 3
|
||||
*/
|
||||
function lastIndexOf(array, value, fromIndex) {
|
||||
var index = array ? array.length : 0;
|
||||
var length = array ? array.length : 0,
|
||||
index = length;
|
||||
|
||||
if (typeof fromIndex == 'number') {
|
||||
index = (fromIndex < 0 ? nativeMax(index + fromIndex, 0) : nativeMin(fromIndex || 0, index - 1)) + 1;
|
||||
} else if (fromIndex) {
|
||||
index = sortedLastIndex(array, value) - 1;
|
||||
return (length && array[index] === value) ? index : -1;
|
||||
}
|
||||
while (index--) {
|
||||
if (array[index] === value) {
|
||||
@@ -3706,38 +3746,53 @@
|
||||
* into `array`.
|
||||
* @example
|
||||
*
|
||||
* _.sortedIndex([20, 30, 50], 40);
|
||||
* _.sortedIndex([30, 50], 40);
|
||||
* // => 1
|
||||
*
|
||||
* _.sortedIndex([4, 4, 5, 5, 6, 6], 5);
|
||||
* // => 2
|
||||
*
|
||||
* var dict = {
|
||||
* 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'forty': 40, 'fifty': 50 }
|
||||
* };
|
||||
* var dict = { 'data': { 'thirty': 30, 'forty': 40, 'fifty': 50 } };
|
||||
*
|
||||
* // using an iterator function
|
||||
* _.sortedIndex(['twenty', 'thirty', 'fifty'], 'forty', function(word) {
|
||||
* return this.wordToNumber[word];
|
||||
* _.sortedIndex(['thirty', 'fifty'], 'forty', function(word) {
|
||||
* return this.data[word];
|
||||
* }, dict);
|
||||
* // => 2
|
||||
* // => 1
|
||||
*
|
||||
* // using "_.pluck" callback shorthand
|
||||
* _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
|
||||
* // => 2
|
||||
* _.sortedIndex([{ 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
|
||||
* // => 1
|
||||
*/
|
||||
function sortedIndex(array, value, iterator, thisArg) {
|
||||
var low = 0,
|
||||
high = array ? array.length : low;
|
||||
iterator = iterator == null ? identity : lodash.callback(iterator, thisArg, 1);
|
||||
return baseSortedIndex(array, value, iterator);
|
||||
}
|
||||
|
||||
// explicitly reference `identity` for better inlining in Firefox
|
||||
iterator = iterator ? lodash.callback(iterator, thisArg, 1) : identity;
|
||||
value = iterator(value);
|
||||
|
||||
while (low < high) {
|
||||
var mid = (low + high) >>> 1;
|
||||
(iterator(array[mid]) < value)
|
||||
? (low = mid + 1)
|
||||
: (high = mid);
|
||||
}
|
||||
return low;
|
||||
/**
|
||||
* This method is like `_.sortedIndex` except that it returns the highest
|
||||
* index at which a value should be inserted into a given sorted array in
|
||||
* order to maintain the sort order of the array.
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @category Array
|
||||
* @param {Array} array The array to inspect.
|
||||
* @param {*} value The value to evaluate.
|
||||
* @param {Function|Object|string} [iterator=identity] The function called
|
||||
* per iteration. If a property name or object is provided it is used to
|
||||
* create a "_.pluck" or "_.where" style callback respectively.
|
||||
* @param {*} [thisArg] The `this` binding of `iterator`.
|
||||
* @returns {number} Returns the index at which `value` should be inserted
|
||||
* into `array`.
|
||||
* @example
|
||||
*
|
||||
* _.sortedLastIndex([4, 4, 5, 5, 6, 6], 5);
|
||||
* // => 4
|
||||
*/
|
||||
function sortedLastIndex(array, value, iterator, thisArg) {
|
||||
iterator = iterator == null ? identity : lodash.callback(iterator, thisArg, 1);
|
||||
return baseSortedIndex(array, value, iterator, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
38
perf/perf.js
38
perf/perf.js
@@ -1139,6 +1139,18 @@
|
||||
})
|
||||
);
|
||||
|
||||
suites.push(
|
||||
Benchmark.Suite('`_.indexOf` performing a binary search')
|
||||
.add(buildName, {
|
||||
'fn': 'lodash.indexOf(hundredValues, 99, true)',
|
||||
'teardown': 'function multiArrays(){}'
|
||||
})
|
||||
.add(otherName, {
|
||||
'fn': '_.indexOf(hundredValues, 99, true)',
|
||||
'teardown': 'function multiArrays(){}'
|
||||
})
|
||||
);
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
suites.push(
|
||||
@@ -1361,12 +1373,26 @@
|
||||
|
||||
suites.push(
|
||||
Benchmark.Suite('`_.lastIndexOf`')
|
||||
.add(buildName, '\
|
||||
lodash.lastIndexOf(numbers, 9)'
|
||||
)
|
||||
.add(otherName, '\
|
||||
_.lastIndexOf(numbers, 9)'
|
||||
)
|
||||
.add(buildName, {
|
||||
'fn': 'lodash.lastIndexOf(hundredValues, 0)',
|
||||
'teardown': 'function multiArrays(){}'
|
||||
})
|
||||
.add(otherName, {
|
||||
'fn': '_.lastIndexOf(hundredValues, 0)',
|
||||
'teardown': 'function multiArrays(){}'
|
||||
})
|
||||
);
|
||||
|
||||
suites.push(
|
||||
Benchmark.Suite('`_.lastIndexOf` performing a binary search')
|
||||
.add(buildName, {
|
||||
'fn': 'lodash.lastIndexOf(hundredValues, 0, true)',
|
||||
'teardown': 'function multiArrays(){}'
|
||||
})
|
||||
.add(otherName, {
|
||||
'fn': '_.lastIndexOf(hundredValues, 0, true)',
|
||||
'teardown': 'function multiArrays(){}'
|
||||
})
|
||||
);
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
226
test/test.js
226
test/test.js
@@ -4248,25 +4248,23 @@
|
||||
strictEqual(_.indexOf(array, 3), 2);
|
||||
});
|
||||
|
||||
test('should return `-1` for an unmatched value', 4, function() {
|
||||
strictEqual(_.indexOf(array, 4), -1);
|
||||
strictEqual(_.indexOf(array, 4, true), -1);
|
||||
|
||||
var empty = [];
|
||||
strictEqual(_.indexOf(empty, undefined), -1);
|
||||
strictEqual(_.indexOf(empty, undefined, true), -1);
|
||||
});
|
||||
|
||||
test('should work with a positive `fromIndex`', 1, function() {
|
||||
strictEqual(_.indexOf(array, 1, 2), 3);
|
||||
});
|
||||
|
||||
test('should work with `fromIndex` >= `array.length`', 12, function() {
|
||||
_.each([6, 8, Math.pow(2, 32), Infinity], function(fromIndex) {
|
||||
strictEqual(_.indexOf(array, 1, fromIndex), -1);
|
||||
strictEqual(_.indexOf(array, undefined, fromIndex), -1);
|
||||
strictEqual(_.indexOf(array, '', fromIndex), -1);
|
||||
test('should work with `fromIndex` >= `array.length`', 1, function() {
|
||||
var values = [6, 8, Math.pow(2, 32), Infinity],
|
||||
expected = _.map(values, _.constant([-1, -1, -1]));
|
||||
|
||||
var actual = _.map(values, function(fromIndex) {
|
||||
return [
|
||||
_.indexOf(array, undefined, fromIndex),
|
||||
_.indexOf(array, 1, fromIndex),
|
||||
_.indexOf(array, '', fromIndex)
|
||||
];
|
||||
});
|
||||
|
||||
deepEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('should treat falsey `fromIndex` values as `0`', 1, function() {
|
||||
@@ -4279,22 +4277,31 @@
|
||||
deepEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('should treat non-number `fromIndex` values as `0`', 1, function() {
|
||||
strictEqual(_.indexOf([1, 2, 3], 1, '1'), 0);
|
||||
test('should perform a binary search when `fromIndex` is a non-number truthy value', 1, function() {
|
||||
var sorted = [4, 4, 5, 5, 6, 6],
|
||||
values = [true, '1', {}],
|
||||
expected = _.map(values, _.constant(2));
|
||||
|
||||
var actual = _.map(values, function(value) {
|
||||
return _.indexOf(sorted, 5, value);
|
||||
});
|
||||
|
||||
deepEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('should work with a negative `fromIndex`', 1, function() {
|
||||
strictEqual(_.indexOf(array, 2, -3), 4);
|
||||
});
|
||||
|
||||
test('should work with a negative `fromIndex` <= `-array.length`', 3, function() {
|
||||
_.each([-6, -8, -Infinity], function(fromIndex) {
|
||||
strictEqual(_.indexOf(array, 1, fromIndex), 0);
|
||||
});
|
||||
});
|
||||
test('should work with a negative `fromIndex` <= `-array.length`', 1, function() {
|
||||
var values = [-6, -8, -Infinity],
|
||||
expected = _.map(values, _.constant(0));
|
||||
|
||||
test('should work with `isSorted`', 1, function() {
|
||||
strictEqual(_.indexOf([1, 2, 3], 1, true), 0);
|
||||
var actual = _.map(values, function(fromIndex) {
|
||||
return _.indexOf(array, 1, fromIndex);
|
||||
});
|
||||
|
||||
deepEqual(actual, expected);
|
||||
});
|
||||
}());
|
||||
|
||||
@@ -6268,20 +6275,23 @@
|
||||
strictEqual(_.lastIndexOf(array, 3), 5);
|
||||
});
|
||||
|
||||
test('should return `-1` for an unmatched value', 1, function() {
|
||||
strictEqual(_.lastIndexOf(array, 4), -1);
|
||||
});
|
||||
|
||||
test('should work with a positive `fromIndex`', 1, function() {
|
||||
strictEqual(_.lastIndexOf(array, 1, 2), 0);
|
||||
});
|
||||
|
||||
test('should work with `fromIndex` >= `array.length`', 12, function() {
|
||||
_.each([6, 8, Math.pow(2, 32), Infinity], function(fromIndex) {
|
||||
strictEqual(_.lastIndexOf(array, undefined, fromIndex), -1);
|
||||
strictEqual(_.lastIndexOf(array, 1, fromIndex), 3);
|
||||
strictEqual(_.lastIndexOf(array, '', fromIndex), -1);
|
||||
test('should work with `fromIndex` >= `array.length`', 1, function() {
|
||||
var values = [6, 8, Math.pow(2, 32), Infinity],
|
||||
expected = _.map(values, _.constant([-1, 3, -1]));
|
||||
|
||||
var actual = _.map(values, function(fromIndex) {
|
||||
return [
|
||||
_.lastIndexOf(array, undefined, fromIndex),
|
||||
_.lastIndexOf(array, 1, fromIndex),
|
||||
_.lastIndexOf(array, '', fromIndex)
|
||||
];
|
||||
});
|
||||
|
||||
deepEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('should treat falsey `fromIndex` values, except `0` and `NaN`, as `array.length`', 1, function() {
|
||||
@@ -6296,19 +6306,31 @@
|
||||
deepEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('should treat non-number `fromIndex` values as `array.length`', 2, function() {
|
||||
strictEqual(_.lastIndexOf(array, 3, '1'), 5);
|
||||
strictEqual(_.lastIndexOf(array, 3, true), 5);
|
||||
test('should perform a binary search when `fromIndex` is a non-number truthy value', 1, function() {
|
||||
var sorted = [4, 4, 5, 5, 6, 6],
|
||||
values = [true, '1', {}],
|
||||
expected = _.map(values, _.constant(3));
|
||||
|
||||
var actual = _.map(values, function(value) {
|
||||
return _.lastIndexOf(sorted, 5, value);
|
||||
});
|
||||
|
||||
deepEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('should work with a negative `fromIndex`', 1, function() {
|
||||
strictEqual(_.lastIndexOf(array, 2, -3), 1);
|
||||
});
|
||||
|
||||
test('should work with a negative `fromIndex` <= `-array.length`', 3, function() {
|
||||
_.each([-6, -8, -Infinity], function(fromIndex) {
|
||||
strictEqual(_.lastIndexOf(array, 1, fromIndex), 0);
|
||||
test('should work with a negative `fromIndex` <= `-array.length`', 1, function() {
|
||||
var values = [-6, -8, -Infinity],
|
||||
expected = _.map(values, _.constant(0));
|
||||
|
||||
var actual = _.map(values, function(fromIndex) {
|
||||
return _.lastIndexOf(array, 1, fromIndex);
|
||||
});
|
||||
|
||||
deepEqual(actual, expected);
|
||||
});
|
||||
}());
|
||||
|
||||
@@ -6316,23 +6338,33 @@
|
||||
|
||||
QUnit.module('indexOf methods');
|
||||
|
||||
(function() {
|
||||
_.each(['indexOf', 'lastIndexOf'], function(methodName) {
|
||||
var func = _[methodName];
|
||||
_.each(['indexOf', 'lastIndexOf'], function(methodName) {
|
||||
var func = _[methodName];
|
||||
|
||||
test('`_.' + methodName + '` should accept a falsey `array` argument', 1, function() {
|
||||
var expected = _.map(falsey, _.constant(-1));
|
||||
test('`_.' + methodName + '` should accept a falsey `array` argument', 1, function() {
|
||||
var expected = _.map(falsey, _.constant(-1));
|
||||
|
||||
var actual = _.map(falsey, function(value, index) {
|
||||
try {
|
||||
return index ? func(value) : func();
|
||||
} catch(e) { }
|
||||
});
|
||||
|
||||
deepEqual(actual, expected);
|
||||
var actual = _.map(falsey, function(value, index) {
|
||||
try {
|
||||
return index ? func(value) : func();
|
||||
} catch(e) { }
|
||||
});
|
||||
|
||||
deepEqual(actual, expected);
|
||||
});
|
||||
}());
|
||||
|
||||
|
||||
test('`_.' + methodName + '` should return `-1` for an unmatched value', 4, function() {
|
||||
var array = [1, 2, 3],
|
||||
empty = [];
|
||||
|
||||
strictEqual(func(array, 4), -1);
|
||||
strictEqual(func(array, 4, true), -1);
|
||||
|
||||
strictEqual(func(empty, undefined), -1);
|
||||
strictEqual(func(empty, undefined, true), -1);
|
||||
});
|
||||
});
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
@@ -8975,38 +9007,94 @@
|
||||
QUnit.module('lodash.sortedIndex');
|
||||
|
||||
(function() {
|
||||
var array = [20, 30, 50],
|
||||
objects = [{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }];
|
||||
test('should return the correct insert index', 1, function() {
|
||||
var array = [30, 50],
|
||||
values = [30, 40, 50],
|
||||
expected = [0, 1, 1];
|
||||
|
||||
test('should return the insert index of a given value', 2, function() {
|
||||
strictEqual(_.sortedIndex(array, 40), 2);
|
||||
strictEqual(_.sortedIndex(array, 30), 1);
|
||||
var actual = _.map(values, function(value) {
|
||||
return _.sortedIndex(array, value);
|
||||
});
|
||||
|
||||
deepEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('should pass the correct `callback` arguments', 1, function() {
|
||||
test('should work with an array of strings', 1, function() {
|
||||
var array = ['a', 'c'],
|
||||
values = ['a', 'b', 'c'],
|
||||
expected = [0, 1, 1];
|
||||
|
||||
var actual = _.map(values, function(value) {
|
||||
return _.sortedIndex(array, value);
|
||||
});
|
||||
|
||||
deepEqual(actual, expected);
|
||||
});
|
||||
}());
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('lodash.sortedLastIndex');
|
||||
|
||||
(function() {
|
||||
test('should return the correct insert index', 1, function() {
|
||||
var array = [30, 50],
|
||||
values = [30, 40, 50],
|
||||
expected = [1, 1, 2];
|
||||
|
||||
var actual = _.map(values, function(value) {
|
||||
return _.sortedLastIndex(array, value);
|
||||
});
|
||||
|
||||
deepEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('should work with an array of strings', 1, function() {
|
||||
var array = ['a', 'c'],
|
||||
values = ['a', 'b', 'c'],
|
||||
expected = [1, 1, 2];
|
||||
|
||||
var actual = _.map(values, function(value) {
|
||||
return _.sortedLastIndex(array, value);
|
||||
});
|
||||
|
||||
deepEqual(actual, expected);
|
||||
});
|
||||
}());
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
QUnit.module('sortedIndex methods');
|
||||
|
||||
_.each(['sortedIndex', 'sortedLastIndex'], function(methodName) {
|
||||
var array = [30, 50],
|
||||
func = _[methodName],
|
||||
objects = [{ 'x': 30 }, { 'x': 50 }];
|
||||
|
||||
test('`_.' + methodName + '` should pass the correct `callback` arguments', 1, function() {
|
||||
var args;
|
||||
|
||||
_.sortedIndex(array, 40, function() {
|
||||
func(array, 40, function() {
|
||||
args || (args = slice.call(arguments));
|
||||
});
|
||||
|
||||
deepEqual(args, [40]);
|
||||
});
|
||||
|
||||
test('should support the `thisArg` argument', 1, function() {
|
||||
var actual = _.sortedIndex(array, 40, function(num) {
|
||||
test('`_.' + methodName + '` should support the `thisArg` argument', 1, function() {
|
||||
var actual = func(array, 40, function(num) {
|
||||
return this[num];
|
||||
}, { '20': 20, '30': 30, '40': 40 });
|
||||
}, { '30': 30, '40': 40, '50': 50 });
|
||||
|
||||
strictEqual(actual, 2);
|
||||
strictEqual(actual, 1);
|
||||
});
|
||||
|
||||
test('should work with a string for `callback`', 1, function() {
|
||||
var actual = _.sortedIndex(objects, { 'x': 40 }, 'x');
|
||||
strictEqual(actual, 2);
|
||||
test('`_.' + methodName + '` should work with a string for `callback`', 1, function() {
|
||||
var actual = func(objects, { 'x': 40 }, 'x');
|
||||
strictEqual(actual, 1);
|
||||
});
|
||||
|
||||
test('supports arrays with lengths larger than `Math.pow(2, 31) - 1`', 1, function() {
|
||||
test('`_.' + methodName + '` supports arrays with lengths larger than `Math.pow(2, 31) - 1`', 1, function() {
|
||||
var length = Math.pow(2, 32) - 1,
|
||||
index = length - 1,
|
||||
array = Array(length),
|
||||
@@ -9014,14 +9102,14 @@
|
||||
|
||||
if (array.length == length) {
|
||||
array[index] = index;
|
||||
_.sortedIndex(array, index, function() { steps++; });
|
||||
func(array, index, function() { steps++; });
|
||||
strictEqual(steps, 33);
|
||||
}
|
||||
else {
|
||||
skipTest();
|
||||
}
|
||||
});
|
||||
}());
|
||||
});
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
@@ -11247,7 +11335,7 @@
|
||||
|
||||
var acceptFalsey = _.difference(allMethods, rejectFalsey);
|
||||
|
||||
test('should accept falsey arguments', 190, function() {
|
||||
test('should accept falsey arguments', 191, function() {
|
||||
var emptyArrays = _.map(falsey, _.constant([])),
|
||||
isExposed = '_' in root,
|
||||
oldDash = root._;
|
||||
|
||||
Reference in New Issue
Block a user