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

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