The code in this article and an implementation of zipLongest
are in this gist.
The built-in zip
function is ubiquitous in python. It allows highly readable iteration over multiple iterables:
def naturals():
n = 1
while True:
yield n
n += 1
for n, c in zip(naturals(), "Hello!"):
print(n, c)
# 1 H
# 2 e
# 3 l
# 4 l
# 5 o
# 6 !
Now, with Mapped Types in typescript, we can do the same thing, all while keeping our lovely types too!
With mapped types, we can make an Iterableify
type, which will take a type, and change it so that its members are of type Iterable:
type Iterableify<T> = { [K in keyof T]: Iterable<T[K]> }
For example
type T1 = Iterableify<string[]> // Iterable<string>[]
type T2 = Iterableify<{a: number, b: string}> // {a: Iterable<number>, b: Iterable<string>}
type T3 = Iterableify<[number, string][]> // Iterable<[number, string]>[]
We can then make a simple zip function like so:
function* zip<T extends Array<any>>(
...toZip: Iterableify<T>
): Generator<T> {
// Get iterators for all of the 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 of the iterators are done, we should stop.
if (results.some(({ done }) => done)) {
break
}
// We can assert the yield type, since we know none
// of the iterators are done.
yield results.map(({ value }) => value) as T
}
}
Since the mapped types affect typescript tuples as well, we can now do something like this:
for (const [a, b] of zip([1, 2, 3], "abc")) {
// type of a is number!
// type of b is string!
// This has no compilation errors!
console.log(b.repeat(a))
}
Top comments (1)
Nice,
zip
in python is really convenient, thank you for this type safe implementation in typescript!