Add _.sampleSize.

This commit is contained in:
John-David Dalton
2015-09-27 18:05:05 -07:00
parent 9c79cb7766
commit 528bec0bb1
3 changed files with 95 additions and 146 deletions

View File

@@ -1432,12 +1432,13 @@
* `modArgsSet', 'negate`, `omit`, `omitBy`, `once`, `pairs`, `partial`, * `modArgsSet', 'negate`, `omit`, `omitBy`, `once`, `pairs`, `partial`,
* `partialRight`, `partition`, `pick`, `pickBy`, `plant`, `property`, * `partialRight`, `partition`, `pick`, `pickBy`, `plant`, `property`,
* `propertyOf`, `pull`, `pullAll`, `pullAt`, `push`, `range`, `rearg`, * `propertyOf`, `pull`, `pullAll`, `pullAt`, `push`, `range`, `rearg`,
* `reject`, `remove`, `rest`, `restParam`, `reverse`, `set`, `setWith`, * `reject`, `remove`, `rest`, `restParam`, `reverse`, `sampleSize`, `set`,
* `shuffle`, `slice`, `sort`, `sortBy`, `sortByOrder`, `splice`, `spread`, * `setWith`, `shuffle`, `slice`, `sort`, `sortBy`, `sortByOrder`, `splice`,
* `take`, `takeRight`, `takeRightWhile`, `takeWhile`, `tap`, `throttle`, * `spread`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, `tap`,
* `thru`, `times`, `toArray`, `toPath`, `toPlainObject`, `transform`, `union`, * `throttle`, `thru`, `times`, `toArray`, `toPath`, `toPlainObject`,
* `uniq`, `uniqBy`, `unset`, `unshift`, `unzip`, `unzipWith`, `values`, * `transform`, `union`, `uniq`, `uniqBy`, `unset`, `unshift`, `unzip`,
* `valuesIn`, `without`, `wrap`, `xor`, `zip`, `zipObject`, and `zipWith` * `unzipWith`, `values`, `valuesIn`, `without`, `wrap`, `xor`, `zip`,
* `zipObject`, and `zipWith`
* *
* The wrapper methods that are **not** chainable by default are: * The wrapper methods that are **not** chainable by default are:
* `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clone`, `cloneDeep`, * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clone`, `cloneDeep`,
@@ -1450,16 +1451,13 @@
* `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, `isNumber`, * `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, `isNumber`,
* `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, `isSafeInteger`, * `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, `isSafeInteger`,
* `isString`, `isUndefined`, `isTypedArray`, `join`, `kebabCase`, `last`, * `isString`, `isUndefined`, `isTypedArray`, `join`, `kebabCase`, `last`,
* `lastIndexOf`, `lt`, `lte`, `max`, `min`, `noConflict`, `noop`, `now`, * `lastIndexOf`, `lt`, `lte`, `max`, `min`, `noConflict`, `noop`, `now`, `pad`,
* `pad`, `padLeft`, `padRight`, `parseInt`, `pop`, `random`, `reduce`, * `padLeft`, `padRight`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`,
* `reduceRight`, `repeat`, `result`, `round`, `runInContext`, `shift`, `size`, * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`,
* `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`, * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`,
* `sortedLastIndexBy`, `startCase`, `startsWith`, `sum`, `sumBy`, `template`, * `sortedLastIndexBy`, `startCase`, `startsWith`, `sum`, `sumBy`, `template`,
* `toInteger`, `toString`, `trim`, `trimLeft`, `trimRight`, `trunc`, `unescape`, * `toInteger`, `toString`, `trim`, `trimLeft`, `trimRight`, `trunc`,
* `uniqueId`, `value`, and `words` * `unescape`, `uniqueId`, `value`, and `words`
*
* The wrapper method `sample` will return a wrapped value when `n` is provided,
* otherwise an unwrapped value is returned.
* *
* @name _ * @name _
* @constructor * @constructor
@@ -6629,9 +6627,9 @@
* *
* The guarded methods are: * The guarded methods are:
* `ary`, `callback`, `curry`, `curryRight`, `drop`, `dropRight`, `every`, * `ary`, `callback`, `curry`, `curryRight`, `drop`, `dropRight`, `every`,
* `fill`, `invert`, `parseInt`, `random`, `range`, `sample`, `slice`, `some`, * `fill`, `invert`, `parseInt`, `random`, `range`, `slice`, `some`, `sortBy`,
* `sortBy`, `take`, `takeRight`, `template`, `trim`, `trimLeft`, `trimRight`, * `take`, `takeRight`, `template`, `trim`, `trimLeft`, `trimRight`, `uniq`,
* `uniq`, and `words` * and `words`
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -6820,29 +6818,40 @@
} }
/** /**
* Gets a random element or `n` random elements from a collection. * Gets a random element from `collection`.
* *
* @static * @static
* @memberOf _ * @memberOf _
* @category Collection * @category Collection
* @param {Array|Object} collection The collection to sample. * @param {Array|Object} collection The collection to sample.
* @param {number} [n] The number of elements to sample. * @returns {*} Returns the random element.
* @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
* @returns {*} Returns the random sample(s).
* @example * @example
* *
* _.sample([1, 2, 3, 4]); * _.sample([1, 2, 3, 4]);
* // => 2 * // => 2
*/
function sample(collection) {
var array = isArrayLike(collection) ? collection : values(collection),
length = array.length;
return length > 0 ? array[baseRandom(0, length - 1)] : undefined;
}
/**
* Gets `n` random elements from `collection`.
* *
* _.sample([1, 2, 3, 4], 2); * @static
* @memberOf _
* @category Collection
* @param {Array|Object} collection The collection to sample.
* @param {number} [n=0] The number of elements to sample.
* @returns {Array} Returns the random elements.
* @example
*
* _.sampleSize([1, 2, 3, 4], 2);
* // => [3, 1] * // => [3, 1]
*/ */
function sample(collection, n, guard) { function sampleSize(collection, n) {
if (guard || n == null) {
collection = isArrayLike(collection) ? collection : values(collection);
length = collection.length;
return length > 0 ? collection[baseRandom(0, length - 1)] : undefined;
}
var index = -1, var index = -1,
result = toArray(collection), result = toArray(collection),
length = result.length, length = result.length,
@@ -6875,7 +6884,7 @@
* // => [4, 1, 3, 2] * // => [4, 1, 3, 2]
*/ */
function shuffle(collection) { function shuffle(collection) {
return sample(collection, INFINITY); return sampleSize(collection, MAX_ARRAY_LENGTH);
} }
/** /**
@@ -12218,6 +12227,7 @@
lodash.remove = remove; lodash.remove = remove;
lodash.rest = rest; lodash.rest = rest;
lodash.restParam = restParam; lodash.restParam = restParam;
lodash.sampleSize = sampleSize;
lodash.set = set; lodash.set = set;
lodash.setWith = setWith; lodash.setWith = setWith;
lodash.shuffle = shuffle; lodash.shuffle = shuffle;
@@ -12354,6 +12364,7 @@
lodash.result = result; lodash.result = result;
lodash.round = round; lodash.round = round;
lodash.runInContext = runInContext; lodash.runInContext = runInContext;
lodash.sample = sample;
lodash.size = size; lodash.size = size;
lodash.snakeCase = snakeCase; lodash.snakeCase = snakeCase;
lodash.some = some; lodash.some = some;
@@ -12389,20 +12400,6 @@
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
// Add functions capable of returning wrapped and unwrapped values when chaining.
lodash.sample = sample;
lodash.prototype.sample = function(n) {
if (!this.__chain__ && n == null) {
return sample(this.value());
}
return this.thru(function(value) {
return sample(value, n);
});
};
/*------------------------------------------------------------------------*/
/** /**
* The semantic version number. * The semantic version number.
* *

View File

@@ -15907,29 +15907,59 @@
assert.ok(_.includes(array, actual)); assert.ok(_.includes(array, actual));
}); });
QUnit.test('should return two random elements', function(assert) { QUnit.test('should return `undefined` when sampling empty collections', function(assert) {
assert.expect(1); assert.expect(1);
var actual = _.sample(array, 2); var expected = _.map(empties, _.constant(undefined));
assert.ok(actual.length == 2 && actual[0] !== actual[1] && _.includes(array, actual[0]) && _.includes(array, actual[1]));
var actual = _.transform(empties, function(result, value) {
try {
result.push(_.sample(value));
} catch (e) {}
});
assert.deepEqual(actual, expected);
});
QUnit.test('should sample an object', function(assert) {
assert.expect(1);
var object = { 'a': 1, 'b': 2, 'c': 3 },
actual = _.sample(object);
assert.ok(_.includes(array, actual));
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.sampleSize');
(function() {
var array = [1, 2, 3];
QUnit.test('should return an array of random elements', function(assert) {
assert.expect(2);
var actual = _.sampleSize(array, 2);
assert.strictEqual(actual.length, 2);
assert.deepEqual(_.difference(actual, array), []);
}); });
QUnit.test('should contain elements of the collection', function(assert) { QUnit.test('should contain elements of the collection', function(assert) {
assert.expect(1); assert.expect(1);
var actual = _.sample(array, array.length); var actual = _.sampleSize(array, array.length);
assert.deepEqual(actual.sort(), array); assert.deepEqual(actual.sort(), array);
}); });
QUnit.test('should treat falsey `n` values, except nullish, as `0`', function(assert) { QUnit.test('should treat falsey `n` values as `0`', function(assert) {
assert.expect(1); assert.expect(1);
var expected = _.map(falsey, function(value) { var expected = _.map(falsey, _.constant([]));
return value == null ? 1 : [];
});
var actual = _.map(falsey, function(n) { var actual = _.map(falsey, function(n, index) {
return _.sample([1], n); return index ? _.sampleSize([1], n) : _.sampleSize([1]);
}); });
assert.deepEqual(actual, expected); assert.deepEqual(actual, expected);
@@ -15939,7 +15969,7 @@
assert.expect(3); assert.expect(3);
_.each([0, -1, -Infinity], function(n) { _.each([0, -1, -Infinity], function(n) {
assert.deepEqual(_.sample(array, n), []); assert.deepEqual(_.sampleSize(array, n), []);
}); });
}); });
@@ -15947,34 +15977,25 @@
assert.expect(4); assert.expect(4);
_.each([3, 4, Math.pow(2, 32), Infinity], function(n) { _.each([3, 4, Math.pow(2, 32), Infinity], function(n) {
assert.deepEqual(_.sample(array, n).sort(), array); assert.deepEqual(_.sampleSize(array, n).sort(), array);
}); });
}); });
QUnit.test('should coerce `n` to an integer', function(assert) { QUnit.test('should coerce `n` to an integer', function(assert) {
assert.expect(1); assert.expect(1);
var actual = _.sample(array, 1.6); var actual = _.sampleSize(array, 1.6);
assert.strictEqual(actual.length, 1); assert.strictEqual(actual.length, 1);
}); });
QUnit.test('should return `undefined` when sampling an empty array', function(assert) {
assert.expect(1);
assert.strictEqual(_.sample([]), undefined);
});
QUnit.test('should return an empty array for empty collections', function(assert) { QUnit.test('should return an empty array for empty collections', function(assert) {
assert.expect(1); assert.expect(1);
var expected = _.transform(empties, function(result) { var expected = _.map(empties, _.constant([]));
result.push(undefined, []);
});
var actual = []; var actual = _.transform(empties, function(result, value) {
_.each(empties, function(value) {
try { try {
actual.push(_.sample(value), _.sample(value, 1)); result.push(_.sampleSize(value, 1));
} catch (e) {} } catch (e) {}
}); });
@@ -15985,83 +16006,10 @@
assert.expect(2); assert.expect(2);
var object = { 'a': 1, 'b': 2, 'c': 3 }, var object = { 'a': 1, 'b': 2, 'c': 3 },
actual = _.sample(object); actual = _.sampleSize(object, 2);
assert.ok(_.includes(array, actual)); assert.strictEqual(actual.length, 2);
assert.deepEqual(_.difference(actual, _.values(object)), []);
actual = _.sample(object, 2);
assert.ok(actual.length == 2 && actual[0] !== actual[1] && _.includes(array, actual[0]) && _.includes(array, actual[1]));
});
QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
assert.expect(2);
var array1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
array2 = ['abc', 'def', 'ghi'];
_.each([array1, array2], function(values) {
var a = values[0],
b = values[1],
c = values[2],
actual = _.map(values, _.sample);
assert.ok(_.includes(a, actual[0]) && _.includes(b, actual[1]) && _.includes(c, actual[2]));
});
});
QUnit.test('should return a wrapped value when chaining and `n` is provided', function(assert) {
assert.expect(2);
if (!isNpm) {
var wrapped = _(array).sample(2),
actual = wrapped.value();
assert.ok(wrapped instanceof _);
assert.ok(actual.length == 2 && actual[0] !== actual[1] && _.includes(array, actual[0]) && _.includes(array, actual[1]));
}
else {
skipTest(assert, 2);
}
});
QUnit.test('should return an unwrapped value when chaining and `n` is not provided', function(assert) {
assert.expect(1);
if (!isNpm) {
var actual = _(array).sample();
assert.ok(_.includes(array, actual));
}
else {
skipTest(assert);
}
});
QUnit.test('should return a wrapped value when explicitly chaining', function(assert) {
assert.expect(1);
if (!isNpm) {
assert.ok(_(array).chain().sample() instanceof _);
}
else {
skipTest(assert);
}
});
QUnit.test('should use a stored reference to `_.sample` when chaining', function(assert) {
assert.expect(2);
if (!isNpm) {
var sample = _.sample;
_.sample = _.noop;
var wrapped = _(array);
assert.notStrictEqual(wrapped.sample(), undefined);
assert.notStrictEqual(wrapped.sample(2).value(), undefined);
_.sample = sample;
}
else {
skipTest(assert, 2);
}
}); });
}()); }());
@@ -21024,7 +20972,7 @@
'reject', 'reject',
'remove', 'remove',
'rest', 'rest',
'sample', 'sampleSize',
'shuffle', 'shuffle',
'sortBy', 'sortBy',
'sortByOrder', 'sortByOrder',
@@ -21042,7 +20990,7 @@
var acceptFalsey = _.difference(allMethods, rejectFalsey); var acceptFalsey = _.difference(allMethods, rejectFalsey);
QUnit.test('should accept falsey arguments', function(assert) { QUnit.test('should accept falsey arguments', function(assert) {
assert.expect(243); assert.expect(245);
var emptyArrays = _.map(falsey, _.constant([])); var emptyArrays = _.map(falsey, _.constant([]));

View File

@@ -199,6 +199,10 @@
'reject': [ 'reject': [
'Returns empty list given empty array' 'Returns empty list given empty array'
], ],
'sample': [
'behaves correctly on negative n',
'Died on test #3'
],
'some': [ 'some': [
'Can be called with object', 'Can be called with object',
'Died on test #17', 'Died on test #17',