diff --git a/sudoku.go b/sudoku.go index 06ab7d0..4a40de5 100644 --- a/sudoku.go +++ b/sudoku.go @@ -1 +1,142 @@ package main + +import "fmt" + +// BoardSize represents the size of the Sudoku board. +const BoardSize = 9 + +// SubgridSize represents the size of the subgrids within the Sudoku board. +const SubgridSize = 3 + +// EmptyCellValue represents the value of an empty cell in the Sudoku board. +const EmptyCellValue = 0 + +// isValidBoard checks if the input Sudoku board is valid. +func isValidBoard(board [][]int) bool { + if len(board) != BoardSize || len(board[0]) != BoardSize { + return false + } + + for _, row := range board { + for _, num := range row { + if num < 0 || num > BoardSize { + return false + } + } + } + return true +} + +// isPlacementValid checks if it's safe to place a number in a specific cell. +func isPlacementValid(board [][]int, row, col, num int) bool { + for x := 0; x < BoardSize; x++ { + if board[row][x] == num || board[x][col] == num { + return false + } + } + + startRow, startCol := row-row%SubgridSize, col-col%SubgridSize + for i := 0; i < SubgridSize; i++ { + for j := 0; j < SubgridSize; j++ { + if board[i+startRow][j+startCol] == num { + return false + } + } + } + + return true +} + +// findEmptyCell finds the first empty cell in the board. +func findEmptyCell(board [][]int) (int, int, bool) { + for i := 0; i < BoardSize; i++ { + for j := 0; j < BoardSize; j++ { + if board[i][j] == EmptyCellValue { + return i, j, true + } + } + } + return 0, 0, false +} + +// placeNumber places a number in a specific cell on the board. +func placeNumber(board [][]int, row, col, num int) { + board[row][col] = num +} + +// resetCell resets a cell to 0 on the board. +func resetCell(board [][]int, row, col int) { + board[row][col] = EmptyCellValue +} + +// SolveSudoku is a function that solves the Sudoku puzzle using a recursive backtracking algorithm. +// It takes the Sudoku board as input and returns the solved board. If the input board is invalid or +// there is no solution, it returns nil. +func SolveSudoku(board [][]int) [][]int { + var solve func() bool + solve = func() bool { + row, col, isEmpty := findEmptyCell(board) + if !isEmpty { + return true + } + for num := 1; num <= BoardSize; num++ { + if isPlacementValid(board, row, col, num) { + placeNumber(board, row, col, num) + + if solve() { + return true + } + + resetCell(board, row, col) + } + } + return false + } + + if !isValidBoard(board) { + return nil + } + + if solve() { + return board + } + + return nil +} + +// printBoard prints the Sudoku board in a readable format. +func printBoard(board [][]int) { + fmt.Println("[") + for i, row := range board { + fmt.Print(" [") + for j, num := range row { + fmt.Printf("%d", num) + if j < len(row)-1 { + fmt.Print(", ") + } + } + fmt.Print("]") + if i < len(board)-1 { + fmt.Print(",") + } + fmt.Println() + } + fmt.Println("]") +} + +func main() { + puzzle := [][]int{ + {5, 3, 0, 0, 7, 0, 0, 0, 0}, + {6, 0, 0, 1, 9, 5, 0, 0, 0}, + {0, 9, 8, 0, 0, 0, 0, 6, 0}, + {8, 0, 0, 0, 6, 0, 0, 0, 3}, + {4, 0, 0, 8, 0, 3, 0, 0, 1}, + {7, 0, 0, 0, 2, 0, 0, 0, 6}, + {0, 6, 0, 0, 0, 0, 2, 8, 0}, + {0, 0, 0, 4, 1, 9, 0, 0, 5}, + {0, 0, 0, 0, 8, 0, 0, 7, 9}, + } + + solved := SolveSudoku(puzzle) + printBoard(solved) +} diff --git a/sudoku_test.go b/sudoku_test.go index 63e3196..454aba6 100644 --- a/sudoku_test.go +++ b/sudoku_test.go @@ -1,38 +1,302 @@ package main import ( - "reflect" - "testing" + "reflect" + "testing" ) func TestSolveSudoku(t *testing.T) { - input := [][]int{ - {5, 3, 0, 0, 7, 0, 0, 0, 0}, - {6, 0, 0, 1, 9, 5, 0, 0, 0}, - {0, 9, 8, 0, 0, 0, 0, 6, 0}, - {8, 0, 0, 0, 6, 0, 0, 0, 3}, - {4, 0, 0, 8, 0, 3, 0, 0, 1}, - {7, 0, 0, 0, 2, 0, 0, 0, 6}, - {0, 6, 0, 0, 0, 0, 2, 8, 0}, - {0, 0, 0, 4, 1, 9, 0, 0, 5}, - {0, 0, 0, 0, 8, 0, 0, 7, 9}, - } - - expected := [][]int{ - {5, 3, 4, 6, 7, 8, 9, 1, 2}, - {6, 7, 2, 1, 9, 5, 3, 4, 8}, - {1, 9, 8, 3, 4, 2, 5, 6, 7}, - {8, 5, 9, 7, 6, 1, 4, 2, 3}, - {4, 2, 6, 8, 5, 3, 7, 9, 1}, - {7, 1, 3, 9, 2, 4, 8, 5, 6}, - {9, 6, 1, 5, 3, 7, 2, 8, 4}, - {2, 8, 7, 4, 1, 9, 6, 3, 5}, - {3, 4, 5, 2, 8, 6, 1, 7, 9}, - } - - solved := SolveSudoku(input) - - if !reflect.DeepEqual(solved, expected) { - t.Errorf("Sudoku puzzle was not solved correctly. Expected:\n%v\n\nGot:\n%v", expected, solved) - } -} \ No newline at end of file + input := [][]int{ + {5, 3, 0, 0, 7, 0, 0, 0, 0}, + {6, 0, 0, 1, 9, 5, 0, 0, 0}, + {0, 9, 8, 0, 0, 0, 0, 6, 0}, + {8, 0, 0, 0, 6, 0, 0, 0, 3}, + {4, 0, 0, 8, 0, 3, 0, 0, 1}, + {7, 0, 0, 0, 2, 0, 0, 0, 6}, + {0, 6, 0, 0, 0, 0, 2, 8, 0}, + {0, 0, 0, 4, 1, 9, 0, 0, 5}, + {0, 0, 0, 0, 8, 0, 0, 7, 9}, + } + + expected := [][]int{ + {5, 3, 4, 6, 7, 8, 9, 1, 2}, + {6, 7, 2, 1, 9, 5, 3, 4, 8}, + {1, 9, 8, 3, 4, 2, 5, 6, 7}, + {8, 5, 9, 7, 6, 1, 4, 2, 3}, + {4, 2, 6, 8, 5, 3, 7, 9, 1}, + {7, 1, 3, 9, 2, 4, 8, 5, 6}, + {9, 6, 1, 5, 3, 7, 2, 8, 4}, + {2, 8, 7, 4, 1, 9, 6, 3, 5}, + {3, 4, 5, 2, 8, 6, 1, 7, 9}, + } + + solved := SolveSudoku(input) + + if !reflect.DeepEqual(solved, expected) { + t.Errorf("Sudoku puzzle was not solved correctly. Expected:\n%v\n\nGot:\n%v", expected, solved) + } +} + +// Test sudoku with a single empty cell. +func TestSolveSingleEmptyCell(t *testing.T) { + input := [][]int{ + {1, 2, 3, 4, 5, 6, 7, 8, 9}, + {4, 5, 6, 7, 8, 9, 1, 2, 3}, + {7, 8, 9, 1, 2, 3, 4, 5, 6}, + {2, 1, 4, 3, 6, 5, 8, 9, 7}, + {3, 6, 5, 8, 9, 7, 2, 1, 4}, + {8, 9, 7, 2, 1, 4, 3, 6, 5}, + {5, 3, 1, 6, 4, 2, 9, 7, 8}, + {6, 4, 2, 9, 7, 8, 5, 3, 1}, + {9, 7, 8, 5, 3, 1, 6, 4, 0}, + } + + expected := [][]int{ + {1, 2, 3, 4, 5, 6, 7, 8, 9}, + {4, 5, 6, 7, 8, 9, 1, 2, 3}, + {7, 8, 9, 1, 2, 3, 4, 5, 6}, + {2, 1, 4, 3, 6, 5, 8, 9, 7}, + {3, 6, 5, 8, 9, 7, 2, 1, 4}, + {8, 9, 7, 2, 1, 4, 3, 6, 5}, + {5, 3, 1, 6, 4, 2, 9, 7, 8}, + {6, 4, 2, 9, 7, 8, 5, 3, 1}, + {9, 7, 8, 5, 3, 1, 6, 4, 2}, + } + + solved := SolveSudoku(input) + + if !reflect.DeepEqual(solved, expected) { + t.Errorf("Sudoku puzzle with a single empty cell was not solved correctly. Expected:\n%v\n\nGot:\n%v", expected, solved) + } +} + +// World's hardest sudoku created by Arto Inkala. +func TestSolveInkalaSudoku(t *testing.T) { + input := [][]int{ + {8, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 3, 6, 0, 0, 0, 0, 0}, + {0, 7, 0, 0, 9, 0, 2, 0, 0}, + {0, 5, 0, 0, 0, 7, 0, 0, 0}, + {0, 0, 0, 0, 4, 5, 7, 0, 0}, + {0, 0, 0, 1, 0, 0, 0, 3, 0}, + {0, 0, 1, 0, 0, 0, 0, 6, 8}, + {0, 0, 8, 5, 0, 0, 0, 1, 0}, + {0, 9, 0, 0, 0, 0, 4, 0, 0}, + } + + expected := [][]int{ + {8, 1, 2, 7, 5, 3, 6, 4, 9}, + {9, 4, 3, 6, 8, 2, 1, 7, 5}, + {6, 7, 5, 4, 9, 1, 2, 8, 3}, + {1, 5, 4, 2, 3, 7, 8, 9, 6}, + {3, 6, 9, 8, 4, 5, 7, 2, 1}, + {2, 8, 7, 1, 6, 9, 5, 3, 4}, + {5, 2, 1, 9, 7, 4, 3, 6, 8}, + {4, 3, 8, 5, 2, 6, 9, 1, 7}, + {7, 9, 6, 3, 1, 8, 4, 5, 2}, + } + + solved := SolveSudoku(input) + + if !reflect.DeepEqual(solved, expected) { + t.Errorf("Inkala's Hardest Sudoku puzzle was not solved correctly. Expected:\n%v\n\nGot:\n%v", expected, solved) + } +} + +// Test an already solved sudoku. +func TestCompletedSudoku(t *testing.T) { + input := [][]int{ + {5, 3, 4, 6, 7, 8, 9, 1, 2}, + {6, 7, 2, 1, 9, 5, 3, 4, 8}, + {1, 9, 8, 3, 4, 2, 5, 6, 7}, + {8, 5, 9, 7, 6, 1, 4, 2, 3}, + {4, 2, 6, 8, 5, 3, 7, 9, 1}, + {7, 1, 3, 9, 2, 4, 8, 5, 6}, + {9, 6, 1, 5, 3, 7, 2, 8, 4}, + {2, 8, 7, 4, 1, 9, 6, 3, 5}, + {3, 4, 5, 2, 8, 6, 1, 7, 9}, + } + + expected := [][]int{ + {5, 3, 4, 6, 7, 8, 9, 1, 2}, + {6, 7, 2, 1, 9, 5, 3, 4, 8}, + {1, 9, 8, 3, 4, 2, 5, 6, 7}, + {8, 5, 9, 7, 6, 1, 4, 2, 3}, + {4, 2, 6, 8, 5, 3, 7, 9, 1}, + {7, 1, 3, 9, 2, 4, 8, 5, 6}, + {9, 6, 1, 5, 3, 7, 2, 8, 4}, + {2, 8, 7, 4, 1, 9, 6, 3, 5}, + {3, 4, 5, 2, 8, 6, 1, 7, 9}, + } + + solved := SolveSudoku(input) + + if !reflect.DeepEqual(solved, expected) { + t.Errorf("Sudoku puzzle was not solved correctly. Expected:\n%v\n\nGot:\n%v", expected, solved) + } +} + +// Test soduku with an incomplete column. +func TestIncompleteColumn(t *testing.T) { + input := [][]int{ + {5, 3, 4, 6, 7, 8, 9, 0, 2}, + {6, 7, 2, 1, 9, 5, 3, 0, 8}, + {1, 9, 8, 3, 4, 2, 5, 0, 7}, + {8, 5, 9, 7, 6, 1, 4, 0, 3}, + {4, 2, 6, 8, 5, 3, 7, 0, 1}, + {7, 1, 3, 9, 2, 4, 8, 0, 6}, + {9, 6, 1, 5, 3, 7, 2, 0, 4}, + {2, 8, 7, 4, 1, 9, 6, 0, 5}, + {3, 4, 5, 2, 8, 6, 1, 0, 9}, + } + + expected := [][]int{ + {5, 3, 4, 6, 7, 8, 9, 1, 2}, + {6, 7, 2, 1, 9, 5, 3, 4, 8}, + {1, 9, 8, 3, 4, 2, 5, 6, 7}, + {8, 5, 9, 7, 6, 1, 4, 2, 3}, + {4, 2, 6, 8, 5, 3, 7, 9, 1}, + {7, 1, 3, 9, 2, 4, 8, 5, 6}, + {9, 6, 1, 5, 3, 7, 2, 8, 4}, + {2, 8, 7, 4, 1, 9, 6, 3, 5}, + {3, 4, 5, 2, 8, 6, 1, 7, 9}, + } + + solved := SolveSudoku(input) + + if !reflect.DeepEqual(solved, expected) { + t.Errorf("Sudoku puzzle was not solved correctly. Expected:\n%v\n\nGot:\n%v", expected, solved) + } +} + +// Test soduku with an incomplete column. +func TestIncompleteRow(t *testing.T) { + input := [][]int{ + {5, 3, 4, 6, 7, 8, 9, 1, 2}, + {6, 7, 2, 1, 9, 5, 3, 4, 8}, + {1, 9, 8, 3, 4, 2, 5, 6, 7}, + {8, 5, 9, 7, 6, 1, 4, 2, 3}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {7, 1, 3, 9, 2, 4, 8, 5, 6}, + {9, 6, 1, 5, 3, 7, 2, 8, 4}, + {2, 8, 7, 4, 1, 9, 6, 3, 5}, + {3, 4, 5, 2, 8, 6, 1, 7, 9}, + } + + expected := [][]int{ + {5, 3, 4, 6, 7, 8, 9, 1, 2}, + {6, 7, 2, 1, 9, 5, 3, 4, 8}, + {1, 9, 8, 3, 4, 2, 5, 6, 7}, + {8, 5, 9, 7, 6, 1, 4, 2, 3}, + {4, 2, 6, 8, 5, 3, 7, 9, 1}, + {7, 1, 3, 9, 2, 4, 8, 5, 6}, + {9, 6, 1, 5, 3, 7, 2, 8, 4}, + {2, 8, 7, 4, 1, 9, 6, 3, 5}, + {3, 4, 5, 2, 8, 6, 1, 7, 9}, + } + + solved := SolveSudoku(input) + + if !reflect.DeepEqual(solved, expected) { + t.Errorf("Sudoku puzzle was not solved correctly. Expected:\n%v\n\nGot:\n%v", expected, solved) + } +} + +// Test soduku with an incomplete block. +func TestIncompleteBlock(t *testing.T) { + input := [][]int{ + {0, 0, 0, 6, 7, 8, 9, 1, 2}, + {0, 0, 0, 1, 9, 5, 3, 4, 8}, + {0, 0, 0, 3, 4, 2, 5, 6, 7}, + {8, 5, 9, 7, 6, 1, 4, 2, 3}, + {4, 2, 6, 8, 5, 3, 7, 9, 1}, + {7, 1, 3, 9, 2, 4, 8, 5, 6}, + {9, 6, 1, 5, 3, 7, 2, 8, 4}, + {2, 8, 7, 4, 1, 9, 6, 3, 5}, + {3, 4, 5, 2, 8, 6, 1, 7, 9}, + } + + expected := [][]int{ + {5, 3, 4, 6, 7, 8, 9, 1, 2}, + {6, 7, 2, 1, 9, 5, 3, 4, 8}, + {1, 9, 8, 3, 4, 2, 5, 6, 7}, + {8, 5, 9, 7, 6, 1, 4, 2, 3}, + {4, 2, 6, 8, 5, 3, 7, 9, 1}, + {7, 1, 3, 9, 2, 4, 8, 5, 6}, + {9, 6, 1, 5, 3, 7, 2, 8, 4}, + {2, 8, 7, 4, 1, 9, 6, 3, 5}, + {3, 4, 5, 2, 8, 6, 1, 7, 9}, + } + + solved := SolveSudoku(input) + + if !reflect.DeepEqual(solved, expected) { + t.Errorf("Sudoku puzzle was not solved correctly. Expected:\n%v\n\nGot:\n%v", expected, solved) + } +} + +// Test sudoku with an invalid board (not 9x9). Should return nil. +func TestInvalidBoard(t *testing.T) { + input := [][]int{ + {5, 3, 0, 0, 7, 0, 0, 0, 0}, + {6, 0, 0, 1, 9, 5, 0, 0, 0}, + {0, 9, 8, 0, 0, 0, 0, 6, 0}, + {8, 0, 0, 0, 6, 0, 0, 0, 3}, + {4, 0, 0, 8, 0, 3, 0, 0, 1}, + {7, 0, 0, 0, 2, 0, 0, 0, 6}, + } + + expected := [][]int(nil) + + solved := SolveSudoku(input) + + if !reflect.DeepEqual(solved, expected) { + t.Errorf("Sudoku puzzle was not solved correctly. Expected:\n%v\n\nGot:\n%v", expected, solved) + } +} + +// Test sudoku with starting numbers in invalid locations. Should return nil. +func TestInvalidBoardLayout(t *testing.T) { + input := [][]int{ + {5, 3, 2, 0, 7, 0, 0, 0, 0}, + {6, 0, 0, 1, 9, 5, 0, 0, 0}, + {0, 9, 8, 0, 0, 0, 0, 6, 0}, + {8, 0, 0, 0, 6, 0, 0, 0, 3}, + {4, 0, 0, 8, 0, 3, 0, 0, 1}, + {7, 0, 0, 0, 2, 0, 0, 0, 6}, + {0, 6, 0, 0, 0, 0, 2, 8, 0}, + {0, 0, 0, 4, 1, 9, 0, 0, 5}, + {0, 0, 0, 0, 8, 0, 0, 7, 9}, + } + + expected := [][]int(nil) + + solved := SolveSudoku(input) + + if !reflect.DeepEqual(solved, expected) { + t.Errorf("Sudoku puzzle was not solved correctly. Expected:\n%v\n\nGot:\n%v", expected, solved) + } +} + +// Test sudoku with invalid numbers (e.g. -5, 88). Should return nil. +func TestInvalidNumbers(t *testing.T) { + input := [][]int{ + {-5, -3, 0, 0, -7, 0, 0, 0, 0}, + {-6, 0, 0, -1, -9, -5, 0, 0, 0}, + {0, -9, 8, 0, 0, 0, 0, -6, 0}, + {8, 0, 0, 0, -6, 0, 0, 0, 3}, + {4, 0, 0, -8, 0, 3, 0, 0, 1}, + {7, 0, 0, 0, 29, 0, 0, 0, 6}, + {0, 6, 0, 0, 0, 0, -2, 8, 0}, + {0, 0, 0, 4, 1, 9, 0, 0, 5}, + {0, 0, 0, 0, 88, 0, 0, 78, -9}, + } + + expected := [][]int(nil) + + solved := SolveSudoku(input) + + if !reflect.DeepEqual(solved, expected) { + t.Errorf("Sudoku puzzle was not solved correctly. Expected:\n%v\n\nGot:\n%v", expected, solved) + } +}