Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MultidimensionalArray type #277

Merged
merged 7 commits into from
Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export {ConditionalPick} from './source/conditional-pick';
export {UnionToIntersection} from './source/union-to-intersection';
export {Stringified} from './source/stringified';
export {FixedLengthArray} from './source/fixed-length-array';
export {MultidimensionalArray} from './source/multidimensional-array';
export {MultidimensionalReadonlyArray} from './source/multidimensional-readonly-array';
export {IterableElement} from './source/iterable-element';
export {Entry} from './source/entry';
export {Entries} from './source/entries';
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ Click the type names for complete docs.
- [`UnionToIntersection`](source/union-to-intersection.d.ts) - Convert a union type to an intersection type.
- [`Stringified`](source/stringified.d.ts) - Create a type with the keys of the given type changed to `string` type.
- [`FixedLengthArray`](source/fixed-length-array.d.ts) - Create a type that represents an array of the given type and length.
- [`MultidimensionalArray`](source/multidimensional-array.d.ts) - Create a type that represents a multidimensional array of the given type and dimensions.
- [`MultidimensionalReadonlyArray`](source/multidimensional-readonly-array.d.ts) - Create a type that represents a multidimensional readonly array of the given type and dimensions.
- [`IterableElement`](source/iterable-element.d.ts) - Get the element type of an `Iterable`/`AsyncIterable`. For example, an array or a generator.
- [`Entry`](source/entry.d.ts) - Create a type that represents the type of an entry of a collection.
- [`Entries`](source/entries.d.ts) - Create a type that represents the type of the entries of a collection.
Expand Down
26 changes: 26 additions & 0 deletions source/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,29 @@ export type IsEqual<T, U> =
(<G>() => G extends U ? 1 : 2)
? true
: false;

/**
Infer the length of the given array `<T>`.

@link https://itnext.io/implementing-arithmetic-within-typescripts-type-system-a1ef140a6f6f
*/
type TupleLength<T extends readonly unknown[]> = T extends {readonly length: infer L} ? L : never;

/**
Create a tuple type of the given length `<L>`.

@link https://itnext.io/implementing-arithmetic-within-typescripts-type-system-a1ef140a6f6f
*/
type BuildTuple<L extends number, T extends readonly unknown[] = []> = T extends {readonly length: L}
? T
: BuildTuple<L, [...T, unknown]>;

/**
Create a tuple of length `A` and a tuple composed of two other tuples,
the inferred tuple `U` and a tuple of length `B`, then extracts the length of tuple `U`.

@link https://itnext.io/implementing-arithmetic-within-typescripts-type-system-a1ef140a6f6f
*/
export type Subtract<A extends number, B extends number> = BuildTuple<A> extends [...(infer U), ...BuildTuple<B>]
? TupleLength<U>
: never;
41 changes: 41 additions & 0 deletions source/multidimensional-array.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {IsEqual, Subtract} from './internal';

type Recursive<T> = Array<Recursive<T>>;

/**
Creates a type that represents a multidimensional array of the given type and dimension.

Use-cases:
- Return a n-dimensional array from functions.
- Declare a n-dimensional array by defining its dimensions rather than declaring `[]` repetitively.
- Infer the dimensions of a n-dimensional array automatically from function arguments.
- Avoid the need to know in advance the dimensions of a n-dimensional array allowing them to be dynamic.

@example
```
import {MultidimensionalArray} from 'type-fest';

function emptyMatrix<T extends number>(dimensions: T): MultidimensionalArray<unknown, T> {
const matrix: unknown[] = [];

let subMatrix = matrix;
for (let i = 1; i < dimensions; ++i) {
Menecats marked this conversation as resolved.
Show resolved Hide resolved
subMatrix[0] = [];
subMatrix = subMatrix[0] as unknown[];
}

return matrix as MultidimensionalArray<unknown, T>;
}

const matrix = emptyMatrix(3);

matrix[0][0][0] = 42;
```

@category Utilities
*/
export type MultidimensionalArray<Element, Dimensions extends number> = number extends Dimensions
? Recursive<Element>
: IsEqual<Dimensions, 0> extends true
? Element
: Array<MultidimensionalArray<Element, Subtract<Dimensions, 1>>>;
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
45 changes: 45 additions & 0 deletions source/multidimensional-readonly-array.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {IsEqual, Subtract} from './internal';

