From 02f14b36972d424419f9675d6a42d374bd9fd944 Mon Sep 17 00:00:00 2001 From: jgjgill <79239852+jgjgill@users.noreply.github.com> Date: Sun, 30 Jun 2024 19:36:26 +0900 Subject: [PATCH] feat(sampleSize): Add sampleSize (#101) --- benchmarks/sampleSize.bench.ts | 15 ++++++++++ docs/.vitepress/en.mts | 1 + docs/.vitepress/ko.mts | 1 + docs/ko/reference/array/sampleSize.md | 32 +++++++++++++++++++++ docs/reference/array/sampleSize.md | 32 +++++++++++++++++++++ src/array/index.ts | 1 + src/array/sampleSize.spec.ts | 30 ++++++++++++++++++++ src/array/sampleSize.ts | 41 +++++++++++++++++++++++++++ 8 files changed, 153 insertions(+) create mode 100644 benchmarks/sampleSize.bench.ts create mode 100644 docs/ko/reference/array/sampleSize.md create mode 100644 docs/reference/array/sampleSize.md create mode 100644 src/array/sampleSize.spec.ts create mode 100644 src/array/sampleSize.ts diff --git a/benchmarks/sampleSize.bench.ts b/benchmarks/sampleSize.bench.ts new file mode 100644 index 00000000..b0ae7ccc --- /dev/null +++ b/benchmarks/sampleSize.bench.ts @@ -0,0 +1,15 @@ +import { bench, describe } from 'vitest'; +import { sampleSize as sampleSizeToolkit } from 'es-toolkit'; +import { sampleSize as sampleSizeLodash } from 'lodash'; + +describe('sampleSize', () => { + bench('es-toolkit/sampleSize', () => { + const array = [1, 2, 3, 4, 5]; + sampleSizeToolkit(array, 3); + }); + + bench('lodash/sampleSize', () => { + const array = [1, 2, 3, 4, 5]; + sampleSizeLodash(array, 3); + }); +}); diff --git a/docs/.vitepress/en.mts b/docs/.vitepress/en.mts index c34ce8ff..07b6c576 100644 --- a/docs/.vitepress/en.mts +++ b/docs/.vitepress/en.mts @@ -65,6 +65,7 @@ function sidebar(): DefaultTheme.Sidebar { { text: 'maxBy', link: '/reference/array/maxBy' }, { text: 'partition', link: '/reference/array/partition' }, { text: 'sample', link: '/reference/array/sample' }, + { text: 'sampleSize', link: '/reference/array/sampleSize' }, { text: 'shuffle', link: '/reference/array/shuffle' }, { text: 'take', link: '/reference/array/take' }, { text: 'takeWhile', link: '/reference/array/takeWhile' }, diff --git a/docs/.vitepress/ko.mts b/docs/.vitepress/ko.mts index d5147cc5..cae8a18d 100644 --- a/docs/.vitepress/ko.mts +++ b/docs/.vitepress/ko.mts @@ -64,6 +64,7 @@ function sidebar(): DefaultTheme.Sidebar { { text: 'maxBy', link: '/ko/reference/array/maxBy' }, { text: 'partition', link: '/ko/reference/array/partition' }, { text: 'sample', link: '/ko/reference/array/sample' }, + { text: 'sampleSize', link: '/ko/reference/array/sampleSize' }, { text: 'shuffle', link: '/ko/reference/array/shuffle' }, { text: 'take', link: '/ko/reference/array/take' }, { text: 'takeWhile', link: '/ko/reference/array/takeWhile' }, diff --git a/docs/ko/reference/array/sampleSize.md b/docs/ko/reference/array/sampleSize.md new file mode 100644 index 00000000..b1cb5f61 --- /dev/null +++ b/docs/ko/reference/array/sampleSize.md @@ -0,0 +1,32 @@ +# sampleSize + +지정된 `size`의 샘플 요소 배열을 반환해요. + +이 함수는 배열과 숫자를 받아요. [Floyd의 알고리즘](https://www.nowherenearithaca.com/2013/05/robert-floyds-tiny-and-beautiful.html)을 사용해서 샘플된 요소들이 포함된 배열을 반환해요. + +## 인터페이스 + +```typescript +export function sampleSize(array: readonly T[], size: number): T[]; +``` + +### 파리미터 + +- `array` (`T[]`): 샘플링할 배열이에요. +- `size` (`number`): 샘플링할 크기에요. + +### 반환 값 + +(`T[]`): 샘플 크기가 적용된 새로운 배열이에요. + +### 에러 + +`size`가 `array`의 길이보다 크면 에러를 던져요. + +## 예시 + +```typescript +const result = sampleSize([1, 2, 3], 2); +// 결과는 배열의 요소 중 두 개를 포함하는 배열이 돼요. +// [1, 2] or [1, 3] or [2, 3] +``` diff --git a/docs/reference/array/sampleSize.md b/docs/reference/array/sampleSize.md new file mode 100644 index 00000000..2d0551ee --- /dev/null +++ b/docs/reference/array/sampleSize.md @@ -0,0 +1,32 @@ +# sampleSize + +Returns a sample element array of a specified `size`. + +This function takes an array and a number, and returns an array containing the sampled elements using [Floyd's algorithm](https://www.nowherenearithaca.com/2013/05/robert-floyds-tiny-and-beautiful.html). + +## Signature + +```typescript +export function sampleSize(array: readonly T[], size: number): T[]; +``` + +### Parameters + +- `array` (`T[]`): The array to sample from. +- `size` (`number`): The size of sample. + +### Returns + +(`T[]`): A new array with sample size applied. + +### Throws + +Throws an error if `size` is greater than the length of `array`. + +## Examples + +```typescript +const result = sampleSize([1, 2, 3], 2); +// result will be an array containing two of the elements from the array. +// [1, 2] or [1, 3] or [2, 3] +``` diff --git a/src/array/index.ts b/src/array/index.ts index 636920e3..7469ca9f 100644 --- a/src/array/index.ts +++ b/src/array/index.ts @@ -16,6 +16,7 @@ export { maxBy } from './maxBy.ts'; export { minBy } from './minBy.ts'; export { partition } from './partition.ts'; export { sample } from './sample.ts'; +export { sampleSize } from './sampleSize.ts'; export { shuffle } from './shuffle.ts'; export { take } from './take.ts'; export { takeRight } from './takeRight.ts'; diff --git a/src/array/sampleSize.spec.ts b/src/array/sampleSize.spec.ts new file mode 100644 index 00000000..d07cc949 --- /dev/null +++ b/src/array/sampleSize.spec.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest'; +import { sampleSize } from './sampleSize'; + +describe('sampleSize', () => { + it('returns a sample element array of a specified size', () => { + const array = [1, 2, 3]; + const result = sampleSize(array, 2); + + expect(array).toEqual(expect.arrayContaining(result)); + }); + + it('returns an empty array for size 0', () => { + const result = sampleSize([1, 2, 3], 0); + expect(result).toEqual([]); + }); + + it('returns the same array if the size is equal to the array length', () => { + const array = [1, 2, 3]; + const result = sampleSize(array, array.length); + + expect(result).toEqual(array); + expect(result).not.toBe(array); + }); + + it('throws an error if the size is greater than the array length', () => { + expect(() => sampleSize([1, 2, 3], 4)).toThrowErrorMatchingInlineSnapshot( + `[Error: Size must be less than or equal to the length of array.]` + ); + }); +}); diff --git a/src/array/sampleSize.ts b/src/array/sampleSize.ts new file mode 100644 index 00000000..6351c78f --- /dev/null +++ b/src/array/sampleSize.ts @@ -0,0 +1,41 @@ +import { randomInt } from '../math'; + +/** + * Returns a sample element array of a specified `size`. + * + * This function takes an array and a number, and returns an array containing the sampled elements using Floyd's algorithm. + * + * {@link https://www.nowherenearithaca.com/2013/05/robert-floyds-tiny-and-beautiful.html Floyd's algoritm} + * + * @param {T[]} array - The array to sample from. + * @param {number} size - The size of sample. + * @returns {T[]} A new array with sample size applied. + * @throws {Error} Throws an error if `size` is greater than the length of `array`. + * + * @example + * const result = sampleSize([1, 2, 3], 2) + * // result will be an array containing two of the elements from the array. + * // [1, 2] or [1, 3] or [2, 3] + */ +export function sampleSize(array: readonly T[], size: number): T[] { + if (size > array.length) { + throw new Error('Size must be less than or equal to the length of array.'); + } + + const result = new Array(size); + const selected = new Set(); + + for (let step = array.length - size, resultIndex = 0; step < array.length; step++, resultIndex++) { + let index = randomInt(0, step + 1); + + if (selected.has(index)) { + index = step; + } + + selected.add(index); + + result[resultIndex] = array[index]; + } + + return result; +}