Skip to content

Instantly share code, notes, and snippets.

@sebinsua
Last active August 22, 2023 09:29
Show Gist options
  • Save sebinsua/5441536ac5bbbce444f44c8f57d8fb8e to your computer and use it in GitHub Desktop.
Save sebinsua/5441536ac5bbbce444f44c8f57d8fb8e to your computer and use it in GitHub Desktop.
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 FixedSequence<T extends readonly (readonly any[])[]> = TupleFromKeys<Zipped<T>, NumericRange<T[0]['length']>>;
function zip<const T extends readonly (readonly any[])[]>(...arrays: T): FixedSequence<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 FixedSequence<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);
@sebinsua
Copy link
Author

I just realised that our code would only support const literals, so we should make it type-check for this:

// We check whether `T` is a numeric literal by checking that `number`
// does not extend from `T` but that `T` does extend from `number`.
type IsNumericLiteral<T> = number extends T
  ? false
  : T extends number
  ? true
  : false;
type InvalidArgument<T> = readonly [never, T];
 
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 FixedSequence<T extends readonly (readonly any[])[]> = TupleFromKeys<Zipped<T>, NumericRange<T[0]['length']>>;

function zip<const T extends readonly (readonly any[])[]>(...arrays:
  true extends IsNumericLiteral<T['length']>
    ? T
    : InvalidArgument<T>
): FixedSequence<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 FixedSequence<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);

See: https://www.typescriptlang.org/play?#code/PTAEHUFNQYwC0jA1qA7ggLggTqABgCp6gCWAzqAIagB2ArgLaTYkygA2JGzl7oARgE9YCZCRoBzUFkoZ89Bv2Z4AUCFAATAPaQKNLXMgAPbjQ2gAZti0N8RAXTky5hYtt2hjp81ZvzGSth4AHQqGIIADtAAkmQAcozMrAAyXDzsADwEAHygALy0AcyeJpBmFAQqoKAA-Ja8ZJBVoABcoAQl3npF2M11GNh0TdVtFg2QANxhkTE0AG68JBoAgtgSiTQYWbkF2JCU2jTswgDaNJBzzAA07QC6U6DTUaAAWiQRURoZy51lGhR7A5aI7CAAUgMOxyoNEEJ1uAEo4TtQABvZonABKWlQ0TMxlINFASEggi0FlAyxOAAZbrc2licXijL9yqAAETsMoSLBs2qFRTFNpo6rVE6rbCUQS4jT48REklkil0ili7ASqVM24s-6gCHAqGUGFwvmU8WS6XGW6Y7EWoxatrnS7YB6gAC+U3dKie0ASTBYMAxhokkCyBl4yS5WG13QF2BuBEgDAigck0C8fwohthWoKSPyzQTSZTwZOAHJOZIsKWtenWQQw+wI5W4HzC8mg9A2r6kgGO6GMOHI3AbidgmO28XIPHE+3U2WK9y4NXbtkpt72nQIpyAGLWBgAaRJZCyN330d1+0hYOJpPJBERK-yqPR0QJ8tvoH3yv3J2iNdKrI3oqHR1AQJw-n+9q0BczAemu4TPNuJBGJAGgAMqQAAjkMNAwCGHS1jqeogqA4KXvqwhZnCD7IgQm47nuh6CMebwfKh2w3N2-qTlk1JWuWQ7Vtkq5ehYdC4RgJDAqAABe7wZDAwJkHIBEAUR5EkWRQIkVRCJIqCY7BJQaqSmQLTNAMQznrEXEpGkEqZGBAnNkJzTVKBbmtKAuILJwKxrBsWw5Co8JtEhKHoVhOF4dsz7VIpNDKaADDiE2i5PgAsrIcDBClNAGWOxnqmQuWUBEoJFfkuRFcEC5YPC8IuglSV7GQdDsBgbS6caub3F61QWFouCgpycgkE+VITKQoAZMlqVDlNJAANRLfCcUis1chyWxGjRNwtgFEVpmleVlV5NVaonCQCIutUrXtRgwQRHQZBwKC22fHtiaNc0rr9ReGB0NghL3R1VAUOFqEYdhZQxTkHpeptoASCw5i5uipaUKWNwAIw3JZkC3FcGP8NjoAAEw3GM7CNETGMwGTADM+ODITKhapQFCbWuSMnIp7B47AWjsJTQvsIzOaye8BXBCjSw-SozXC5AtVaBIoL8zjP1K5yqvq-z5Pa0pyt6xrwuM41QA

