public static SudokuBoard SolveInternal(SudokuBoard sudokuBoard) { if (!sudokuBoard.IsValid()) { return(null); } var cells = Enumerable.Range(0, NumberOfBoardCellsInSingleDirection) .SelectMany( vni => Enumerable.Range(0, NumberOfBoardCellsInSingleDirection) .Select(hni => new Cell(hni, vni, sudokuBoard._numbers))).ToList(); Debug.Assert(cells.Count == NumberOfBoardCellsInSingleDirection * NumberOfBoardCellsInSingleDirection); var rows = Enumerable.Range(0, NumberOfBoardCellsInSingleDirection).Select(rowIndex => { var rowCells = cells.Where(c => c.VerticalIndex == rowIndex).ToList(); return(new SolutionRegion(rowCells)); }).ToList(); // TODO: rows and cols are mismatched, imporve just for readability var columns = Enumerable.Range(0, NumberOfBoardCellsInSingleDirection).Select(columnIndex => { var columnCells = cells.Where(c => c.HorizontalIndex == columnIndex).ToList(); return(new SolutionRegion(columnCells)); }).ToList(); var quadrants = Enumerable.Range(0, NumberOfQuadrantsInOneDirection).SelectMany(quadrantHorizontalIndex => Enumerable.Range(0, NumberOfQuadrantsInOneDirection) .Select( quadrantVerticalIndex => new { QuadrantHorizontalIndex = quadrantHorizontalIndex, QuadrantVerticalIndex = quadrantVerticalIndex })).Select(i => { var quardantCells = cells.Where( c => c.HorizontalIndex >= i.QuadrantHorizontalIndex * NumberOfQuadrantCellsInOneDirection && c.HorizontalIndex < (i.QuadrantHorizontalIndex + 1) * NumberOfQuadrantCellsInOneDirection && c.VerticalIndex >= i.QuadrantVerticalIndex * NumberOfQuadrantCellsInOneDirection && c.VerticalIndex < (i.QuadrantVerticalIndex + 1) * NumberOfQuadrantCellsInOneDirection ).ToList(); return(new SolutionRegion(quardantCells)); }).ToList(); Debug.Assert(new[] { rows, columns, quadrants }.All(s => s.Count == NumberOfBoardCellsInSingleDirection)); var solutionRegions = rows.Concat(columns).Concat(quadrants).ToList(); var cellsToSolutionRegionsMap = cells.ToDictionary(c => c, c => (IReadOnlyCollection <SolutionRegion>)solutionRegions.Where(r => r.Cells.Contains(c)).ToList()); Debug.Assert(cellsToSolutionRegionsMap.Values.All(s => s.Count == 3)); var cellsToProcess = cells.Where(c => !c.Number.HasValue).ToList(); var processedCellsBacktrackingInfos = new Stack <CellBacktractingInfo>(); while (true) { var currentCellToProcess = cellsToProcess.OrderBy(c => GetCellPossibleValues(c, cellsToSolutionRegionsMap[c]).Count) .FirstOrDefault(); if (currentCellToProcess == null) { return(sudokuBoard); } Debug.Assert(!currentCellToProcess.Number.HasValue); var currentCellToProcessPossibleValues = GetCellPossibleValues(currentCellToProcess, cellsToSolutionRegionsMap[currentCellToProcess]); var currentCellToProcessBacktrackingInfo = new CellBacktractingInfo(currentCellToProcess, currentCellToProcessPossibleValues); do { var currentCellToProcessPossibleNumber = currentCellToProcessBacktrackingInfo.GetNextPossibleNumber(); if (!currentCellToProcessPossibleNumber.HasValue) { currentCellToProcessBacktrackingInfo.Cell.Number = null; if (!processedCellsBacktrackingInfos.Any()) { return(null); } currentCellToProcessBacktrackingInfo = processedCellsBacktrackingInfos.Pop(); cellsToProcess.Add(currentCellToProcessBacktrackingInfo.Cell); continue; } currentCellToProcessBacktrackingInfo.Cell.Number = currentCellToProcessPossibleNumber; processedCellsBacktrackingInfos.Push(currentCellToProcessBacktrackingInfo); cellsToProcess.Remove(currentCellToProcessBacktrackingInfo.Cell); break; } while (true); } }