From 55f6fbcdae58a322e61f8c5a05502200afbc0b86 Mon Sep 17 00:00:00 2001 From: Tomas Zijdemans Date: Tue, 7 Apr 2026 22:24:42 +0200 Subject: [PATCH 1/4] feat(collections/unstable): accept `Iterable` in interleave --- collections/unstable_interleave.ts | 56 +++++++++++++++++++++---- collections/unstable_interleave_test.ts | 25 +++++++++++ 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/collections/unstable_interleave.ts b/collections/unstable_interleave.ts index db3e2da0ff8f..cecc3f98eb3f 100644 --- a/collections/unstable_interleave.ts +++ b/collections/unstable_interleave.ts @@ -2,18 +2,18 @@ // This module is browser compatible. /** - * Merges multiple arrays into a single array by round-robin picking one + * Merges multiple iterables into a single array by round-robin picking one * element from each source in turn. Unlike {@linkcode zip}, which stops at * the shortest array and produces tuples, `interleave` continues through * all elements and returns a flat array. * * @experimental **UNSTABLE**: New API, yet to be vetted. * - * @typeParam T Tuple of element types, one per input array; result is + * @typeParam T Tuple of element types, one per input iterable; result is * `T[number][]`. * - * @param arrays The arrays to interleave. - * @returns A new array containing elements from all input arrays in + * @param iterables The iterables to interleave. + * @returns A new array containing elements from all input iterables in * round-robin order. * * @example Basic usage @@ -37,24 +37,66 @@ * [1, "a", true, 2, "b", 3], * ); * ``` + * + * @example With iterables + * ```ts + * import { interleave } from "@std/collections/unstable-interleave"; + * import { assertEquals } from "@std/assert"; + * + * assertEquals( + * interleave(new Set([1, 2, 3]), ["a", "b", "c"]), + * [1, "a", 2, "b", 3, "c"], + * ); + * ``` */ export function interleave( - ...arrays: { [K in keyof T]: ReadonlyArray } + ...iterables: { [K in keyof T]: Iterable } ): T[number][] { - const arrayCount = arrays.length; + const arrayCount = iterables.length; if (arrayCount === 0) return []; + const arrays = iterables.map((it) => Array.isArray(it) ? it : Array.from(it)); + let maxLength = 0; + let minLength = Infinity; let totalLength = 0; for (let i = 0; i < arrayCount; ++i) { const len = arrays[i]!.length; totalLength += len; if (len > maxLength) maxLength = len; + if (len < minLength) minLength = len; } const result: T[number][] = new Array(totalLength); - let k = 0; + if (arrayCount === 2) { + const a = arrays[0]!; + const b = arrays[1]!; + let k = 0; + for (let i = 0; i < minLength; ++i) { + result[k++] = a[i] as T[number]; + result[k++] = b[i] as T[number]; + } + for (let i = minLength; i < a.length; ++i) { + result[k++] = a[i] as T[number]; + } + for (let i = minLength; i < b.length; ++i) { + result[k++] = b[i] as T[number]; + } + return result; + } + + if (minLength === maxLength) { + let k = 0; + for (let i = 0; i < maxLength; ++i) { + for (let j = 0; j < arrayCount; ++j) { + result[k++] = arrays[j]![i] as T[number]; + } + } + return result; + } + + let k = 0; for (let i = 0; i < maxLength; ++i) { for (let j = 0; j < arrayCount; ++j) { if (i < arrays[j]!.length) { diff --git a/collections/unstable_interleave_test.ts b/collections/unstable_interleave_test.ts index 4ea70fa75003..ae0f5833c40a 100644 --- a/collections/unstable_interleave_test.ts +++ b/collections/unstable_interleave_test.ts @@ -79,3 +79,28 @@ Deno.test({ ); }, }); + +Deno.test({ + name: "interleave() handles non-array iterables", + fn() { + function* numbers() { + yield 1; + yield 2; + yield 3; + } + assertEquals( + interleave(numbers(), ["a", "b", "c"]), + [1, "a", 2, "b", 3, "c"], + ); + }, +}); + +Deno.test({ + name: "interleave() handles Set iterables", + fn() { + assertEquals( + interleave(new Set([1, 2, 3]), ["a", "b", "c"]), + [1, "a", 2, "b", 3, "c"], + ); + }, +}); From b9a16ff9dad2d220b4d9e59389ce2906137bc035 Mon Sep 17 00:00:00 2001 From: Tomas Zijdemans Date: Wed, 8 Apr 2026 08:12:03 +0200 Subject: [PATCH 2/4] docs --- collections/unstable_interleave.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/collections/unstable_interleave.ts b/collections/unstable_interleave.ts index cecc3f98eb3f..f3c8a9838b54 100644 --- a/collections/unstable_interleave.ts +++ b/collections/unstable_interleave.ts @@ -2,18 +2,17 @@ // This module is browser compatible. /** - * Merges multiple iterables into a single array by round-robin picking one - * element from each source in turn. Unlike {@linkcode zip}, which stops at - * the shortest array and produces tuples, `interleave` continues through - * all elements and returns a flat array. + * Returns all elements from the given iterables in round-robin order. + * Unlike {@linkcode zip}, which stops at the shortest iterable and returns + * tuples, `interleave` continues until all input iterables are exhausted and + * returns a flat array. * * @experimental **UNSTABLE**: New API, yet to be vetted. * - * @typeParam T Tuple of element types, one per input iterable; result is - * `T[number][]`. + * @typeParam T The tuple of element types in the input iterables. * * @param iterables The iterables to interleave. - * @returns A new array containing elements from all input iterables in + * @returns A new array containing all elements from the input iterables in * round-robin order. * * @example Basic usage @@ -69,6 +68,7 @@ export function interleave( const result: T[number][] = new Array(totalLength); + // Fast path for two arrays if (arrayCount === 2) { const a = arrays[0]!; const b = arrays[1]!; @@ -86,6 +86,7 @@ export function interleave( return result; } + // Fast path for equal-length arrays if (minLength === maxLength) { let k = 0; for (let i = 0; i < maxLength; ++i) { @@ -96,6 +97,7 @@ export function interleave( return result; } + // General case let k = 0; for (let i = 0; i < maxLength; ++i) { for (let j = 0; j < arrayCount; ++j) { From 71912f2658061c5870db65550445d6465b6a5596 Mon Sep 17 00:00:00 2001 From: Tomas Zijdemans Date: Thu, 9 Apr 2026 20:00:11 +0200 Subject: [PATCH 3/4] fix --- collections/unstable_interleave.ts | 14 +----------- collections/unstable_interleave_test.ts | 30 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/collections/unstable_interleave.ts b/collections/unstable_interleave.ts index f3c8a9838b54..409032e105d7 100644 --- a/collections/unstable_interleave.ts +++ b/collections/unstable_interleave.ts @@ -5,7 +5,7 @@ * Returns all elements from the given iterables in round-robin order. * Unlike {@linkcode zip}, which stops at the shortest iterable and returns * tuples, `interleave` continues until all input iterables are exhausted and - * returns a flat array. + * returns a flat array. All input iterables are consumed eagerly. * * @experimental **UNSTABLE**: New API, yet to be vetted. * @@ -86,18 +86,6 @@ export function interleave( return result; } - // Fast path for equal-length arrays - if (minLength === maxLength) { - let k = 0; - for (let i = 0; i < maxLength; ++i) { - for (let j = 0; j < arrayCount; ++j) { - result[k++] = arrays[j]![i] as T[number]; - } - } - return result; - } - - // General case let k = 0; for (let i = 0; i < maxLength; ++i) { for (let j = 0; j < arrayCount; ++j) { diff --git a/collections/unstable_interleave_test.ts b/collections/unstable_interleave_test.ts index ae0f5833c40a..faf3ccb90f45 100644 --- a/collections/unstable_interleave_test.ts +++ b/collections/unstable_interleave_test.ts @@ -80,6 +80,36 @@ Deno.test({ }, }); +Deno.test({ + name: "interleave() handles second array longer", + fn() { + assertEquals( + interleave([1, 2, 3], ["a"]), + [1, "a", 2, 3], + ); + }, +}); + +Deno.test({ + name: "interleave() handles three equal-length arrays", + fn() { + assertEquals( + interleave([1, 2], ["a", "b"], [true, false]), + [1, "a", true, 2, "b", false], + ); + }, +}); + +Deno.test({ + name: "interleave() handles four equal-length arrays", + fn() { + assertEquals( + interleave([1, 2], ["a", "b"], [true, false], [10, 20]), + [1, "a", true, 10, 2, "b", false, 20], + ); + }, +}); + Deno.test({ name: "interleave() handles non-array iterables", fn() { From b8269fb5c8c7c4c10725cb9310ef218c0ea78a0a Mon Sep 17 00:00:00 2001 From: Tomas Zijdemans Date: Fri, 10 Apr 2026 21:36:27 +0200 Subject: [PATCH 4/4] fix --- collections/unstable_interleave.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/collections/unstable_interleave.ts b/collections/unstable_interleave.ts index 409032e105d7..ca1e4883062f 100644 --- a/collections/unstable_interleave.ts +++ b/collections/unstable_interleave.ts @@ -57,13 +57,11 @@ export function interleave( const arrays = iterables.map((it) => Array.isArray(it) ? it : Array.from(it)); let maxLength = 0; - let minLength = Infinity; let totalLength = 0; for (let i = 0; i < arrayCount; ++i) { const len = arrays[i]!.length; totalLength += len; if (len > maxLength) maxLength = len; - if (len < minLength) minLength = len; } const result: T[number][] = new Array(totalLength); @@ -72,15 +70,16 @@ export function interleave( if (arrayCount === 2) { const a = arrays[0]!; const b = arrays[1]!; + const minLen = Math.min(a.length, b.length); let k = 0; - for (let i = 0; i < minLength; ++i) { + for (let i = 0; i < minLen; ++i) { result[k++] = a[i] as T[number]; result[k++] = b[i] as T[number]; } - for (let i = minLength; i < a.length; ++i) { + for (let i = minLen; i < a.length; ++i) { result[k++] = a[i] as T[number]; } - for (let i = minLength; i < b.length; ++i) { + for (let i = minLen; i < b.length; ++i) { result[k++] = b[i] as T[number]; } return result;