Byron Weber Becker
This is a backtracking solver for the Sudoku puzzle. It was built to illustrate the concept of backtracking in implicit trees for CS135 at the University of Waterloo. The class studies a find-path algorithm in an explicit graph; backtracking in an implicit graph is only mentioned briefly. The space being explored by the solver is shown on the board with the ability to step forwards or backwards, play forwards rapidly, or “scrub” to an approximate position.
This solver is implemented in Scala.js. CS135 uses Racket. Nevertheless, there are many similarities in the code:
solve
parallels find-path
while solveList
parallels find-path/list
.
neighbours
function. In find-path
it simply looks up the neighbours in the explicit graph. In solve
it
needs to calculate the neighbours based on the current state of the board.
false
to flag when a route cannot be found.
The Scala code uses an Option
for the same purpose.
find-path
has a very simple termination test: is the origin the same
as the destination? solve
needs to look for an unsolved cell, which is
delegated to a helper function. If it returns None
, we know a solution
has been found.
Scala find-path | Racket find-path |
---|---|
object FindPath { def main(args: Array[String]) = { println(findPath('A, 'H, g)) } type Node = Symbol type Graph = List[(Node, List[Node])] type Route = List[Node] private val g: Graph = List( ('A, List('C, 'D, 'E)), ('B, List('E, 'J)), ('C, List()), ('D, List('F, 'J)), ('E, List('K)), ('F, List('K, 'H)), ('H, List()), ('J, List('H)), ('K, List()) ) private def neighbours(n: Node, g: Graph): List[Node] = { g.filter(adjList ⇒ adjList._1 == n).head._2 } private def findPathList(nbrs: List[Node], dest: Node, g: Graph): Option[Route] = { nbrs match { case Nil ⇒ None case first :: rest ⇒ findPath(first, dest, g) .orElse(findPathList(rest, dest, g)) } } private def findPath(orig: Node, dest: Node, g: Graph): Option[Route] = { (orig == dest) match { case true ⇒ Some(List(orig)) case false ⇒ findPathList(neighbours(orig, g), dest, g) .map(route ⇒ orig :: route) } } } |
(define graph '((A (C D E)) (B (E J)) (C ()) (D (F J)) (E (K)) (F (K H)) (H ()) (J (H)) (K ()))) ;; neighbours: Node Graph --> (listof Node) ;; requires: v is a node in g (define (neighbours v g) (cond [(empty? g) (error "Node not found")] [(symbol=? v (first (first g))) (second (first g))] [else (neighbours v (rest g))])) ;; (find-path orig dest g) finds path from orig to dest in g if it exists ;; find-path: Node Node Graph --> (anyof (listof Node) false) (define (find-path orig dest g) (cond [(symbol=? orig dest) (list dest)] [else (local [(define nbrs (neighbours orig g)) (define ?path (find-path/list nbrs dest g))] (cond [(false? ?path) false] [else (cons orig ?path)]))])) ;; (find-path/list nbrs dest g) produces path from ;; an element of nbrs to dest in g, if one exists ;; find-path/list: (listof Node) Node Graph --> (anyof (listof Node) false) (define (find-path/list nbrs dest g) (cond [(empty? nbrs) false] [else (local [(define ?path (find-path (first nbrs) dest g))] (cond [(false? ?path) (find-path/list (rest nbrs) dest g)] [else ?path]))])) |
Scala Sudoku Solver | Scala Sudoku Helpers |
type Board = Array[Array[Int]] val bd: Board = Array( Array(0, 6, 0, 3, 0, 7, 8, 0, 0), Array(0, 0, 0, 0, 9, 0, 0, 0, 0), Array(7, 1, 0, 0, 0, 0, 4, 0, 0), Array(0, 5, 0, 8, 0, 9, 2, 0, 4), Array(0, 0, 0, 0, 0, 0, 0, 0, 0), Array(2, 0, 3, 5, 0, 1, 0, 6, 0), Array(0, 0, 9, 0, 0, 0, 0, 8, 1), Array(0, 0, 0, 0, 1, 0, 0, 0, 0), Array(0, 0, 7, 2, 0, 3, 0, 4, 0) ) /** Find a "route" from the starting board to a complete board. */ def solve(bd: Board): Option[Board] = { firstUnsolved(bd) match { case None ⇒ Some(bd) case Some(pos) ⇒ solveList(neighbours(bd)) } } /** Find a "route" from one of a list of incomplete boards to a finished board. */ def solveList(bds: List[Board]): Option[Board] = { bds match { case Nil ⇒ None case bd :: others ⇒ solve(bd).orElse(solveList(others)) } } |
/** Is it safe or valid to place an i at bd(row, col)? */ def safe(bd: Board, row: Int, col: Int, i: Int): Boolean = { // Find the upper-left corner of the block in which (row,col) appears val startRow = row / 3 * 3 val startCol = col / 3 * 3 /** * Step through the 0..8 cells in the 3x3 block. Do some * fancy stuff with j to get the row and column offset from * the upper-left corner of the block. * Return true if it's safe to put and i at bd(row)(col). */ def safeBlock(j: Int): Boolean = { val r = j / 3 val c = j % 3 // This location is OK and either we've reached the bottom right // corner of the block or the rest of the block is safe. bd(startRow + r)(startCol + c) != i && (j == 8 || safeBlock(j + 1)) } (bd(row).count(p ⇒ p == i) == 0) && (bd.count(arr ⇒ arr(col) == i) == 0) && safeBlock(0) } /** Find the first unsolved (0) cell in the board. None if they're all solved. */ def firstUnsolved(bd: Board): Option[Pos] = { for (r ← 0 until bd.length) for (c ← 0 until bd(r).length) if (bd(r)(c) == 0) return Some(Pos(r, c)) None } /** * Find the first unsolved position. Return a list of new * boards (the neighbours of bd) with the unsolved position * filled with valid candidate integers. */ def neighbours(bd: Board): List[Board] = { firstUnsolved(bd) match { case None ⇒ List() case Some(pos) ⇒ (for { i ← 1 to 9 if safe(bd, pos.row, pos.col, i) } yield { newBoard(bd, pos.row, pos.col, i) }).toList } } |