From 4844417eabab0a64657b14e4267367fea0f27c36 Mon Sep 17 00:00:00 2001 From: Aura Date: Sat, 23 Nov 2024 00:25:20 +0100 Subject: [PATCH] feat: add `windows` (#836) --- packages/iterator-utilities/package.json | 10 ++++ packages/iterator-utilities/src/index.ts | 1 + .../iterator-utilities/src/lib/windows.ts | 39 ++++++++++++++++ .../iterator-utilities/tests/windows.test.ts | 46 +++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 packages/iterator-utilities/src/lib/windows.ts create mode 100644 packages/iterator-utilities/tests/windows.test.ts diff --git a/packages/iterator-utilities/package.json b/packages/iterator-utilities/package.json index dc5b581045..a0bf39438b 100644 --- a/packages/iterator-utilities/package.json +++ b/packages/iterator-utilities/package.json @@ -945,6 +945,16 @@ "default": "./dist/cjs/lib/unzip.cjs" } }, + "./windows": { + "import": { + "types": "./dist/esm/lib/windows.d.mts", + "default": "./dist/esm/lib/windows.mjs" + }, + "require": { + "types": "./dist/cjs/lib/windows.d.cts", + "default": "./dist/cjs/lib/windows.cjs" + } + }, "./zip": { "import": { "types": "./dist/esm/lib/zip.d.mts", diff --git a/packages/iterator-utilities/src/index.ts b/packages/iterator-utilities/src/index.ts index 365d94f2d0..f75ee96613 100644 --- a/packages/iterator-utilities/src/index.ts +++ b/packages/iterator-utilities/src/index.ts @@ -84,4 +84,5 @@ export * from './lib/toIterableIterator'; export * from './lib/union'; export * from './lib/unique'; export * from './lib/unzip'; +export * from './lib/windows'; export * from './lib/zip'; diff --git a/packages/iterator-utilities/src/lib/windows.ts b/packages/iterator-utilities/src/lib/windows.ts new file mode 100644 index 0000000000..818d7adad0 --- /dev/null +++ b/packages/iterator-utilities/src/lib/windows.ts @@ -0,0 +1,39 @@ +import { from, type IterableResolvable } from './from'; +import { assertPositive } from './shared/_assertPositive'; +import { makeIterableIterator } from './shared/_makeIterableIterator'; +import { toIntegerOrInfinityOrThrow } from './shared/_toIntegerOrInfinityOrThrow'; + +/** + * Creates an iterable with arrays of `count` elements representing a sliding window. + * + * @param iterable The iterator to take values from. + * @param count The maximum number of values in the window. + * @returns An iterator that yields windows with `count` values from the provided iterator. + * + * @example + * ```typescript + * import { windows } from '@sapphire/iterator-utilities'; + * + * const iterable = [1, 2, 3, 4, 5]; + * console.log([...windows(iterable, 2)]); + * // Output: [[1, 2], [2, 3], [3, 4], [4, 5]] + * ``` + */ +export function windows(iterable: IterableResolvable, count: number): IterableIterator { + count = assertPositive(toIntegerOrInfinityOrThrow(count), count); + + const buffer = [] as ElementType[]; + const resolvedIterable = from(iterable); + return makeIterableIterator(() => { + while (buffer.length !== count) { + const result = resolvedIterable.next(); + if (result.done) return { done: true, value: undefined }; + + buffer.push(result.value); + } + + const value = buffer.slice(); + buffer.shift(); + return { done: false, value }; + }); +} diff --git a/packages/iterator-utilities/tests/windows.test.ts b/packages/iterator-utilities/tests/windows.test.ts new file mode 100644 index 0000000000..78f485ea84 --- /dev/null +++ b/packages/iterator-utilities/tests/windows.test.ts @@ -0,0 +1,46 @@ +import { windows } from '../src'; + +describe('windows', () => { + test('GIVEN iterable and limit greater than iterable length THEN returns an empty iterable', () => { + const iterable = [1, 2, 3]; + const limit = 5; + const result = [...windows(iterable, limit)]; + expect(result).toEqual([]); + }); + + test('GIVEN iterable and limit equal to iterable length THEN returns an array with the contents of the iterable', () => { + const iterable = [1, 2, 3]; + const limit = 3; + const result = [...windows(iterable, limit)]; + expect(result).toEqual([[1, 2, 3]]); + }); + + test('GIVEN iterable and limit less than iterable length THEN returns multiple windows', () => { + const iterable = [1, 2, 3]; + const limit = 2; + const result = [...windows(iterable, limit)]; + expect(result).toEqual([ + [1, 2], + [2, 3] + ]); + }); + + test('GIVEN empty iterable THEN returns empty iterable', () => { + const iterable: number[] = []; + const limit = 5; + const result = [...windows(iterable, limit)]; + expect(result).toEqual([]); + }); + + test('GIVEN iterable and limit equal to 0 THEN throws RangeError', () => { + const iterable = [1, 2, 3]; + const limit = 0; + expect(() => windows(iterable, limit)).toThrowError(new RangeError('0 must be a positive number')); + }); + + test('GIVEN iterable and limit less than 0 THEN throws RangeError', () => { + const iterable = [1, 2, 3]; + const limit = -1; + expect(() => windows(iterable, limit)).toThrowError(new RangeError('-1 must be a positive number')); + }); +});