/**
Creates a type that represents a multidimensional readonly array that of the given type and dimension.

Use-cases:
- Return a n-dimensional array from functions.
- Declare a n-dimensional array by defining its dimensions rather than declaring `[]` repetitively
- Infer the dimensions of a n-dimensional array automatically from function arguments
- Avoid the need to know in advance the dimensions of a n-dimensional array allowing them to be dynamic

@example
```
import {MultidimensionalReadonlyArray} from 'type-fest';

function emptyMatrix<T extends number>(dimensions: T): MultidimensionalReadonlyArray<unknown, T> {
const matrix: unknown[] = [];

let subMatrix = matrix;
for (let i = 1; i < dimensions; ++i) {
Menecats marked this conversation as resolved.
Show resolved Hide resolved
subMatrix[0] = [];
if (i < dimensions - 1) {
subMatrix = subMatrix[0] as unknown[];
} else {
subMatrix[0] = 42;
}
}

return matrix as MultidimensionalReadonlyArray<unknown, T>;
}

const matrix = emptyMatrix(3);

const answer = matrix[0][0][0]; // 42
```

@category Utilities
*/
export type MultidimensionalReadonlyArray<Element, Dimensions extends number> = number extends Dimensions
? Recursive<Element>
: IsEqual<Dimensions, 0> extends true
? Element
: ReadonlyArray<MultidimensionalReadonlyArray<Element, Subtract<Dimensions, 1>>>;

type Recursive<T> = ReadonlyArray<Recursive<T>>;
28 changes: 28 additions & 0 deletions test-d/multidimensional-array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {expectType} from 'tsd';
import {MultidimensionalArray} from '../index';

function createArray<T extends number>(dimensions: T): MultidimensionalArray<unknown, T> {
const root: unknown[] = [];

let array = root;
for (let i = 1; i < dimensions; ++i) {
Menecats marked this conversation as resolved.
Show resolved Hide resolved
array[0] = [];
array = array[0] as unknown[];
}

return root as MultidimensionalArray<unknown, T>;
}

sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
const a: MultidimensionalArray<number, 3> = [];
const b: MultidimensionalArray<boolean, number> = [];
const c = createArray(2);
const d = createArray(7);

a[0][0][0] = 42;

type RecursiveArray<T> = Array<RecursiveArray<T>>;

expectType<number[][][]>(a);
expectType<RecursiveArray<boolean>>(b);
expectType<unknown[][]>(c);
expectType<unknown[][][][][][][]>(d);
32 changes: 32 additions & 0 deletions test-d/multidimensional-readonly-array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {expectType} from 'tsd';
import {MultidimensionalReadonlyArray} from '../index';

function createArray<T extends number>(dimensions: T): MultidimensionalReadonlyArray<string, T> {
const root: unknown[] = [];

let array = root;
for (let i = 1; i < dimensions; ++i) {
array[0] = [];
if (i < dimensions - 1) {
array = array[0] as unknown[];
} else {
array[0] = '42';
}
}

return root as MultidimensionalReadonlyArray<unknown, T> as MultidimensionalReadonlyArray<string, T>;
}

const a: MultidimensionalReadonlyArray<number, 3> = [];
const b: MultidimensionalReadonlyArray<boolean, number> = [];
const c = createArray(2);

const answer = c[0][0]; // '42'

type RecursiveArray<T> = ReadonlyArray<RecursiveArray<T>>;

expectType<string>(answer);

expectType<ReadonlyArray<ReadonlyArray<readonly number[]>>>(a);
expectType<RecursiveArray<boolean>>(b);
expectType<ReadonlyArray<readonly string[]>>(c);