@sebinsua
Copy link
Author

// We check whether `T` is a numeric literal by checking that `number`
// does not extend from `T` but that `T` does extend from `number`.
type IsNumericLiteral<T> = number extends T
  ? false
  : T extends number
  ? true
  : false;
type UserProvidedInvalidArgument<T> = readonly [invalidArgumentToZip: never];
 
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 FixedSequence<T extends readonly (readonly any[])[]> = TupleFromKeys<Zipped<T>, NumericRange<T[0]['length']>>;

function zip<U extends (readonly any[]), T extends U[]>(...arrays: T):
  true extends IsNumericLiteral<T[number]['length']>
    ? FixedSequence<T>
    : UserProvidedInvalidArgument<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 any;
}

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

const [col1, col2, col3] = zip(...grid1);
const [col4, col5, col6] = zip(...grid2);
const [col7, col8, col9] = zip(...grid3);

console.log(col1);
console.log(col2);
console.log(col3);

See: https://www.typescriptlang.org/play?#code/PTAEHUFNQYwC0jA1qA7ggLggTqABgCp6gCWAzqAIagB2ArgLaTYkygA2JGzl7oARgE9YCZCRoBzUFkoZ89Bv2Z4AUCFAATAPaQKNLXMgAPbjQ2gAZti0N8RAXTky5hYtt2hjp81ZvzGSth4AHQqGIIADtAAkmQAcozMrAAyXDzsADwEAHygALy0AcyeJpBmFAQqoKAA-Ja8ZJBVoABcoAQl3npF2M11GNh0TdVtFg2QANxhkdAAqo3YAArWAG4kGpAa0TQrvOsAgtgSiTQYWbkF2JCU2jTswgDa4rucGofHTKcEWgBaJBFtGiQFbMAC6U1A0yioD+ESiGgy+06ZQ0FCuNy0d2EAAp0bd7lQaIIHqCAJQki6gADezQeACUtKhthsjKQaKAkJBBFoLKB9g8AAyg0FtBlMszGZHlUAAInYZQkWBltUKimKbRp1WqD0O2EogmZkvEHK5PL5Ir5OuweoNEqMoKlqNAeMxBMoRJJKv5uv1hvt9MZfodgOBzAhoAAvlMoyoodAEkwWDA6e6JJAsgZeMkFVhHd01dgADTtSAMCIpyTQLwoiju4kOgoU-LNAil8upyAPADk8skWC7Dur0u+GCzObgKtbZYradaoATSWTHYzo-Y2b7cGLD2CO6n7crxb3M87PfHA9B2SmcfadAi8oAYtYGABpLlkLLF59553XfE4zncryBDkhe+TUrS0RsiagGgM+FrPg80SDqU0oAWaHR1AQDwIUhwa0KG2DRle4TQveJBGJsADKkAAI5DDQMDph0Q5Oi6WKgLiv6usIdYkiBlIELeD5Pq+gjvrC8LnMWC5JseWSCqC3a9oqcADtkl6xhYdAMRgJCYqAABe-wZLM36cRi7G8WSh7frMFLYjuwSUNa+pkG0wEtM0AxDN+sQySkaR6pkWEKIEimnhuanNNUdRkRRGjUXRZSMec0VzvMzDLFoawbFsOx7G8RwnGcOTgdUMCYmQcgMOI64qWBACyshwMENU0A5O7OTaZCtZQETYl1+S5F1wTKVgpKkuGFU0FVP5kHQ7AYG0Vmeo24KxtUFhaLg2LynIJBgQKEykKAGSgG1dVYMdJAANQ3aSZVatNs1GXCmzRNwtgFF1rm9f1g15MN1pPGS4bVFc82LcEER0GQcDYq98IfaWk3NBGG0-hgdDYOyEMLXIlC1kS0axs9cgSCwGgAIxgQ8tJdpQXbFlTxbeZADqE7AlUYIW9P8EzoAAEzFmM7CNBzFBk7z2pdjAAsAMys4M7NUJL3O8+tZOgBT6yC7T9OM8zStDKC0ugN2-PFsL9Ri+zZvdnLxaK9IyugioEtczNGBTFrOsaPL+sy4boAsy7Jv212ltCyL4ym-TjugM7bNu+tKhaw8FXsKHmfW5n8sNoZ-wdcEftU6j6eZwALMWmcAKw11o7AAGwF69xd+4L5fc+bmcAOwN+wAAcA8AJyt0Xjl+-LqNp5VjeQKNWgSNimdlz7c-yovy8513M3z1vK+N9PExAA

