Skip to content

Instantly share code, notes, and snippets.

@sebinsua
Last active July 21, 2023 22:47
Show Gist options
  • Save sebinsua/e756554faaa2d346aff0341374ee792e to your computer and use it in GitHub Desktop.
Save sebinsua/e756554faaa2d346aff0341374ee792e to your computer and use it in GitHub Desktop.
type _Tuple<
T,
N extends number,
R extends readonly T[] = []
> = R["length"] extends N ? R : _Tuple<T, N, readonly [T, ...R]>;
type Tuple<T, N extends number> = _Tuple<T, N> & {
readonly length: N;
[I: number]: T;
[Symbol.iterator]: () => IterableIterator<T>;
};
function zip<
A extends readonly any[],
Length extends A["length"],
B extends Tuple<any, Length>
>(a: A, b: B): Tuple<readonly [A[number], B[number]], Length> {
if (a.length !== b.length) {
throw new Error(`zip cannot operate on different length arrays; ${a.length} !== ${b.length}`);
}
return a.map((v, index) => [v, b[index]]) as Tuple<
readonly [A[number], B[number]],
Length
>;
}
const a = [1, 2, 3] as const;
const b1 = [1, 2, 6, 2, 4] as const;
const b2 = [1, 2, 6] as const;
// @ts-expect-error Source has 5 element(s) but target allows only 3.
const c1 = zip(a, b1);
const c2 = zip(a, b2);
console.log(c2);
// ^?
@sebinsua
Copy link
Author

I improved upon this and wrote it up as a blog post here: https://twitter.com/sebinsua/status/1656297294008819712

@sebinsua
Copy link
Author

sebinsua commented Jul 21, 2023

type Zipped<A extends readonly (readonly any[])[]> = {
  [RowIndex in keyof A[0]]: RowIndex extends "length" ? number : {
    [ArrayIndex in keyof A]: A[ArrayIndex] extends readonly any[] ? A[ArrayIndex][RowIndex] : never;
  };
};

type NumericRange<TotalLength extends number, TempRange extends any[] = []> =
  TempRange['length'] extends TotalLength ? TempRange : NumericRange<TotalLength, [...TempRange, TempRange['length']]>;

type TupleFromKeys<T, K extends readonly (keyof T)[]> = {
  [I in keyof K]: K[I] extends keyof T ? T[K[I]] : never;
};

type Tuple<T extends readonly (readonly any[])[]> = TupleFromKeys<Zipped<T>, NumericRange<T[0]['length']>>;

function zip<const T extends readonly (readonly any[])[]>(...arrays: T): Tuple<T> {
  const minLength = Math.min(...arrays.map(arr => arr.length));
  const result: any[][] = [];

  for (let i = 0; i < minLength; i++) {
    const zippedItem = arrays.map(arr => arr[i]);
    result.push(zippedItem);
  }

  return result as Tuple<T>;
}

const grid = [
  ['a', 1, true],
  ['b', 2, false],
  ['c', 3, true]
] as const;

const [col1, col2, col3] = zip(...grid);
console.log(col1);
console.log(col2);
console.log(col3);

^ This approach would be useful if you needed to convert a grid of rows into a list of columns.

See here: https://www.typescriptlang.org/play?#code/C4TwDgpgBAWglmSATAPAQShAHsCA7JAZygCcIBDJAezwBsQoAKMym+qcvEAbQF0BKPgD4oAXigBvAFBQo3AEpUA7gEkC2KHDxQA1hBBUAZlDTcADL14AuKItXqsmHPiJQARLXwBzYAAs3UAD8UHgArgC2AEYQJFA20rKy3GgkJOQgakgaWrr6RibWJsmp6ZnYvE64BMQs1HQMnDwVwaYpaRkOvArKZVgVNngQAG4xANwyUAC+49NSUqCQUAByETFwAMbynF4QKAAqVMDktAAy3n6VLsRhUTEANFB7EOFgW3g7l9UcXHxicrwiUQTJ4vN47bgAck87z8EIq2CqrgOR1O518QUez1e22gNhW4TWmxx+0OxzOMN8D24ADpaSDse8IA96WCIJDoT5fHCAeN5uBoHtQmBPAAxEhUcIAaX0hH2D0ln1ctTYDEYegMxj2ggBfwSchUmm06vyksKku4Knhzi+xs1GL23HNlv6IWGYyksz5i0Fwt2e0VNQodXYzCDKu+TW1gMeQtF4qlMpQ8EQEFQeyED3xhNZ+3MXShaLhQiEvMMoTw62AcBoUAAXggUOsaIRgI8A6Qw-UmMqu40+FHGLTqeQSiBCDYtRPY36RHqm3gW1BwlpyZy-gBZch+anLvCD2kj9qEHfkMCMQ9iESH6kcvz8fjjWTzxdkQihWjAGx9roVcR8XmyIYVCxIwnitnAfxmKMmhQCgS4rmi0FwAA1Mh-CSBMT7Nq29YpkgKi4OEfyHukx7hKe56pJeHCpNwcACI+iSvu+wDUmAoSEL4jC4cgBHPA+EyTHMshkMAoQkNozEfhwxA+p4+wlh6czPq2XgkHASB-NwEyQuQEIPAAjA8wAkKEEC8HcOkQpE+lQAATA8hjHIQ5mWUkELrLZADMxmmeZUgVOQxAqbyKlyE2tBGVAEUOdFVC0F5v51gg+7UmpGkCc+8UQDeVBeIwEUGZlzbZbl+UxcVC6lbQeUFfFXkPkAA

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