Skip to content

Instantly share code, notes, and snippets.

@chrismilson
Last active October 12, 2022 10:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrismilson/e6549023bdca1fa9c263973b8f7a713b to your computer and use it in GitHub Desktop.
Save chrismilson/e6549023bdca1fa9c263973b8f7a713b to your computer and use it in GitHub Desktop.
A python-like zip iterator in typescript
export type Iterableify<T> = { [K in keyof T]: Iterable<T[K]> }
/**
* Iterates over multiple iterable objects in parallel.
*
* Stops iteration when any of the iterators is done.
*/
export function* zip<T extends Array<any>>(
...toZip: Iterableify<T>
): Generator<T> {
// Get iterators from the passed iterables.
const iterators = toZip.map(i => i[Symbol.iterator]())
while (true) {
// Advance all of the iterators
const results = iterators.map(i => i.next())
// If any iterators are done, we are done.
if (results.some(({ done }) => done) {
break
}
// Yield the results.
yield results.map(({ value }) => value) as T
}
}
/**
* Much the same as `zip`, but stops when all iterators are done.
*
* If an iterator is finished, it will yield undefined.
*
* Note that this will yield the return value from an iterator as
* well. For example, the following generator
*
* ```ts
* const g = function*() {
* yield 1
* return 5
* }
* ```
*
* will produce the following result:
*
* ```ts
* for (const [a, b] in zipLongest([1, 2, 3], g())) {
* console.log(a, b)
* }
* // 1 1
* // 2 5
* // 3 undefined
* ```
*/
export function* zipLongest<T extends Array<any>, U>(
...toZip: Iterableify<T>
): Generator<Partial<T>> {
const iterators = toZip.map(i => i[Symbol.iterator]())
while (true) {
const results = iterators.map(i => i.next())
if (results.every(({ done }) => done)) {
break
}
yield results.map(({ value }) => value) as Partial<T>
}
}
@shubhammehra4
Copy link

In zipLongest

    if (!results.some(({ done }) => done)) {
       break
    }

should be replaced with

    if (results.every(({ done }) => done)) {
       break
    }
!results.some(({ done }) => done) // this is true initially so it immediately breaks

@chrismilson
Copy link
Author

Thanks! Nice catch.

@fpreiss
Copy link

fpreiss commented Oct 10, 2022

It looks like the zip function is missing a closing bracket in the conditional statement:

-    if (results.some(({ done }) => done) {
+    if (results.some(({ done }) => done)) {
       break
     }

Additionally I notice, that the zip function here does not act as its own inverse, as it would in python for simple inputs. In python I get:

>>> a = (1,2,3)
>>> b = ("one","two","three")
>>> c = ("a","b","c")
>>> [a,b,c]
[(1, 2, 3), ('one', 'two', 'three'), ('a', 'b', 'c')]
>>> [*zip(*zip(a,b,c))]
[(1, 2, 3), ('one', 'two', 'three'), ('a', 'b', 'c')]

But with this zip implementation it is:

$ [...zip([...zip([1,2],["one", "two"], 'ab')])]
[ [ [ 1, 'one', 'a' ] ], [ [ 2, 'two', 'b' ] ] ]

Edit: my bad, I overlooked some brackets myself:

$ [...zip(...zip([1,2],["one", "two"], 'ab'))]
[ [ 1, 2 ], [ 'one', 'two' ], [ 'a', 'b' ] ]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment