Skip to content

Commit 394b7fd

Browse files
authored
fix, cleanup, and tighten type programming utils (#710)
* add excludeIndexes function implementation to array utils * tighten up type programming parts * fix: remove readonly for array function return types * further type hardening and fixing comment
1 parent 76b2031 commit 394b7fd

File tree

4 files changed

+274
-171
lines changed

4 files changed

+274
-171
lines changed

core/base/src/constants/guardians.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { MapLevels} from './../utils/index.js';
2-
import { constMap, column, cartesianRightRecursive } from './../utils/index.js';
2+
import { constMap, filterIndexes, zip, cartesianRightRecursive } from './../utils/index.js';
33
import type { Network } from './networks.js';
44

55
// prettier-ignore
@@ -30,8 +30,8 @@ const guardianKeyAndNameEntries = [[
3030
]]
3131
] as const satisfies MapLevels<[Network, string, string]>;
3232

33-
export const guardianKeys = column(cartesianRightRecursive(guardianKeyAndNameEntries), 1);
34-
export const guardianNames = column(cartesianRightRecursive(guardianKeyAndNameEntries), 2);
33+
export const [guardianKeys, guardianNames] =
34+
filterIndexes(zip(cartesianRightRecursive(guardianKeyAndNameEntries)), [1, 2]);
3535

3636
export const guardianNameToKey = constMap(guardianKeyAndNameEntries, [[0, 2], 1]);
3737
export const guardianKeyToName = constMap(guardianKeyAndNameEntries, [1, [0, 2]]);

core/base/src/utils/array.ts

+133-73
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,172 @@
1-
import type { RoArray, RoArray2D, IsUnion } from './metaprogramming.js';
1+
import type { RoPair, RoTuple, RoArray, Extends, Xor, Not } from './metaprogramming.js';
22

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>>;
45

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"]]>;
1010

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;
1317

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;
1624

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> =
1847
[...{ [K in keyof T]: K extends `${infer N extends number}` ? [N, T[K]] : never }];
1948

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+
}
2262

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>]
2871
: [];
2972

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]:
3285
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]
3689
: never
3790
}];
3891

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
61103

62104
export type Column<A extends RoArray2D, I extends number> =
63105
[...{ [K in keyof A]: K extends `${number}` ? A[K][I] : never }];
64106

65107
export const column = <const A extends RoArray2D, const I extends number>(tupArr: A, index: I) =>
66108
tupArr.map((tuple) => tuple[index]) as Column<A, I>;
67109

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
74113
? [...{ [K in keyof Head]:
75114
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 }]
77116
: never
78117
}]
79118
: []
80-
: never
119+
: never;
120+
121+
export type Zip<A extends RoArray2D> =
122+
A extends RoTuple2D
123+
? TupleZip<A>
124+
: Flatten<A>[number][][];
81125

82126
export const zip = <const Args extends RoArray2D>(arr: Args) =>
83127
range(arr[0]!.length).map(col =>
84128
range(arr.length).map(row => arr[row]![col])
85-
) as unknown as ([Zip<Args>] extends [never] ? RoArray2D : Zip<Args>);
129+
) as Zip<Args>;
86130

87131
//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+
: [];
98138

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>
105154
: never
106155
: [];
107156

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+
};
110170

111171
export type Cartesian<L, R> =
112172
L extends RoArray

0 commit comments

Comments
 (0)