I have zip and unzip functions like this:
function range(size: number, startAt: number = 0): ReadonlyArray<number> {
return [...Array(size).keys()].map((i) => i startAt);
}
function unzip<T extends unknown[]>(array: T[]) /* ??? */ {
const maxLength = Math.max(...array.map((x) => x.length));
return array.reduce(
(acc, val) => {
val.forEach((v, i) => acc[i].push(v));
return acc;
},
range(maxLength).map(() => [])
);
}
function zip<T extends unknown[]>(...arrays: T[]) /* ??? */ {
const maxLength = Math.max(...arrays.map((x) => x.length));
return range(maxLength).map((i) =>
range(arrays.length).map((_, k) => arrays[k][i])
);
}
I have no idea how to type the return types though. Is it even possible?
I think it would be nice if zip([1, 2, 3], ["a", "b"])
returned with a type of [number | undefined, string | undefined][]
. It seems that there is an answer here as someone in the comments pointed out that does precisely this. Likewise, it would be nice if unzip([1, "a"], [2, "b"], [3, undefined])
returned a type of [number[], string[]]
or maybe [number[], (string | undefined)[]]
depending on whichever is easiest.
I am a typescript noob, so naturally I tried to find some types on the internet, but they all seem to go the "lazy" route and type returns as any
.
If this is even possible, how would I type the return types of these two functions (as well as similar functions where you "invert" the type of an array)?
CodePudding user response:
I got a solution for unzip
that does what you want.
function unzip<
T extends [...{ [K in keyof S]: S[K] }][], S extends any[]
>(arr: [...T]): T[0] extends infer A
? { [K in keyof A]: T[number][K & keyof T[number]][] }
: never
{
const maxLength = Math.max(...arr.map((x) => x.length));
return arr.reduce(
(acc: any, val) => {
val.forEach((v, i) => acc[i].push(v));
return acc;
},
range(maxLength).map(() => [])
);
}
Let's see if it works:
const a = unzip([[1, "a"], [2, "b"], [3, "c"]])
// ^? [number[], string[]]
const b = unzip([[1, "a", new Date()], [2, "b", new Date()], [3, "c", new Date()]] )
// ^? [number[], string[], Date[]]
const c = unzip([[1, "a"], [2, "b"], [3, undefined]])
// ^? [number[], (string | undefined)[]]
Here is an alternative solution that is fully compatible with the solution above. But for array literals, this will produce more accurate return types.
type ValidContent =
| null
| string
| number
| boolean
| Array<JSON>
| Date
| undefined
| {
[prop: string]: ValidContent
}
type UnzipReturn<T extends any[]> = T[0] extends infer A
? {
[K in keyof A]: [...{
[K2 in keyof T]: T[K2][K & keyof T[K2]]
}]
}
: never
function unzip<
T extends [...{[K in keyof S]: S[K]}][], S extends ValidContent[]
>(arr: [...T]): UnzipReturn<T> {
const maxLength = Math.max(...arr.map((x) => x.length));
return arr.reduce(
(acc: any, val) => {
val.forEach((v, i) => acc[i].push(v));
return acc;
},
range(maxLength).map(() => [])
);
}
const a = unzip([[1, "a"], [2, "b"], [3, "c"]])
// ^? [[1, 2, 3], ["a", "b", "c"]]
const b = unzip([[1, "a", new Date()], [2, "b", new Date()], [3, "c", new Date()]] )
// ^? [[1, 2, 3], ["a", "b", "c"], [Date, Date, Date]]
const c = unzip([[1, "a"], [2, "b"], [3, undefined]])
// ^? [[1, 2, 3], ["a", "b", undefined]]
const d = unzip([[1, "a"], [2, "b"], [3, "c"]] as [number, string][])
// ^? [number[], string[]]