From 05bf741365d897352c8469161ec1ab39fe4eb48f Mon Sep 17 00:00:00 2001 From: ersimont <8042088+ersimont@users.noreply.github.com> Date: Mon, 14 Dec 2020 16:55:23 -0500 Subject: [PATCH] feat(micro-dash): add `sampleSize()` --- TODO.md | 1 + .../src/app/collection/sample-size.lodash.ts | 7 +++ .../app/collection/sample-size.microdash.ts | 7 +++ .../micro-dash/src/lib/collection/index.ts | 1 + .../src/lib/collection/sample-size.spec.ts | 49 +++++++++++++++++++ .../src/lib/collection/sample-size.ts | 27 ++++++++++ projects/micro-dash/src/lib/math/random.ts | 11 ++++- .../collection/sample-size.dts-spec.ts | 25 ++++++++++ 8 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 projects/micro-dash-sizes/src/app/collection/sample-size.lodash.ts create mode 100644 projects/micro-dash-sizes/src/app/collection/sample-size.microdash.ts create mode 100644 projects/micro-dash/src/lib/collection/sample-size.spec.ts create mode 100644 projects/micro-dash/src/lib/collection/sample-size.ts create mode 100644 projects/micro-dash/src/typing-tests/collection/sample-size.dts-spec.ts diff --git a/TODO.md b/TODO.md index f544eb0b..7fbf6f36 100644 --- a/TODO.md +++ b/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 diff --git a/projects/micro-dash-sizes/src/app/collection/sample-size.lodash.ts b/projects/micro-dash-sizes/src/app/collection/sample-size.lodash.ts new file mode 100644 index 00000000..37d3d57a --- /dev/null +++ b/projects/micro-dash-sizes/src/app/collection/sample-size.lodash.ts @@ -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]), +); diff --git a/projects/micro-dash-sizes/src/app/collection/sample-size.microdash.ts b/projects/micro-dash-sizes/src/app/collection/sample-size.microdash.ts new file mode 100644 index 00000000..51c2caf2 --- /dev/null +++ b/projects/micro-dash-sizes/src/app/collection/sample-size.microdash.ts @@ -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]), +); diff --git a/projects/micro-dash/src/lib/collection/index.ts b/projects/micro-dash/src/lib/collection/index.ts index d3fb0630..c0ae0212 100644 --- a/projects/micro-dash/src/lib/collection/index.ts +++ b/projects/micro-dash/src/lib/collection/index.ts @@ -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'; diff --git a/projects/micro-dash/src/lib/collection/sample-size.spec.ts b/projects/micro-dash/src/lib/collection/sample-size.spec.ts new file mode 100644 index 00000000..8917212d --- /dev/null +++ b/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([]); + }); +}); diff --git a/projects/micro-dash/src/lib/collection/sample-size.ts b/projects/micro-dash/src/lib/collection/sample-size.ts new file mode 100644 index 00000000..f3db36af --- /dev/null +++ b/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(array: T[] | Nil, n?: number): T[]; +export function sampleSize(object: T | Nil, n?: number): Array; + +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], + ); +} diff --git a/projects/micro-dash/src/lib/math/random.ts b/projects/micro-dash/src/lib/math/random.ts index d07f5a70..007eb2b0 100644 --- a/projects/micro-dash/src/lib/math/random.ts +++ b/projects/micro-dash/src/lib/math/random.ts @@ -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; @@ -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; @@ -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. diff --git a/projects/micro-dash/src/typing-tests/collection/sample-size.dts-spec.ts b/projects/micro-dash/src/typing-tests/collection/sample-size.dts-spec.ts new file mode 100644 index 00000000..2f6eb44a --- /dev/null +++ b/projects/micro-dash/src/typing-tests/collection/sample-size.dts-spec.ts @@ -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);