Last active
October 12, 2022 10:03
-
-
Save chrismilson/e6549023bdca1fa9c263973b8f7a713b to your computer and use it in GitHub Desktop.
A python-like zip iterator in typescript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
} | |
} |
Thanks! Nice catch.
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
In
zipLongest
should be replaced with