Initial check-in
This commit is contained in:
commit
ea695a3055
5 changed files with 403 additions and 0 deletions
120
grid.go
Normal file
120
grid.go
Normal file
|
@ -0,0 +1,120 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Grid [81]int
|
||||
|
||||
func (g Grid) ElementAt(row, col int) int {
|
||||
return g[col+row*9]
|
||||
}
|
||||
|
||||
func (g Grid) WithElementAt(row, col int, val int) Grid {
|
||||
g[col+row*9] = val
|
||||
return g
|
||||
}
|
||||
|
||||
func (g Grid) String() string {
|
||||
var sb strings.Builder
|
||||
for r := 0; r < 9; r++ {
|
||||
fmt.Fprintf(&sb, "%d %d %d | %d %d %d | %d %d %d\n",
|
||||
g.ElementAt(r, 0), g.ElementAt(r, 1), g.ElementAt(r, 2),
|
||||
g.ElementAt(r, 3), g.ElementAt(r, 4), g.ElementAt(r, 5),
|
||||
g.ElementAt(r, 6), g.ElementAt(r, 7), g.ElementAt(r, 8))
|
||||
if r == 2 || r == 5 {
|
||||
sb.WriteString("------+-------+------\n")
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (g Grid) Neighbours(row, col int) IntSet {
|
||||
neighbours := IntSet{}
|
||||
|
||||
// Neighbouring row
|
||||
for c := 0; c < 9; c++ {
|
||||
if v := g.ElementAt(row, c); v != 0 {
|
||||
neighbours.Insert(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Neighbouring col
|
||||
for r := 0; r < 9; r++ {
|
||||
if v := g.ElementAt(r, col); v != 0 {
|
||||
neighbours.Insert(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Top-left corner of the 3x3 subgrid
|
||||
tlRow := (row / 3) * 3
|
||||
tlCol := (col / 3) * 3
|
||||
|
||||
// Neighbouring subgrid
|
||||
for r := tlRow; r < tlRow+3; r++ {
|
||||
for c := tlCol; c < tlCol+3; c++ {
|
||||
if v := g.ElementAt(r, c); v != 0 {
|
||||
neighbours.Insert(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return neighbours
|
||||
}
|
||||
|
||||
func (g Grid) FirstEmptyCell() int {
|
||||
for i, v := range g {
|
||||
if v == 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (g Grid) Candidates(row, col int) []int {
|
||||
neighbours := g.Neighbours(row, col)
|
||||
var candidates []int
|
||||
for _, v := range []int{1, 2, 3, 4, 5, 6, 7, 8, 9} {
|
||||
if !neighbours.Contains(v) {
|
||||
candidates = append(candidates, v)
|
||||
}
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
|
||||
func (g Grid) Solve() (Grid, error) {
|
||||
i := g.FirstEmptyCell()
|
||||
if i == -1 {
|
||||
// No unfilled cells: we have found a solution!
|
||||
return g, nil
|
||||
}
|
||||
row := i / 9
|
||||
col := i % 9
|
||||
candidates := g.Candidates(row, col)
|
||||
// Try each of the candidates in turn
|
||||
for _, v := range candidates {
|
||||
result, err := g.WithElementAt(row, col, v).Solve()
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
// There were no valid candidates
|
||||
return g, fmt.Errorf("No solutions found")
|
||||
}
|
||||
|
||||
func ParseGrid(data []byte) (Grid, error) {
|
||||
G := Grid{}
|
||||
i := 0
|
||||
for _, x := range data {
|
||||
x := int(x) - int('0')
|
||||
if x >= 0 && x <= 9 {
|
||||
G[i] = x
|
||||
i++
|
||||
}
|
||||
}
|
||||
if i < len(G) {
|
||||
return G, fmt.Errorf("Invalid input: found %d elements, expected %d", i, len(G))
|
||||
}
|
||||
return G, nil
|
||||
}
|
190
grid_test.go
Normal file
190
grid_test.go
Normal file
|
@ -0,0 +1,190 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestElementAt(t *testing.T) {
|
||||
G := Grid{} // an empty grid
|
||||
for row := 0; row < 9; row++ {
|
||||
for col := 0; col < 9; col++ {
|
||||
v := G.ElementAt(row, col)
|
||||
if v != 0 {
|
||||
t.Errorf("ElementAt(%d, %d) = %d, expected 0", row, col, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestElementAtPanic(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("ElementAt did not panic when out of bounds")
|
||||
}
|
||||
}()
|
||||
G := Grid{}
|
||||
G.ElementAt(10, 10)
|
||||
}
|
||||
|
||||
func TestWithElementAt(t *testing.T) {
|
||||
G := Grid{}
|
||||
for i := 0; i < 10; i++ {
|
||||
row := rand.Intn(9)
|
||||
col := rand.Intn(9)
|
||||
val := rand.Intn(10)
|
||||
G = G.WithElementAt(row, col, val)
|
||||
if G.ElementAt(row, col) != val {
|
||||
t.Errorf("WithElementAt(%d, %d, %d) did not return expected value", row, col, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseGrid(t *testing.T) {
|
||||
data := []byte(`010020706
|
||||
700913040
|
||||
380004001
|
||||
000007010
|
||||
500109003
|
||||
090500000
|
||||
200300094
|
||||
040762005
|
||||
105090070`)
|
||||
G, err := ParseGrid(data)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error parsing grid: %v", err)
|
||||
}
|
||||
// Check that row 2 is as expected
|
||||
row := 2
|
||||
expected := []int{3, 8, 0, 0, 0, 4, 0, 0, 1}
|
||||
for col, want := range expected {
|
||||
got := G.ElementAt(row, col)
|
||||
if got != want {
|
||||
t.Errorf("ElementAt(%d,%d) = %d, expected %d", row, col, got, want)
|
||||
}
|
||||
}
|
||||
fmt.Println(G)
|
||||
}
|
||||
|
||||
func TestParseGridError(t *testing.T) {
|
||||
data := []byte(`01002070
|
||||
700913040
|
||||
380004001
|
||||
000007010
|
||||
500109003
|
||||
090500000
|
||||
200300094
|
||||
040762005
|
||||
105090070`)
|
||||
_, err := ParseGrid(data)
|
||||
if err == nil {
|
||||
t.Errorf("ParseGrid() did not return an expected error")
|
||||
}
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
data []byte
|
||||
grid Grid
|
||||
firstEmptyCell int
|
||||
neighbours []int
|
||||
candidates []int
|
||||
}{
|
||||
{
|
||||
[]byte("010020706700913040380004001000007010500109003090500000200300094040762005105090070"),
|
||||
Grid{},
|
||||
0,
|
||||
[]int{1, 2, 3, 5, 6, 7, 8},
|
||||
[]int{4, 9},
|
||||
},
|
||||
{
|
||||
[]byte("000000800785090006040700002068300051400000007570009320900002060800060719006000000"),
|
||||
Grid{},
|
||||
0,
|
||||
[]int{4, 5, 7, 8, 9},
|
||||
[]int{1, 2, 3, 6},
|
||||
},
|
||||
{
|
||||
[]byte("320000809400600027005000040000401000002000900000502000080000100640003008107000032"),
|
||||
Grid{},
|
||||
2,
|
||||
[]int{2, 3, 4, 5, 7, 8, 9},
|
||||
[]int{1, 6},
|
||||
},
|
||||
{
|
||||
[]byte("000008000040100390000900561006094000003000200000320800372001000095003020000200000"),
|
||||
Grid{},
|
||||
0,
|
||||
[]int{3, 4, 8},
|
||||
[]int{1, 2, 5, 6, 7, 9},
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
for i := 0; i < len(tests); i++ {
|
||||
grid, err := ParseGrid(tests[i].data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tests[i].grid = grid
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirstEmptyCell(t *testing.T) {
|
||||
for i, x := range tests {
|
||||
got := tests[i].grid.FirstEmptyCell()
|
||||
if got != x.firstEmptyCell {
|
||||
t.Errorf("FirstEmptyCell(grid[%d]) = %d, expected %d", i, got, x.firstEmptyCell)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNeighbours(t *testing.T) {
|
||||
for i, x := range tests {
|
||||
r := x.firstEmptyCell / 9
|
||||
c := x.firstEmptyCell % 9
|
||||
got := x.grid.Neighbours(r, c)
|
||||
if len(got) != len(x.neighbours) {
|
||||
t.Errorf("len(Neighbours(grid[%d]) = %d, expected %d", i, len(got), len(x.neighbours))
|
||||
}
|
||||
for _, v := range x.neighbours {
|
||||
if !got.Contains(v) {
|
||||
t.Errorf("Neighbours(grid[%d]) did not contain %d", i, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandidates(t *testing.T) {
|
||||
for i, x := range tests {
|
||||
r := x.firstEmptyCell / 9
|
||||
c := x.firstEmptyCell % 9
|
||||
got := x.grid.Candidates(r, c)
|
||||
if len(got) != len(x.candidates) {
|
||||
t.Errorf("len(Candidates(grid[%d]) = %d, expected %d", i, len(got), len(x.candidates))
|
||||
}
|
||||
for _, v := range x.candidates {
|
||||
found := false
|
||||
for _, w := range got {
|
||||
if v == w {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Candidates(grid[%d]) did not contain %d", i, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSolve(t *testing.T) {
|
||||
for i, x := range tests {
|
||||
res, err := x.grid.Solve()
|
||||
if err != nil {
|
||||
t.Errorf("Could not solve grid %d", i)
|
||||
} else {
|
||||
fmt.Println(res)
|
||||
}
|
||||
}
|
||||
}
|
21
intset.go
Normal file
21
intset.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package main
|
||||
|
||||
type IntSet []int
|
||||
|
||||
// Contains returns true if x is in the set, otherwise false
|
||||
func (s *IntSet) Contains(x int) bool {
|
||||
for _, v := range *s {
|
||||
if v == x {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Insert adds x to the set if it is not already present
|
||||
func (s *IntSet) Insert(x int) {
|
||||
if s.Contains(x) {
|
||||
return
|
||||
}
|
||||
*s = append(*s, x)
|
||||
}
|
41
intset_test.go
Normal file
41
intset_test.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestContains(t *testing.T) {
|
||||
s := IntSet{}
|
||||
if s.Contains(0) {
|
||||
t.Errorf("The empty set should not contain 0")
|
||||
}
|
||||
xs := []int{1, 2, 3}
|
||||
s = IntSet(xs)
|
||||
for _, x := range xs {
|
||||
if !s.Contains(x) {
|
||||
t.Errorf("Expected set to contain %d", x)
|
||||
}
|
||||
}
|
||||
if s.Contains(0) {
|
||||
t.Errorf("The set {1,2,3} should not contain 0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsert(t *testing.T) {
|
||||
s := IntSet{}
|
||||
xs := []int{1, 2, 3}
|
||||
for _, x := range xs {
|
||||
if s.Contains(x) {
|
||||
t.Errorf("%d has not yet been added to the set", x)
|
||||
}
|
||||
s.Insert(x)
|
||||
if !s.Contains(x) {
|
||||
t.Errorf("Expected the set to contain %d", x)
|
||||
}
|
||||
}
|
||||
if len(s) != len(xs) {
|
||||
t.Errorf("The set should contain %d elements", len(xs))
|
||||
}
|
||||
s.Insert(1)
|
||||
if len(s) != len(xs) {
|
||||
t.Errorf("Inserting an element twice should not affect size of the set")
|
||||
}
|
||||
}
|
31
sudoku.go
Normal file
31
sudoku.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Fprintln(os.Stderr, "Usage: sudoku FILENAME")
|
||||
os.Exit(2)
|
||||
}
|
||||
filename := os.Args[1]
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading puzzle file: %v\n", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
grid, err := ParseGrid(data)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing grid: %v\n", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
solution, err := grid.Solve()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(3)
|
||||
}
|
||||
fmt.Println(solution)
|
||||
}
|
Loading…
Reference in a new issue