From 044cb83e7339d77cea322050fd181eea4b9af09a Mon Sep 17 00:00:00 2001 From: Federico Biccheddu <433819+FedericoBiccheddu@users.noreply.github.com> Date: Wed, 19 Feb 2025 18:05:07 +0100 Subject: [PATCH] feat: add Array.window (#4477) --- .changeset/tender-kangaroos-kiss.md | 5 +++++ packages/effect/dtslint/Array.tst.ts | 12 +++++++++++ packages/effect/src/Array.ts | 31 ++++++++++++++++++++++++++++ packages/effect/test/Array.test.ts | 12 +++++++++++ 4 files changed, 60 insertions(+) create mode 100644 .changeset/tender-kangaroos-kiss.md diff --git a/.changeset/tender-kangaroos-kiss.md b/.changeset/tender-kangaroos-kiss.md new file mode 100644 index 00000000000..74ab03a9c47 --- /dev/null +++ b/.changeset/tender-kangaroos-kiss.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +Add `Array.window` function diff --git a/packages/effect/dtslint/Array.tst.ts b/packages/effect/dtslint/Array.tst.ts index 50d980b6f4e..aeec35bb786 100644 --- a/packages/effect/dtslint/Array.tst.ts +++ b/packages/effect/dtslint/Array.tst.ts @@ -942,6 +942,18 @@ describe("Array", () => { .type.toBe<[[string, ...Array], ...Array<[string, ...Array]>]>() }) + it("window", () => { + // Array + expect(Array.window(strings, 2)).type.toBe>>() + expect(pipe(strings, Array.window(2))).type.toBe>>() + expect(Array.window(2)(strings)).type.toBe>>() + + // NonEmptyArray + expect(Array.window(nonEmptyStrings, 2)).type.toBe>>() + expect(pipe(nonEmptyStrings, Array.window(2))).type.toBe>>() + expect(Array.window(2)(nonEmptyStrings)).type.toBe>>() + }) + it("reverse", () => { // Array expect(Array.reverse(strings)).type.toBe>() diff --git a/packages/effect/src/Array.ts b/packages/effect/src/Array.ts index 0b280500ae5..dfadc3a5e8d 100644 --- a/packages/effect/src/Array.ts +++ b/packages/effect/src/Array.ts @@ -1969,6 +1969,37 @@ export const chunksOf: { return [] }) +/** + * Creates sliding windows of size `n` from an `Iterable`. + * If the number of elements is less than `n` or if `n` is not greater than zero, + * an empty array is returned. + * + * @example + * ```ts + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * assert.deepStrictEqual(Array.window(numbers, 3), [[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + * assert.deepStrictEqual(Array.window(numbers, 6), []) + * ``` + * + * @category splitting + * @since 3.13.2 + */ +export const window: { + (n: number): (self: Iterable) => Array> + (self: Iterable, n: number): Array> +} = dual(2, (self: Iterable, n: number): Array> => { + const input = fromIterable(self) + if (n > 0 && isNonEmptyReadonlyArray(input)) { + return Array.from( + { length: input.length - (n - 1) }, + (_, index) => input.slice(index, index + n) + ) + } + return [] +}) + /** * Group equal, consecutive elements of a `NonEmptyReadonlyArray` into `NonEmptyArray`s using the provided `isEquivalent` function. * diff --git a/packages/effect/test/Array.test.ts b/packages/effect/test/Array.test.ts index 5d749a33090..4af94093cb0 100644 --- a/packages/effect/test/Array.test.ts +++ b/packages/effect/test/Array.test.ts @@ -956,6 +956,18 @@ describe("Array", () => { assertSingleChunk(Arr.make(1, 2), 3) }) + it("window", () => { + deepStrictEqual(Arr.window(2)([]), []) + + deepStrictEqual(Arr.window(2)([1, 2, 3, 4, 5]), [[1, 2], [2, 3], [3, 4], [4, 5]]) + deepStrictEqual(Arr.window(3)([1, 2, 3, 4, 5]), [[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + + // n out of bounds + deepStrictEqual(Arr.window([1, 2, 3, 4, 5], 6), []) + deepStrictEqual(Arr.window([1, 2, 3, 4, 5], 0), []) + deepStrictEqual(Arr.window([1, 2, 3, 4, 5], -1), []) + }) + it("min", () => { deepStrictEqual(Arr.min(Num.Order)([2, 1, 3]), 1) deepStrictEqual(Arr.min(Num.Order)([3]), 3)