@sebinsua
Copy link
Author

sebinsua commented Jul 27, 2023

// We check whether `T` is a numeric literal by checking that `number`
// does not extend from `T` but that `T` does extend from `number`.
type IsNumericLiteral<T> = number extends T
  ? false
  : T extends number
  ? true
  : false;
type UserProvidedInvalidArgument<T> = readonly [invalidArgumentToZip: never];
 
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 FixedSequence<T extends readonly (readonly any[])[]> = TupleFromKeys<Zipped<T>, NumericRange<T[0]['length']>>;

function zip<const U extends (readonly any[]), T extends U[]>(...arrays: T):
  true extends IsNumericLiteral<T[number]['length']>
    ? FixedSequence<T>
    : UserProvidedInvalidArgument<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 any;
}

// type GetLength<A extends any[]> = A['length'];
// type Increment<N extends number, A extends any[] = []> = GetLength<A> extends N ? GetLength<[...A, any]> : Increment<N, [...A, any]>

type PrependNumber<A extends any[]> = [...A, A['length']];
type CreateTuple<N extends number, A extends any[] = []> = A['length'] extends N ? A : CreateTuple<N, PrependNumber<A>>;
type Drop<N extends number, A extends any[], B extends any[] = []> = B['length'] extends N ? A : Drop<N, A extends [any, ...infer R] ? R : never, PrependNumber<B>>;

type NRange<Start extends number, Stop extends number> = Drop<Start, CreateTuple<Stop>>;

function range<Start extends number, Stop extends number>(start: Start, stop: Stop): NRange<Start, Stop> {
  const result: number[] = [];
  for (let i = start; i < stop; i++) {
    result.push(i);
  }
  return result as NRange<Start, Stop>
}

function enumerate<Items extends ReadonlyArray<any>, Length extends Items['length'](items: Items) {
  return zip(range(0, items.length as Length), items);
}

const output = enumerate(["a", "b", "c", "d", "e"] as const);

ziping values with a range is quite a fun way of enumerating that appears to come from Haskell.

