Skip to content

Commit

Permalink
feat(micro-dash): add sampleSize()
Browse files Browse the repository at this point in the history
  • Loading branch information
ersimont committed Dec 14, 2020
1 parent 61a37b1 commit 05bf741
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 2 deletions.
1 change: 1 addition & 0 deletions TODO.md
@@ -1,3 +1,4 @@
- make `micro-dash-sizes` support the workflow of tweaking the micro-dash size
- landing page to link to all API docs
- coveralls
- help may be here, to combine multiple coverage runs into one report: https://github.com/angular/angular-cli/issues/11268
Expand Down
@@ -0,0 +1,7 @@
import sampleSize from 'lodash-es/sampleSize';

console.log(
sampleSize([1, 2], 1),
sampleSize({ a: 1, b: 2 }, 2),
sampleSize([1, 2, 3]),
);
@@ -0,0 +1,7 @@
import { sampleSize } from '@s-libs/micro-dash';

console.log(
sampleSize([1, 2], 1),
sampleSize({ a: 1, b: 2 }, 2),
sampleSize([1, 2, 3]),
);
1 change: 1 addition & 0 deletions projects/micro-dash/src/lib/collection/index.ts
Expand Up @@ -11,6 +11,7 @@ export { map } from './map';
export { reduce } from './reduce';
export { reduceRight } from './reduce-right';
export { sample } from './sample';
export { sampleSize } from './sample-size';
export { size } from './size';
export { some } from './some';
export { sortBy } from './sort-by';
49 changes: 49 additions & 0 deletions projects/micro-dash/src/lib/collection/sample-size.spec.ts
@@ -0,0 +1,49 @@
import { difference } from 'lodash-es';
import { sampleSize } from './sample-size';

describe('sampleSize()', () => {
const array = [1, 2, 3];

it('should return an array of random elements', () => {
const actual = sampleSize(array, 2);
expect(actual.length).toBe(2);
expect(difference(actual, array)).toEqual([]);
});

it('should contain elements of the collection', () => {
expect(sampleSize(array, array.length).sort()).toEqual(array);
});

it('should treat falsey `size` values, except `undefined`, as `0`', () => {
expect(sampleSize(['a'], 0)).toEqual([]);
expect(sampleSize(['a'])).toEqual(['a']);
expect(sampleSize(['a'], undefined)).toEqual(['a']);
});

it('should return an empty array when `n` < `1` or `NaN`', () => {
expect(sampleSize(array, 0)).toEqual([]);
expect(sampleSize(array, -1)).toEqual([]);
expect(sampleSize(array, -Infinity)).toEqual([]);
});

it('should return all elements when `n` >= `length`', () => {
expect(sampleSize(array, 3).sort()).toEqual(array);
expect(sampleSize(array, 4).sort()).toEqual(array);
expect(sampleSize(array, 2 ** 32).sort()).toEqual(array);
expect(sampleSize(array, Infinity).sort()).toEqual(array);
});

it('should return an empty array for empty collections', () => {
expect(sampleSize([], 1)).toEqual([]);
expect(sampleSize({}, 1)).toEqual([]);
expect(sampleSize(null, 1)).toEqual([]);
expect(sampleSize(undefined, 1)).toEqual([]);
});

it('should sample an object', () => {
const object = { a: 1, b: 2, c: 3 };
const actual = sampleSize(object, 2);
expect(actual.length).toBe(2);
expect(difference(actual, array)).toEqual([]);
});
});
27 changes: 27 additions & 0 deletions projects/micro-dash/src/lib/collection/sample-size.ts
@@ -0,0 +1,27 @@
import { pullAt } from '../array';
import { Nil } from '../interfaces';
import { randomInt } from '../math/random';
import { identity, times } from '../util';
import { map } from './map';

/**
* Gets `n` random elements at unique keys from `collection` up to the size of `collection`.
*
* Differences from lodash:
* - no special treatment given to fraction values of `n`
*
* Contribution to minified bundle size, when it is the only function imported:
* - Lodash: 4,765 bytes
* - Micro-dash: 706 bytes
*/

export function sampleSize<T>(array: T[] | Nil, n?: number): T[];
export function sampleSize<T>(object: T | Nil, n?: number): Array<T[keyof T]>;

export function sampleSize(collection: any, n = 1): any[] {
const values = map(collection, identity);
return times(
Math.min(n, values.length),
() => pullAt(values, randomInt(0, values.length - 1, false))[0],
);
}
11 changes: 9 additions & 2 deletions projects/micro-dash/src/lib/math/random.ts
Expand Up @@ -9,7 +9,7 @@ import { isBoolean } from '../lang';
*
* Contribution to minified bundle size, when it is the only function imported:
* - Lodash: 2,258 bytes
* - Micro-dash: 320 bytes
* - Micro-dash: 347 bytes
*/

export function random(floating?: boolean): number;
Expand All @@ -34,7 +34,14 @@ export function random(...args: any[]): number {
if (!isBoolean(floating)) {
floating = !Number.isInteger(lower) && !Number.isInteger(upper);
}
return randomInt(lower, upper, floating);
}

export const randomInt = (
lower: number,
upper: number,
floating: boolean,
): number => {
let range = upper - lower;
if (!floating) {
++range;
Expand All @@ -44,7 +51,7 @@ export function random(...args: any[]): number {
result = Math.floor(result);
}
return result;
}
};

// /**
// * Produces a random number between the inclusive `lower` and `upper` bounds. If only one argument is provided a number between `0` and the given number is returned. If `floating` is true, or either `lower` or `upper` are floats, a floating-point number is returned instead of an integer.
Expand Down
@@ -0,0 +1,25 @@
import { sampleSize } from '../../lib/collection';

declare const array: number[];
declare const arrayOrNull: number[] | null;
declare const arrayOrUndefined: number[] | undefined;
declare const object: { a: number; b: string };
declare const objectOrNull: { a: number; b: string } | null;
declare const objectOrUndefined: { a: number; b: string } | undefined;

// $ExpectType number[]
sampleSize(array, 3);
// $ExpectType (string | number)[]
sampleSize(object, 3);
// $ExpectType number[]
sampleSize(array);
// $ExpectType (string | number)[]
sampleSize(object);
// $ExpectType number[]
sampleSize(arrayOrNull, 3);
// $ExpectType number[]
sampleSize(arrayOrUndefined, 3);
// $ExpectType (string | number)[]
sampleSize(objectOrNull, 3);
// $ExpectType (string | number)[]
sampleSize(objectOrUndefined, 3);

0 comments on commit 05bf741

Please sign in to comment.