diff --git a/build.js b/build.js index ba10ac759..31a04ea77 100755 --- a/build.js +++ b/build.js @@ -126,14 +126,14 @@ 'size': ['keys'], 'some': ['createIterator', 'identity'], 'sortBy': ['map', 'pluck'], - 'sortedIndex': [], + 'sortedIndex': ['identity'], 'tap': [], 'template': ['escape'], 'throttle': [], 'times': [], 'toArray': ['values'], 'union': ['indexOf'], - 'uniq': ['indexOf'], + 'uniq': ['identity', 'indexOf'], 'uniqueId': [], 'values': ['createIterator'], 'without': ['indexOf'], diff --git a/lodash.js b/lodash.js index 28346c84f..039877312 100644 --- a/lodash.js +++ b/lodash.js @@ -1072,11 +1072,11 @@ * @returns {Array} Returns a new array of sorted values. * @example * - * _.sortBy([1, 2, 3, 4, 5, 6], function(num) { return Math.sin(num); }); - * // => [5, 4, 6, 3, 1, 2] + * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); }); + * // => [3, 1, 2] * - * _.sortBy([1, 2, 3, 4, 5, 6], function(num) { return this.sin(num); }, Math); - * // => [5, 4, 6, 3, 1, 2] + * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math); + * // => [3, 1, 2] * * _.sortBy(['larry', 'brendan', 'moe'], 'length'); * // => ['moe', 'larry', 'brendan'] @@ -1165,8 +1165,8 @@ * @returns {Array} Returns all but the last value or `n` values of the `array`. * @example * - * _.initial([5, 4, 3, 2, 1]); - * // => [5, 4, 3, 2] + * _.initial([3, 2, 1]); + * // => [3, 2] */ function initial(array, n, guard) { return slice.call(array, 0, -((n == undefined || guard) ? 1 : n)); @@ -1246,7 +1246,7 @@ * @returns {Array} Returns all but the last value or `n` values of the `array`. * @example * - * _.last([5, 4, 3, 2, 1]); + * _.last([3, 2, 1]); * // => 1 */ function last(array, n, guard) { @@ -1446,8 +1446,8 @@ * @returns {Array} Returns all but the first value or `n` values of the `array`. * @example * - * _.rest([5, 4, 3, 2, 1]); - * // => [4, 3, 2, 1] + * _.rest([3, 2, 1]); + * // => [2, 1] */ function rest(array, n, guard) { return slice.call(array, (n == undefined || guard) ? 1 : n); @@ -1484,37 +1484,53 @@ /** * Uses a binary search to determine the smallest index at which the `value` * should be inserted into the `array` in order to maintain the sort order - * of the `array`. If `callback` is passed, it will be executed for `value` and - * each element in the `array` to compute their sort ranking. The `callback` - * is invoked with 1 argument; (value). + * of the sorted `array`. If `callback` is passed, it will be executed for + * `value` and each element in the `array` to compute their sort ranking. + * The `callback` is invoked with 1 argument; (value). * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to iterate over. * @param {Mixed} value The value to evaluate. - * @param {Function} [callback] The function called per iteration. + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. * @returns {Number} Returns the index at which the value should be inserted * into the array. * @example * - * _.sortedIndex([10, 20, 30, 40, 50], 35); - * // => 3 + * _.sortedIndex([20, 30, 40], 35); + * // => 2 + * + * var dict = { + * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'thirty-five': 35, 'fourty': 40 } + * }; + * + * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { + * return dict.wordToNumber[word]; + * }); + * // => 2 + * + * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) { + * return this.wordToNumber[word]; + * }, dict); + * // => 2 */ - function sortedIndex(array, value, callback) { + function sortedIndex(array, value, callback, thisArg) { var mid, low = 0, high = array.length; if (callback) { - value = callback(value); - } - while (low < high) { - mid = (low + high) >> 1; - if ((callback ? callback(array[mid]) : array[mid]) < value) { - low = mid + 1; - } else { - high = mid; + value = callback.call(thisArg, value); + while (low < high) { + mid = (low + high) >>> 1; + callback.call(thisArg, array[mid]) < value ? low = mid + 1 : high = mid; + } + } else { + while (low < high) { + mid = (low + high) >>> 1; + array[mid] < value ? low = mid + 1 : high = mid; } } return low; @@ -1562,22 +1578,43 @@ * @category Arrays * @param {Array} array The array to process. * @param {Boolean} [isSorted=false] A flag to indicate that the `array` is already sorted. - * @param {Function} [callback] A + * @param {Function} [callback=identity] The function called per iteration. + * @param {Mixed} [thisArg] The `this` binding for the callback. * @returns {Array} Returns a duplicate-value-free array. * @example * - * _.uniq([1, 2, 1, 3, 1, 4]); - * // => [1, 2, 3, 4] + * _.uniq([1, 2, 1, 3, 1]); + * // => [1, 2, 3] + * + * _.uiq([1, 1, 2, 2, 3], true); + * // => [1, 2, 3] + * + * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); }); + * // => [1, 2, 3] + * + * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math); + * // => [1, 2, 3] */ - function uniq(array, isSorted, callback) { + function uniq(array, isSorted, callback, thisArg) { var computed, index = -1, length = array.length, result = [], seen = []; + // juggle arguments + if (typeof isSorted == 'function') { + thisArg = callback; + callback = isSorted; + isSorted = false; + } + if (!callback) { + callback = identity; + } else if (thisArg) { + callback = iteratorBind(callback, thisArg); + } while (++index < length) { - computed = callback ? callback(array[index]) : array[index]; + computed = callback(array[index], index, array); if (isSorted ? !index || seen[seen.length - 1] !== computed : indexOf(seen, computed) < 0 @@ -2134,8 +2171,8 @@ * @example * * var iceCream = { 'flavor': 'chocolate' }; - * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'lots' }); - * // => { 'flavor': 'chocolate', 'sprinkles': 'lots' } + * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'rainbow' }); + * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' } */ var defaults = createIterator(extendIteratorOptions, { 'inLoop': 'if (object[index] == undefined)' + extendIteratorOptions.inLoop @@ -2219,7 +2256,7 @@ var isArguments = function(value) { return toString.call(value) == '[object Arguments]'; }; - // fallback for browser like IE<9 which detect `arguments` as `[object Object]` + // fallback for browser like IE < 9 which detect `arguments` as `[object Object]` if (!isArguments(arguments)) { isArguments = function(value) { return !!(value && hasOwnProperty.call(value, 'callee')); diff --git a/perf/perf.js b/perf/perf.js index 2a6d080e0..e3f20c9d8 100644 --- a/perf/perf.js +++ b/perf/perf.js @@ -55,7 +55,7 @@ function log(text) { console.log(text); if (fbPanel) { - // scroll down the Firebug Lite panel + // scroll the Firebug Lite panel down fbPanel.scrollTop = fbPanel.scrollHeight; } } @@ -74,12 +74,7 @@ object = {}, fourNumbers = [5, 25, 10, 30], nestedNumbers = [1, [2], [3, [[4]]]], - twoNumbers = [12, 21], - words = [ - 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', - 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', - 'seventeen', 'eighteen', 'nineteen', 'twenty' - ]; + twoNumbers = [12, 21]; var ctor = function() { }, func = function(greeting) { return greeting + ': ' + this.name; }; @@ -92,6 +87,36 @@ _boundCtor = _.bind(ctor, { 'name': 'moe' }), _boundPartial = _.bind(func, { 'name': 'moe' }, 'hi'); + var wordToNumber = { + 'one': 1, + 'two': 2, + 'three': 3, + 'four': 4, + 'five': 5, + 'six': 6, + 'seven': 7, + 'eight': 8, + 'nine': 9, + 'ten': 10, + 'eleven': 11, + 'twelve': 12, + 'thirteen': 13, + 'fourteen': 14, + 'fifteen': 15, + 'sixteen': 16, + 'seventeen': 17, + 'eighteen': 18, + 'nineteen': 19, + 'twenty': 20, + 'twenty-one': 21, + 'twenty-two': 22, + 'twenty-three': 23, + 'twenty-four': 24, + 'twenty-five': 25 + }; + + var words = _.keys(wordToNumber).slice(0, length); + for (var index = 0; index < length; index++) { numbers[index] = index; object['key' + index] = index; @@ -486,6 +511,32 @@ /*--------------------------------------------------------------------------*/ + suites.push( + Benchmark.Suite('sortedIndex') + .add('Lo-Dash', function() { + lodash.sortedIndex(numbers, 25); + }) + .add('Underscore', function() { + _.sortedIndex(numbers, 25); + }) + ); + + suites.push( + Benchmark.Suite('sortedIndex callback') + .add('Lo-Dash', function() { + lodash.sortedIndex(words, 'twenty-five', function(value) { + return wordToNumber[value]; + }); + }) + .add('Underscore', function() { + _.sortedIndex(words, 'twenty-five', function(value) { + return wordToNumber[value]; + }); + }) + ); + + /*--------------------------------------------------------------------------*/ + suites.push( Benchmark.Suite('times') .add('Lo-Dash', function() { @@ -524,6 +575,32 @@ /*--------------------------------------------------------------------------*/ + suites.push( + Benchmark.Suite('uniq') + .add('Lo-Dash', function() { + lodash.uniq(numbers.concat(fourNumbers, twoNumbers)); + }) + .add('Underscore', function() { + _.uniq(numbers.concat(fourNumbers, twoNumbers)); + }) + ); + + suites.push( + Benchmark.Suite('uniq callback') + .add('Lo-Dash', function() { + lodash.uniq(numbers.concat(fourNumbers, twoNumbers), function(num) { + return num % 2; + }); + }) + .add('Underscore', function() { + _.uniq(numbers.concat(fourNumbers, twoNumbers), function(num) { + return num % 2; + }); + }) + ); + + /*--------------------------------------------------------------------------*/ + suites.push( Benchmark.Suite('values') .add('Lo-Dash', function() { diff --git a/test/test.js b/test/test.js index d2e3c80f1..5aab3a6ab 100644 --- a/test/test.js +++ b/test/test.js @@ -142,6 +142,10 @@ test('should not escape the ">" character', function() { equal(_.escape('>'), '>'); }); + + test('should not escape the "/" character', function() { + equal(_.escape('/'), '/'); + }); }()); /*--------------------------------------------------------------------------*/ @@ -177,14 +181,14 @@ QUnit.module('lodash.find'); (function() { - var array = [1, 2, 3, 4]; + var array = [1, 2, 3]; test('should return found `value`', function() { equal(_.find(array, function(n) { return n > 2; }), 3); }); test('should return `undefined` if `value` is not found', function() { - equal(_.find(array, function(n) { return n == 5; }), undefined); + equal(_.find(array, function(n) { return n == 4; }), undefined); }); }()); @@ -215,7 +219,7 @@ (function() { test('returns the collection', function() { - var collection = [1, 2, 3, 4]; + var collection = [1, 2, 3]; equal(_.forEach(collection, Boolean), collection); }); @@ -294,7 +298,7 @@ (function() { test('returns an empty collection for `n` of `0`', function() { - var array = [1, 2, 3, 4]; + var array = [1, 2, 3]; deepEqual(_.initial(array, 0), []); }); }()); @@ -481,11 +485,25 @@ (function() { test('supports the `thisArg` argument', function() { - var actual = _.sortBy([1, 2, 3, 4], function(num) { + var actual = _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math); - deepEqual(actual, [4, 3, 1, 2]); + deepEqual(actual, [3, 1, 2]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('lodash.sortedIndex'); + + (function() { + test('supports the `thisArg` argument', function() { + var actual = _.sortedIndex([1, 2, 3], 4, function(num) { + return this.sin(num); + }, Math); + + equal(actual, 0); }); }()); @@ -555,6 +573,20 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.uniq'); + + (function() { + test('supports the `thisArg` argument', function() { + var actual = _.uniq([1, 2, 1.5, 3, 2.5], function(num) { + return this.floor(num); + }, Math); + + deepEqual(actual, [1, 2, 3]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash(...).shift'); (function() {