Digits extends Foo ? true : false
type Digits = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
type Row = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
type Col = {
[I in Row as `C${I}`]: [Row, I]
}
type Subgrid = {
S1: [0 | 1 | 2, 0 | 1 | 2]
S2: [0 | 1 | 2, 3 | 4 | 5]
S3: [0 | 1 | 2, 6 | 7 | 8]
S4: [3 | 4 | 5, 0 | 1 | 2]
S5: [3 | 4 | 5, 3 | 4 | 5]
S6: [3 | 4 | 5, 6 | 7 | 8]
S7: [6 | 7 | 8, 0 | 1 | 2]
S8: [6 | 7 | 8, 3 | 4 | 5]
S9: [6 | 7 | 8, 6 | 7 | 8]
}
type ValidSudoku<M extends number[][]> =
{
[R in Row]: Digits extends M[R][number] ? true : false
} & {
[C in keyof Col]: Digits extends M[Col[C][0]][Col[C][1]] ? true : false
} & {
[S in keyof Subgrid]: Digits extends M[Subgrid[S][0]][Subgrid[S][1]] ? true : false
} extends Record<string, true>
? true
: false
Solution by satohshi #35331
type ValidSudoku<M extends number[][], D extends Digits = Digits> = If<[
| (D extends D ? If<Digits, M[Minus1<D>][number], never, `Unresolved in Row ${D}`> : never)
| (D extends D ? If<Digits, M[number][Minus1<D>], never, `Unresolved in Col ${D}`> : never)
| (D extends D ? If<Digits, M[DivBy3<D>][ModBy3<D>], never, `Unresolved in Subgrid ${D}`> : never)
], [never], true, false>;
type If<A, B, C, D> = A extends B ? C : D
type Digits = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type Sections = [0 | 1 | 2, 3 | 4 | 5, 6 | 7 | 8];
type Minus1<D extends Digits> = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8][D];
type DivBy3<D extends Digits> = Sections[[never, 0, 0, 0, 1, 1, 1, 2, 2, 2][D]];
type ModBy3<D extends Digits> = Sections[[never, 0, 1, 2, 0, 1, 2, 0, 1, 2][D]];
1️⃣ Why Digits extends M[...]
Fails Without All Digits
TypeScript evaluates unions like Digits = 1 | 2 | ... | 9 as a whole.
Digits extends [1, 2, 3, 4, 5, 6, 7, 8, 9][number] // ✅ Passes
Digits extends [1, 2, 3][number] // ❌ Fails (missing digits)
2️⃣ Why D extends D ? ... : never
Works
The conditional type D extends D ? ... : never
ensures each digit in the union (1 | 2 | ... | 9) is evaluated independently
D extends D
, TypeScript treats the union as a whole, causing "all-or-nothing” failures.D extends D
, TypeScript distributes the union, checking each digit separately.type Digits = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
// Distributes over each digit:
type Distribute<T> = T extends T ? [T] : never;
Distribute<Digits>
// Resolves to: [1] | [2] | [3] | ... | [9]
I prefer work with union as it can easier handle all data types. if for some reason you don't like it. This is the alt using map.
type ValidSudoku<M extends number[][]> = (Record<`Unresolved in ${'Row'|'Col'|'Subgrid'} ${Digits}`, false> & Record<string, true>)[
| {[D in Digits]: (Record<Digits, never> & Record<number, `Unresolved in Row ${D}`>)[D & M[Minus1<D>][number]]}[Digits]
| {[D in Digits]: (Record<Digits, never> & Record<number, `Unresolved in Col ${D}`>)[D & M[number][Minus1<D>]]}[Digits]
| {[D in Digits]: (Record<Digits, never> & Record<number, `Unresolved in Subgrid ${D}`>)[D & M[DivBy3<D>][ModBy3<D>]]}[Digits]
];
type Digits = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type Sections = [0 | 1 | 2, 3 | 4 | 5, 6 | 7 | 8];
type Minus1<D extends Digits> = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8][D];
type DivBy3<D extends Digits> = Sections[[never, 0, 0, 0, 1, 1, 1, 2, 2, 2][D]];
type ModBy3<D extends Digits> = Sections[[never, 0, 1, 2, 0, 1, 2, 0, 1, 2][D]];
Solution by teamchong #35330
An curious solution without ternary expressions! Also does not use the standard type-utils (which may contain ternaries).
type RN = [number];
type R1 = [0, 1, 2, 3, 4, 5, 6, 7, 8];
type R3 = [0 | 1 | 2, 3 | 4 | 5, 6 | 7 | 8];
type Values = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type MapBool = {[N: number]: true; x: false};
type MapX = {[N: number]: 'x'} & Record<Values, never>;
type NotAllX<V extends number, D extends number = Values> = {[K in D]: MapX[K & V]}[D];
type Check<
M extends number[][],
RI extends number[],
RJ extends number[]
> = {[I in keyof RI]: {[J in keyof RJ]: NotAllX<M[RI[I]][RJ[J]]>}[number]}[number];
type ValidSudoku<M extends number[][]> = MapBool[Check<M, R1, RN> | Check<M, RN, R1> | Check<M, R3, R3>];
NotAllX<V>
is never
if V
contains all digits (Values
), and NotAllX<V>
is 'x'
otherwise.
Why it works? If V
not contains some digit (e.g. 3), then K & V
is never
for K = 3
.
MapX[never]
is 'x'
(determined by [N: number]
key), MapX[digit]
is never
, and result is 'x'
if some MapX[K & V]
is 'x'
.
Check<...> | Check<...> | Check<...>
is union of validates of all ranges (all 1x9, all 9x1, and all 3x3), and it is never
, only if all ranges contains Values
, and is 'x'
otherwise.
MapBool[never]
is true
, and MapBool['x']
is false
.
Solution by alexandroppolus #35322