See: https://www.typescriptlang.org/play?#code/PTAEHUFNQYwC0jA1qA7ggLggTqABgCp6gCWAzqAIagB2ArgLaTYkygA2JGzl7oARgE9YCZCRoBzUFkoZ89Bv2Z4AUCFAATAPaQKNLXMgAPbjQ2gAZti0N8RAXTky5hYtt2hjp81ZvzGSth4AHQqGIIADtAAkmQAcozMrAAyXDzsADwEAHygALy0AcyeJpBmFAQqoKAA-Ja8ZJBVoABcoAQl3npF2M11GNh0TdVtFg2QANxhkdAAqo3YAArWAG4kGpAa0TQrvOsAgtgSiTQYWbkF2JCU2jTswgDa4rucGofHTKcEWgBaJBFtGiQFbMAC6U1A0yioD+ESiGgy+06ZQ0FCuNy0d2EAAp0bd7lQaIIHqCAJQki6gADezQeACUtKhthsjKQaKAkJBBFoLKB9g8AAyg0FtBlMszGZHlUAAInYZQkWBltUKimKbRp1WqD0O2EogmZkvEHK5PL5Ir5OuweoNEqMoKlqNAeMxBMoRJJKv5uv1hvt9MZfodgOBzAhoAAvlMoyoodAEkwWDA6e6JJAsgZeMkFVhHd01dgADTtSAMCIpyTQLwoiju4kOgoU-LNAil8upyAPADk8skWC7Dur0u+GCzObgKtbZYradaoATSWTHYzo-Y2b7cGLD2CO6n7crxb3M87PfHA9B2SmcfadAi8oAYtYGABpLlkLLF59553XfE4zncryBDkhe+TUrS0RsiagGgM+FrPg80SDqU0oAWaHR1AQDwIUhwa0KG2DRle4TQveJBGJsADKkAAI5DDQMDph0Q5Oi6WKgLiv6usIdYkiBlIELeD5Pq+gjvrC8LnMWC5JseWSCqC3a9oqcADtkl6xhYdAMRgJCYqAABe-wZDAmJkHIszfpxGLsbxZKHt+swUtiO7BJQ1r6mQbTAS0zQDEM36xDJKRpHqmRYQogSKaeG5qc01R1GRFEaNRdFlIx5zxXO8zMMsWhrBsWw7HsbxHCcZw5OB1SmTQ5mgAw4jripYEALKyHAwQNTQLk7u5NpkJ1lARNifX5LkfXBMpWCkqS4Y1XVVxkHQ7AYG0dmeo24KxtUFhaLg2LynIJBgQKEykKAGT1Y145nSQADUd2klVWrzXIRlwps0TcLYBR9Z5g3DaNeTjdaTxkuG1SLctGDBBEdBkHA2LvfCX2lrNzQRttP4YHQ2DslDK1ULWRLRrG6gkdAADikAYE1WCIt+dmUvyMUqQOUzkzMoDbDAVyfGccTfpFzDFkiLHE-WYFNgU1O0+OiK5OL84qrLdNwBk247vsxZ1qBbQ83zZQC1urna4SggXrGFOgMskBRGYCaBAzStM1Lpui0pZ7ClM1sAMLotwgl3umgtK8LRZ8ozHoNqA0uWqz-bIV0yt1EibT+9cgdCSHxa2-bGiO8wCsadbAAi1gRBkocoU64ei1H9bFgAQg3G2x6BBRN57sVJzWKeR205daJXcT10rDx1sWrniBYxR0g6dR0nOQIghHecooX2AZE36nEVzcRyZRo7YIYNf5oExZH8PQs9JSQ+V0f7kYMWGeyJAQfyhkV8RLvmnaTAul9J6krF-Y+p9k511AN-G+BZsjYnMk-Noj8T7FnMsPJBGBh6kjaAfZcyDn5QMwT-Z6r0fxLRWoCHobcSThl2vtQ650CgIJPrdC6oA0ERFug9J6motQExhnDBG2ISDo2qJjSGNNcb410NDIm85D5gMvkQ7IKhMYqC0jpPS7IyiJD1NwDIqMGAUCVnSLiWIfSCAyHWbIxY1aBW+mQbubNQTCIcfrBxPDmhXBxnjQy-xcQdmxAKYsaQjGTXHHItWpIQkeNJioUhWhHBwzkAUHRiY37YgeDKSgMpiwyn4Lk2UMBCkyg0CUyAMoHSUAoK9WaQA

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