Listing of doc/example-scripts/rosetta-sudoku.tcl
Rosetta Example: Sudoku
Solve a partially filled-in 9x9 Sudoku grid and display the result in a human-readable format. For detailed description of this example, see https://rosettacode.org/wiki/Sudoku_Solver
This implementation is based on https://wiki.tcl-lang.org/19934
package require nx
The class Sudoku
implements the basic interface to a sudoku 9x9
board to load/dump data and to set/access cells, rows, columns and
regions.
nx::Class create Sudoku { :variable board # Setup an array from 0..9 to ease iterations over the cells of # lines and columns. for {set i 0} {$i < 9} {incr i} {lappend positions $i} :variable positions $positions :public method load {data} { # # Load a 9x9 partially solved sudoku. The unsolved cells are # represented by a@ symbols. # set error "data must be a 9-element list, each element also being a\ list of 9 numbers from 1 to 9 or blank or an @ symbol." if {[llength $data] != 9} { error $error } foreach y ${:positions} { set row [lindex $data $y] if {[llength $row] != 9} { error $error } foreach x ${:positions} { set cell [lindex $row $x] if {![regexp {^[@1-9]?$} $cell]} { error $cell-$error } if {$cell eq "@"} {set cell ""} :set $x $y $cell } } } :public method dump {-pretty-print:switch} { # # Output the current state of the sudoku either as list or in # a pretty-print style. # set rows [lmap y ${:positions} {:getRow 0 $y}] if {${pretty-print}} { set result +-----+-----+-----+\n foreach line $rows postline {0 0 1 0 0 1 0 0 1} { append result |[lrange $line 0 2]|[lrange $line 3 5]|[lrange $line 6 8]|\n if {$postline} { append result +-----+-----+-----+\n } } return $result } else { return $rows } } :method log {msg} { #puts "log: $msg" } :method set {x y value:integer,0..1} { # # Set cell at position x,y to the given value or empty. # if {$value<1 || $value>9} { set :board($x,$y) {} } else { set :board($x,$y) $value } } :method get {x y} { # # Get value of cell at position x, y. # return [set :board($x,$y)] } :method getRow {x y} { # # Return a row at constant position y. # return [lmap x ${:positions} {:get $x $y}] } :method getCol {x y} { # # Return a column at constant position x. # return [lmap y ${:positions} {:get $x $y}] } :method getRegion {x y} { # # Return a 3x3 region # set xR [expr {($x/3)*3}] set yR [expr {($y/3)*3}] set regn {} for {set x $xR} {$x < $xR+3} {incr x} { for {set y $yR} {$y < $yR+3} {incr y} { lappend regn [:get $x $y] } } return $regn } }
The class SudokuSolver
inherits from Sudoku
, and adds the
ability to solve a given Sudoku game. The method solve applies all
rules for each unsolved cell until it finds a safe solution.
nx::Class create SudokuSolver -superclass Sudoku { :public method validchoices {x y} { set v [:get $x $y] if {$v ne {}} { return $v } set row [:getRow $x $y] set col [:getCol $x $y] set regn [:getRegion $x $y] set eliminate [list {*}$row {*}$col {*}$regn] set eliminate [lsearch -all -inline -not $eliminate {}] set eliminate [lsort -unique $eliminate] set choices {} for {set c 1} {$c < 10} {incr c} { if {$c ni $eliminate} { lappend choices $c } } if {[llength $choices]==0} { error "No choices left for square $x,$y" } return $choices } :method completion {} { # # Return the number of already solved items. # return [expr {81-[llength [lsearch -all -inline [join [:dump]] {}]]}] } :public method solve {} { # # Try to solve the sudoku by applying the provided rules. # while {1} { set begin [:completion] foreach y ${:positions} { foreach x ${:positions} { if {[:get $x $y] eq ""} { foreach rule [Rule info instances] { set c [$rule solve [self] $x $y] if {$c} { :set $x $y $c :log "[$rule info class] solved [self] at $x,$y for $c" break } } } } } set end [:completion] if {$end == 81} { :log "Finished solving!" break } elseif {$begin == $end} { :log "A round finished without solving any squares, giving up." break } } } }
The class rule provides "solve" as public interface for all rule objects. The rule objects apply their logic to the values passed in and return either 0 or a number to allocate to the requested square.
nx::Class create Rule { :public method solve {hSudoku:object,type=::SudokuSolver x y} { :Solve $hSudoku $x $y [$hSudoku validchoices $x $y] } # Get all the allocated numbers for each square in the row, column, and # region containing $x,$y. If there is only one unallocated number among all # three groups, it must be allocated at $x,$y :create ruleOnlyChoice { :object method Solve {hSudoku x y choices} { if {[llength $choices] == 1} { return $choices } else { return 0 } } } # Test each column to determine if $choice is an invalid choice for all other # columns in row $X. If it is, it must only go in square $x,$y. :create RuleColumnChoice { :object method Solve {hSudoku x y choices} { foreach choice $choices { set failed 0 for {set x2 0} {$x2 < 9} {incr x2} { if {$x2 != $x && $choice in [$hSudoku validchoices $x2 $y]} { set failed 1 break } } if {!$failed} {return $choice} } return 0 } } # Test each row to determine if $choice is an invalid choice for all other # rows in column $y. If it is, it must only go in square $x,$y. :create RuleRowChoice { :object method Solve {hSudoku x y choices} { foreach choice $choices { set failed 0 for {set y2 0} {$y2 < 9} {incr y2} { if {$y2 != $y && $choice in [$hSudoku validchoices $x $y2]} { set failed 1 break } } if {!$failed} {return $choice} } return 0 } } # Test each square in the region occupied by $x,$y to determine if $choice is # an invalid choice for all other squares in that region. If it is, it must # only go in square $x,$y. :create RuleRegionChoice { :object method Solve {hSudoku x y choices} { foreach choice $choices { set failed 0 set regnX [expr {($x/3)*3}] set regnY [expr {($y/3)*3}] for {set y2 $regnY} {$y2 < $regnY+3} {incr y2} { for {set x2 $regnX} {$x2 < $regnX+3} {incr x2} { if { ($x2!=$x || $y2!=$y) && $choice in [$hSudoku validchoices $x2 $y2] } then { set failed 1 break } } } if {!$failed} {return $choice} } return 0 } } } SudokuSolver create sudoku { :load { {3 9 4 @ @ 2 6 7 @} {@ @ @ 3 @ @ 4 @ @} {5 @ @ 6 9 @ @ 2 @} {@ 4 5 @ @ @ 9 @ @} {6 @ @ @ @ @ @ @ 7} {@ @ 7 @ @ @ 5 8 @} {@ 1 @ @ 6 7 @ @ 8} {@ @ 9 @ @ 8 @ @ @} {@ 2 6 4 @ @ 7 3 5} } :solve puts [:dump -pretty-print] }
The dump method outputs the solved Sudoku:
+-----+-----+-----+
|3 9 4|8 5 2|6 7 1|
|2 6 8|3 7 1|4 5 9|
|5 7 1|6 9 4|8 2 3|
+-----+-----+-----+
|1 4 5|7 8 3|9 6 2|
|6 8 2|9 4 5|3 1 7|
|9 3 7|1 2 6|5 8 4|
+-----+-----+-----+
|4 1 3|5 6 7|2 9 8|
|7 5 9|2 3 8|1 4 6|
|8 2 6|4 1 9|7 3 5|
+-----+-----+-----+