|
1 |
| -import type { RoArray, RoArray2D, IsUnion } from './metaprogramming.js'; |
| 1 | +import type { RoPair, RoTuple, RoArray, Extends, Xor, Not } from './metaprogramming.js'; |
2 | 2 |
|
3 |
| -export const range = (length: number) => [...Array(length).keys()]; |
| 3 | +export type RoTuple2D<T = unknown> = RoTuple<RoTuple<T>>; |
| 4 | +export type RoArray2D<T = unknown> = RoArray<RoArray<T>>; |
4 | 5 |
|
5 |
| -//TODO the intent here is that number represents a number literal, but strictly speaking |
6 |
| -// the type allows for unions of number literals (and an array of such unions) |
7 |
| -//The reason for not just sticking to unions is that unions lose order information which is |
8 |
| -// relevant in some cases (and iterating over them is a pain). |
9 |
| -export type IndexEs = number | RoArray<number>; |
| 6 | +type TupleRangeImpl<L extends number, A extends number[] = []> = |
| 7 | + A["length"] extends L |
| 8 | + ? A |
| 9 | + : TupleRangeImpl<L, [...A, A["length"]]>; |
10 | 10 |
|
11 |
| -export type ElementIndexPairs<T extends RoArray> = |
12 |
| - [...{ [K in keyof T]: K extends `${infer N extends number}` ? [T[K], N] : never }]; |
| 11 | +export type TupleRange<L extends number> = |
| 12 | + number extends L |
| 13 | + ? never |
| 14 | + : L extends any |
| 15 | + ? TupleRangeImpl<L> |
| 16 | + : never; |
13 | 17 |
|
14 |
| -export const elementIndexPairs = <const T extends RoArray>(arr: T) => |
15 |
| - range(arr.length).map(i => [arr[i], i]) as ElementIndexPairs<T>; |
| 18 | +export type Range<L extends number> = |
| 19 | + L extends any |
| 20 | + ? number extends L |
| 21 | + ? number[] |
| 22 | + : TupleRange<L> |
| 23 | + : never; |
16 | 24 |
|
17 |
| -export type Entries<T extends RoArray> = |
| 25 | +export type TupleWithLength<T, L extends number> = |
| 26 | + TupleRange<L> extends infer R extends RoArray<number> |
| 27 | + ? [...{ [K in keyof R]: T }] |
| 28 | + : never; |
| 29 | + |
| 30 | +export type RoTupleWithLength<T, L extends number> = Readonly<TupleWithLength<T, L>>; |
| 31 | + |
| 32 | +export const range = <const L extends number>(length: L) => |
| 33 | + [...Array(length).keys()] as Range<L>; |
| 34 | + |
| 35 | +//capitalization to highlight that this is intended to be a literal or a union of literals |
| 36 | +export type IndexEs = number; |
| 37 | + |
| 38 | +//utility type to reduce boilerplate of iteration code by replacing: |
| 39 | +// `T extends readonly [infer Head extends T[number], ...infer Tail extends RoTuple<T[number]>]` |
| 40 | +//with just: |
| 41 | +// `T extends HeadTail<T, infer Head, infer Tail>` |
| 42 | +//this also avoids the somewhat common mistake of accidentally dropping the readonly modifier |
| 43 | +export type HeadTail<T extends RoTuple, Head extends T[number], Tail extends RoTuple<T[number]>> = |
| 44 | + readonly [Head, ...Tail]; |
| 45 | + |
| 46 | +export type TupleEntries<T extends RoTuple> = |
18 | 47 | [...{ [K in keyof T]: K extends `${infer N extends number}` ? [N, T[K]] : never }];
|
19 | 48 |
|
20 |
| -export const entries = <const T extends RoArray>(arr: T) => |
21 |
| - range(arr.length).map(i => [i, arr[i]]) as Entries<T>; |
| 49 | +//const aware version of Array.entries |
| 50 | +export type Entries<T extends RoArray> = |
| 51 | + T extends RoTuple |
| 52 | + ? TupleEntries<T> |
| 53 | + : T extends RoArray<infer U> |
| 54 | + ? [number, U][] |
| 55 | + : never; |
| 56 | + |
| 57 | +export function entries<const T extends RoTuple>(arr: T): TupleEntries<T>; |
| 58 | +export function entries<const T extends RoArray>(arr: T): Entries<T>; |
| 59 | +export function entries(arr: readonly any[]): [number, any][] { |
| 60 | + return [...arr.entries()]; |
| 61 | +} |
22 | 62 |
|
23 |
| -export type Flatten<T extends RoArray> = |
24 |
| - T extends readonly [infer Head, ...infer Tail extends RoArray] |
25 |
| - ? Head extends RoArray |
26 |
| - ? [...Head, ...Flatten<Tail>] |
27 |
| - : [Head, ...Flatten<Tail>] |
| 63 | +export type IsArray<T> = T extends RoArray<any> ? true : false; |
| 64 | +export type IsFlat<A extends RoArray> = true extends IsArray<A[number]> ? false : true; |
| 65 | + |
| 66 | +export type TupleFlatten<T extends RoTuple> = |
| 67 | + T extends HeadTail<T, infer Head, infer Tail> |
| 68 | + ? Head extends RoTuple |
| 69 | + ? [...Head, ...TupleFlatten<Tail>] |
| 70 | + : [Head, ...TupleFlatten<Tail>] |
28 | 71 | : [];
|
29 | 72 |
|
30 |
| -export type InnerFlatten<T extends RoArray> = |
31 |
| - [...{ [K in keyof T]: |
| 73 | +type StripArray<T> = T extends RoArray<infer E> ? E : T; |
| 74 | + |
| 75 | +export type Flatten<A extends RoArray> = |
| 76 | + A extends RoTuple |
| 77 | + ? TupleFlatten<A> |
| 78 | + : StripArray<A[number]>[]; |
| 79 | + |
| 80 | +export const flatten = <const A extends RoArray>(arr: A) => |
| 81 | + arr.flat() as Flatten<A>; |
| 82 | + |
| 83 | +export type InnerFlatten<A extends RoArray> = |
| 84 | + [...{ [K in keyof A]: |
32 | 85 | K extends `${number}`
|
33 |
| - ? T[K] extends RoArray |
34 |
| - ? Flatten<T[K]> |
35 |
| - : T[K] |
| 86 | + ? A[K] extends RoArray |
| 87 | + ? Flatten<A[K]> |
| 88 | + : A[K] |
36 | 89 | : never
|
37 | 90 | }];
|
38 | 91 |
|
39 |
| -export type IsFlat<T extends RoArray> = |
40 |
| - T extends readonly [infer Head, ...infer Tail extends RoArray] |
41 |
| - ? Head extends RoArray |
42 |
| - ? false |
43 |
| - : IsFlat<Tail> |
44 |
| - : true; |
45 |
| - |
46 |
| -export type Unflatten<T extends RoArray> = |
47 |
| - [...{ [K in keyof T]: K extends `${number}` ? [T[K]] : never }]; |
48 |
| - |
49 |
| -export type AllSameLength<T extends RoArray2D, L extends number | void = void> = |
50 |
| - T extends readonly [infer Head extends RoArray, ...infer Tail extends RoArray2D] |
51 |
| - ? L extends void |
52 |
| - ? AllSameLength<Tail, Head["length"]> |
53 |
| - : Head["length"] extends L |
54 |
| - ? AllSameLength<Tail, L> |
55 |
| - : false |
56 |
| - : true; |
57 |
| - |
58 |
| -export type IsRectangular<T extends RoArray> = |
59 |
| - //1d array is rectangular |
60 |
| - T extends RoArray2D ? AllSameLength<T> : IsFlat<T>; |
| 92 | +export type Unflatten<A extends RoArray> = |
| 93 | + [...{ [K in keyof A]: K extends `${number}` ? [A[K]] : never }]; |
| 94 | + |
| 95 | +export type IsRectangular<T extends RoTuple> = |
| 96 | + T extends RoTuple2D |
| 97 | + ? T extends HeadTail<T, infer Head extends RoTuple, infer Tail extends RoTuple2D> |
| 98 | + ? Tail extends readonly [] |
| 99 | + ? true //a column is rectangular |
| 100 | + : Tail[number]["length"] extends Head["length"] ? true : false |
| 101 | + : true //empty is rectangular |
| 102 | + : true; //a row is rectangular |
61 | 103 |
|
62 | 104 | export type Column<A extends RoArray2D, I extends number> =
|
63 | 105 | [...{ [K in keyof A]: K extends `${number}` ? A[K][I] : never }];
|
64 | 106 |
|
65 | 107 | export const column = <const A extends RoArray2D, const I extends number>(tupArr: A, index: I) =>
|
66 | 108 | tupArr.map((tuple) => tuple[index]) as Column<A, I>;
|
67 | 109 |
|
68 |
| -export type Zip<A extends RoArray2D> = |
69 |
| - //TODO remove, find max length, and return undefined for elements in shorter arrays |
70 |
| - A["length"] extends 0 |
71 |
| - ? [] |
72 |
| - : IsRectangular<A> extends true |
73 |
| - ? A[0] extends infer Head extends RoArray |
| 110 | +export type TupleZip<T extends RoTuple2D> = |
| 111 | + IsRectangular<T> extends true |
| 112 | + ? T[0] extends infer Head extends RoTuple |
74 | 113 | ? [...{ [K in keyof Head]:
|
75 | 114 | K extends `${number}`
|
76 |
| - ? [...{ [K2 in keyof A]: K extends keyof A[K2] ? A[K2][K] : never }] |
| 115 | + ? [...{ [K2 in keyof T]: K extends keyof T[K2] ? T[K2][K] : never }] |
77 | 116 | : never
|
78 | 117 | }]
|
79 | 118 | : []
|
80 |
| - : never |
| 119 | + : never; |
| 120 | + |
| 121 | +export type Zip<A extends RoArray2D> = |
| 122 | + A extends RoTuple2D |
| 123 | + ? TupleZip<A> |
| 124 | + : Flatten<A>[number][][]; |
81 | 125 |
|
82 | 126 | export const zip = <const Args extends RoArray2D>(arr: Args) =>
|
83 | 127 | range(arr[0]!.length).map(col =>
|
84 | 128 | range(arr.length).map(row => arr[row]![col])
|
85 |
| - ) as unknown as ([Zip<Args>] extends [never] ? RoArray2D : Zip<Args>); |
| 129 | + ) as Zip<Args>; |
86 | 130 |
|
87 | 131 | //extracts elements with the given indexes in the specified order, explicitly forbid unions
|
88 |
| -export type OnlyIndexes<E extends RoArray, I extends IndexEs> = |
89 |
| - IsUnion<I> extends false |
90 |
| - ? I extends number |
91 |
| - ? OnlyIndexes<E, [I]> |
92 |
| - : I extends readonly [infer Head extends number, ...infer Tail extends RoArray<number>] |
93 |
| - ? E[Head] extends undefined |
94 |
| - ? OnlyIndexes<E, Tail> |
95 |
| - : [E[Head], ...OnlyIndexes<E, Tail>] |
96 |
| - : [] |
97 |
| - : never; |
| 132 | +export type TuplePickWithOrder<A extends RoArray, I extends RoTuple<number>> = |
| 133 | + I extends HeadTail<I, infer Head, infer Tail> |
| 134 | + ? A[Head] extends undefined |
| 135 | + ? TuplePickWithOrder<A, Tail> |
| 136 | + : [A[Head], ...TuplePickWithOrder<A, Tail>] |
| 137 | + : []; |
98 | 138 |
|
99 |
| -type ExcludeIndexesImpl<T extends RoArray, C extends number> = |
100 |
| - T extends readonly [infer Head, ...infer Tail] |
101 |
| - ? Head extends readonly [infer I extends number, infer V] |
102 |
| - ? I extends C |
103 |
| - ? ExcludeIndexesImpl<Tail, C> |
104 |
| - : [V, ...ExcludeIndexesImpl<Tail, C>] |
| 139 | +export type PickWithOrder<A extends RoArray, I extends RoArray<number>> = |
| 140 | + [A, I] extends [infer T extends RoTuple, infer TI extends RoTuple<number>] |
| 141 | + ? TuplePickWithOrder<T, TI> |
| 142 | + : A; |
| 143 | + |
| 144 | +export const pickWithOrder = |
| 145 | + <const A extends RoArray, const I extends RoArray<number>>(arr: A, indexes: I) => |
| 146 | + indexes.map((i) => arr[i]) as PickWithOrder<A, I>; |
| 147 | + |
| 148 | +type FilterIndexesImpl<T extends RoTuple, I extends IndexEs, E extends boolean> = |
| 149 | + T extends HeadTail<T, infer Head, infer Tail> |
| 150 | + ? Head extends RoPair<infer J extends number, infer V> |
| 151 | + ? Not<Xor<Not<E>, Extends<J, I>>> extends true |
| 152 | + ? [V, ...FilterIndexesImpl<Tail, I, E>] |
| 153 | + : FilterIndexesImpl<Tail, I, E> |
105 | 154 | : never
|
106 | 155 | : [];
|
107 | 156 |
|
108 |
| -export type ExcludeIndexes<T extends RoArray, C extends IndexEs> = |
109 |
| - ExcludeIndexesImpl<Entries<T>, C extends RoArray<number> ? C[number] : C>; |
| 157 | +export type FilterIndexes<A extends RoArray, I extends IndexEs, E extends boolean = false> = |
| 158 | + A extends infer T extends RoTuple |
| 159 | + ? FilterIndexesImpl<Entries<T>, I, E> |
| 160 | + : A; |
| 161 | + |
| 162 | +export const filterIndexes = < |
| 163 | + const T extends RoArray, |
| 164 | + const I extends RoArray<number>, |
| 165 | + const E extends boolean = false |
| 166 | +>(arr: T, indexes: I, exclude?: E) => { |
| 167 | + const indexSet = new Set(Array.isArray(indexes) ? indexes : [indexes]); |
| 168 | + return arr.filter((_,i) => indexSet.has(i) !== exclude) as FilterIndexes<T, I[number], E>; |
| 169 | +}; |
110 | 170 |
|
111 | 171 | export type Cartesian<L, R> =
|
112 | 172 | L extends RoArray
|
|
0 commit comments