using System.Collections.Generic;
public void SolveSudoku(char[][] board)
int[,] iBoard = board.Convert(c => c == '.' ? default(int) : c - '0');
board.CopyFrom(iBoard, i => (char)('0' + i));
public static class Soduku
public static void Solve(int[,] board)
throw new ArgumentNullException(nameof(board));
int N = board.GetLength(0);
if (N != board.GetLength(1))
throw new ArgumentException("Board must be square.", nameof(board));
int n = (int)Math.Sqrt(N);
throw new ArgumentException("Board dimensions are invalid.", nameof(board));
private static void Solve(in int[,] board, in int n, in int N)
if (!TrySolve(board, n, N, new Point(0, 0)))
throw new InvalidOperationException("Unsolvable.");
private static bool TrySolve(in int[,] board, in int n, in int N, in Point start)
if (board[start.Y, start.X] == default)
foreach (int candidate in GetCandidates(board, n, N, start))
board[start.Y, start.X] = candidate;
if (TrySolve(board, n, N, start))
board[start.Y, start.X] = default;
Point next = start.X + 1 == N
? new Point(0, start.Y + 1)
: new Point(start.X + 1, start.Y);
return TrySolve(board, n, N, next);
private static IEnumerable<int> GetCandidates(in int[,] board, in int n, in int N, in Point point)
var candidates = Enumerable.Range(1, N);
var used = GetVerticalNumbers(board, N, point)
.Union(GetHorizontalNumbers(board, N, point))
.Union(GetSubsquareNumbers(board, n, N, point));
return candidates.Except(used);
private static IEnumerable<int> GetVerticalNumbers(int[,] board, in int N, Point point)
return Enumerable.Range(0, N).Select(y => board[y, point.X]).Where(i => i != default);
private static IEnumerable<int> GetHorizontalNumbers(int[,] board, in int N, Point point)
return Enumerable.Range(0, N).Select(x => board[point.Y, x]).Where(i => i != default);
private static IEnumerable<int> GetSubsquareNumbers(int[,] board, int n, int N, Point point)
return Enumerable.Range(point.Y / n * n, n)
.SelectMany(y => Enumerable.Range(point.X / n * n, n).Select(x => board[y, x]))
.Where(i => i != default);
public static class JaggedArrayExtensions
public static U[,] Convert<T, U>(this T[][] arr, Func<T, U> converter)
throw new ArgumentNullException(nameof(arr));
throw new ArgumentNullException(nameof(converter));
int m = arr.Length, n = arr[0].Length;
U[,] result = new U[m, n];
for (int i = 0; i < m; ++i)
throw new ArgumentException("Array of arrays should be square.", nameof(arr));
for (int j = 0; j < n; ++j)
result[i, j] = converter(arr[i][j]);
public static void CopyFrom<T, U>(this U[][] target, T[,] source, Func<T, U> converter)
throw new ArgumentNullException(nameof(target));
throw new ArgumentNullException(nameof(source));
throw new ArgumentNullException(nameof(converter));
if (m != source.GetLength(0))
throw new ArgumentException();
int n = target[0].Length;
if (n != source.GetLength(1))
throw new ArgumentException();
for (int i = 0; i < m; ++i)
if (target[i].Length != n)
throw new ArgumentException();
for (int j = 0; j < n; ++j)
target[i][j] = converter(source[i, j]);