- commit
- 2564c95e4f50a98d993745fb2487a63be7a1ce09
- parent
- f269c2c85d1b54d61a82a1e79c358efc0b5de4fe
- author
- Dami <dami.etoile@gmail.com>
| M | sudoku/ | 15 | +++++- |
| M | sudoku/ | 89 | ++++++++++++++---------------- |
@@ -47,7 +47,20 @@ func fillGrid(g *grid) bool {
4747 }4848 4949 // countSolutions counts solutions of the grid up to limit using backtracking.50+// Returns 0 immediately if the grid already contains conflicting values.5051 func countSolutions(g *grid, limit int) int {52+ for y := range gridSize {53+ for x := range gridSize {54+ if g[y][x].v != 0 && !isValid(g, g[y][x].v, x, y) {55+ return 056+ }57+ }58+ }59+ return countSolutionsRec(g, limit)60+}61+62+// countSolutionsRec is the recursive backtracking helper for countSolutions.63+func countSolutionsRec(g *grid, limit int) int {5164 for y := range gridSize {5265 for x := range gridSize {5366 if g[y][x].v == 0 {@@ -55,7 +68,7 @@ func countSolutions(g *grid, limit int) int {
5568 for val := 1; val <= gridSize; val++ {5669 if isValid(g, val, x, y) {5770 g[y][x].v = val58- count += countSolutions(g, limit-count)71+ count += countSolutionsRec(g, limit-count)5972 g[y][x].v = 06073 if count >= limit {6174 return count@@ -101,26 +101,30 @@ func TestIsValid(t *testing.T) {
101101 })102102 103103 t.Run("all 9 boxes center placement", func(t *testing.T) {104- // Place value 1 in center of each 3x3 box, then verify no cross-box conflict.104+ // Place a distinct value in the center of each 3x3 box so that no row,105+ // column, or box conflict exists, then verify isValid agrees.105106 g := emptyGrid()106- centers := [][2]int{107- {1, 1},108- {4, 1},109- {7, 1},110- {1, 4},111- {4, 4},112- {7, 4},113- {1, 7},114- {4, 7},115- {7, 7},107+ type centerVal struct {108+ x, y, v int109+ }110+ centers := []centerVal{111+ {1, 1, 1},112+ {4, 1, 2},113+ {7, 1, 3},114+ {1, 4, 4},115+ {4, 4, 5},116+ {7, 4, 6},117+ {1, 7, 7},118+ {4, 7, 8},119+ {7, 7, 9},116120 }117121 for _, c := range centers {118- g[c[1]][c[0]].v = 1122+ g[c.y][c.x].v = c.v119123 }120- // Each center should be valid (1 appears once per box, once per row, once per column).124+ // Each center should be valid (unique per box, row, and column).121125 for _, c := range centers {122- if !isValid(&g, 1, c[0], c[1]) {123- t.Errorf("expected valid at center (%d,%d)", c[0], c[1])126+ if !isValid(&g, c.v, c.x, c.y) {127+ t.Errorf("expected valid at center (%d,%d) with value %d", c.x, c.y, c.v)124128 }125129 }126130 })@@ -203,15 +207,27 @@ func TestFillGrid(t *testing.T) {
203207 204208 // --- countSolutions (P0) ---205209 210+// almostCompleteGrid returns a valid grid with four empty cells forming a211+// swap rectangle, giving exactly 2 solutions. The removed cells are (3,0),212+// (4,0), (3,3), (4,3) whose original values (6,7,7,6) can be swapped.213+func almostCompleteGrid() grid {214+ g := validCompleteGrid()215+ g[0][3].v = 0216+ g[0][4].v = 0217+ g[3][3].v = 0218+ g[3][4].v = 0219+ return g220+}221+206222 func TestCountSolutions(t *testing.T) {207- t.Run("complete grid", func(t *testing.T) {223+ t.Run("complete grid has 1 solution", func(t *testing.T) {208224 g := validCompleteGrid()209225 if count := countSolutions(&g, 10); count != 1 {210226 t.Errorf("countSolutions = %d, want 1", count)211227 }212228 })213229 214- t.Run("one empty cell unique", func(t *testing.T) {230+ t.Run("one empty cell has 1 solution", func(t *testing.T) {215231 g := validCompleteGrid()216232 g[0][0].v = 0217233 if count := countSolutions(&g, 10); count != 1 {@@ -219,9 +235,8 @@ func TestCountSolutions(t *testing.T) {
219235 }220236 })221237 222- t.Run("impossible grid", func(t *testing.T) {238+ t.Run("impossible grid has 0 solutions", func(t *testing.T) {223239 g := emptyGrid()224- // Place two 1s in row 0 by forcing the grid state.225240 g[0][0].v = 1226241 g[0][1].v = 1227242 if count := countSolutions(&g, 10); count != 0 {@@ -230,39 +245,19 @@ func TestCountSolutions(t *testing.T) {
230245 })231246 232247 t.Run("respects limit", func(t *testing.T) {233- g := emptyGrid()248+ g := almostCompleteGrid()249+ // This grid has more than 2 solutions.234250 count := countSolutions(&g, 2)235- if count < 2 {236- t.Errorf("countSolutions = %d, want >= 2", count)237- }238- if count > 2 {239- t.Errorf("countSolutions = %d, should not exceed limit 2", count)251+ if count != 2 {252+ t.Errorf("countSolutions = %d, want 2 (limit)", count)240253 }241254 })242255 243- t.Run("two empty cells ambiguous", func(t *testing.T) {244- // Start with a valid complete grid, then clear two cells that create245- // ambiguity: (0,0) and (0,1) where swapping their values still yields246- // a valid grid.247- g := validCompleteGrid()248- // g[0][0]=5, g[0][1]=3. We need to check if swapping creates a valid grid.249- // Clear both and count solutions.250- v1, v2 := g[0][0].v, g[0][1].v251- g[0][0].v = 0252- g[0][1].v = 0256+ t.Run("ambiguous grid has multiple solutions", func(t *testing.T) {257+ g := almostCompleteGrid()253258 count := countSolutions(&g, 10)254- // If swapping is valid, count >= 2; otherwise find a different pair.255- // The valid grid has (0,0)=5,(0,1)=3 — swapping: is 3 valid at (0,0)256- // and 5 valid at (0,1)?257- g[0][0].v = v2258- g[0][1].v = v1259- swapValid := isValid(&g, v2, 0, 0) && isValid(&g, v1, 1, 0)260- if swapValid && count < 2 {261- t.Errorf("countSolutions = %d, expected >= 2 for ambiguous pair", count)262- }263- if !swapValid && count != 1 {264- // The pair wasn't actually ambiguous; just verify we got 1.265- t.Logf("pair (0,0)/(0,1) not ambiguous, count=%d", count)259+ if count <= 1 {260+ t.Errorf("countSolutions = %d, want > 1 for ambiguous grid", count)266261 }267262 })268263 }