35314-hard-valid-sudoku

Back

Approach

  1. Get all the elements in the row as union type
  2. Check if the union type has all the numbers from 1 to 9 (i.e no duplicates) by Digits extends Foo ? true : false
  3. Repeat the process for all rows, columns, and subgrids
  4. Combine the results

Code

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

Playground

Explanation

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

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

Playground

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

Explanation

  1. 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'.

  2. 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.

  3. MapBool[never] is true, and MapBool['x'] is false.

Solution by alexandroppolus #35322