Artur Trzewik: Programming a sudoku solver is a good brain training for an evening.
Perhaps it will in time replace Eight Queens Problem as a popular student homework assignment.
Here is a short implementation. It has some extra functionality to observe the solution progress. It will not solve problems which need several tries.
namespace eval sudoku { variable win } proc sudoku::lremoveAll {list_ref listr} { upvar $list_ref list foreach elem $listr { lremove list $elem } } proc sudoku::lremove {list_ref elem} { upvar $list_ref list if {[set index [lsearch -exact $list $elem]]>=0} { set list [lreplace $list $index $index] return 1 } return 0 } proc sudoku::clear {} { set f [list] for {set i 0} {$i<9*9} {incr i} { lappend f [list] } setWin $f } proc sudoku::complexReduction {ref_f t} { variable win upvar $ref_f f set fields {1 2 3 4 5 6 7 8 9} set found 0 for {set y 0} {$y<9} {incr y} { for {set x 0} {$x<9} {incr x} { if {[llength [set pos [lindex $t [expr {$x+$y*9}]]]]>1} { set i 0 set allposibles [list] foreach p [getColumn $t $x] { if {$i==$y} { incr i continue } set allposibles [concat $allposibles $p] incr i } lremoveAll pos $allposibles if {[llength $pos]==1} { lset f [expr {$x+$y*9}] [lindex $pos 0] lset t [expr {$x+$y*9}] [lindex $pos 0] set found 1 return $found } set pos [lindex $t [expr {$x+$y*9}]] set i 0 set allposibles [list] foreach p [getRow $t $y] { if {$i==$x} { incr i continue } set allposibles [concat $allposibles $p] incr i } lremoveAll pos $allposibles if {[llength $pos]==1} { lset f [expr {$x+$y*9}] [lindex $pos 0] lset t [expr {$x+$y*9}] [lindex $pos 0] set found 1 return $found } } } } return $found } proc sudoku::compute {} { set f [getNumbers] while 1 { set r1 [simpleReduction f] set r2 [complexReduction f [simpleReduction f 0]] if {$r1==0 && $r2==0} { break } } setWin $f } proc sudoku::getColumn {f column} { set l [list] for {set i 0} {$i<9} {incr i} { lappend l [lindex $f [expr {$i*9+$column}]] } return $l } proc sudoku::getNumbers {} { variable win set f [list] for {set i 0} {$i<9*9} {incr i} { lappend f [list] } for {set x 0} {$x<9} {incr x} { for {set y 0} {$y<9} {incr y} { lset f [expr {$x+$y*9}] [$win.e${x}_$y get] } } return $f } proc sudoku::getQuad {f quad} { set diff [expr {($quad%3)*3+($quad/3)*27}] list [lindex $f $diff] [lindex $f [expr {$diff+1}]] [lindex $f [expr {$diff+2}]] [lindex $f [expr {$diff+9}]] [lindex $f [expr {$diff+10}]] [lindex $f [expr {$diff+11}]] [lindex $f [expr {$diff+18}]] [lindex $f [expr {$diff+19}]] [lindex $f [expr {$diff+20}]] } proc sudoku::getRow {f row} { lrange $f [expr {$row*9}] [expr {$row*9+8}] } proc sudoku::load {} { set f [tk_getOpenFile -filetypes {{{SuDoKu Files} *.sdk}}] if {$f eq ""} { return } set file [open $f r] set num [read $file] close $file setWin $num } proc sudoku::lstep {} { set f [getNumbers] set t [simpleReduction f 0] complexReduction f $t setWin $f } proc sudoku::myInit {} { setWin { {} {} {} {} 6 {} {} 3 {} {} {} 5 3 {} {} {} {} {} 8 {} {} {} {} 5 {} 4 7 {} {} {} 1 5 {} {} {} {} {} 1 {} {} {} {} {} 9 {} {} 5 {} {} {} 4 3 {} {} {} {} 4 6 8 {} {} 2 3 2 {} 1 {} 4 {} {} {} 8 {} 9 {} {} 7 2 1 6 {} } } proc sudoku::setMessage mes { variable win $win.lab configure -text $mes } proc sudoku::setWin f { variable win for {set x 0} {$x<9} {incr x} { for {set y 0} {$y<9} {incr y} { $win.e${x}_$y delete 0 end $win.e${x}_$y insert 0 [lindex $f [expr {$x+$y*9}]] } } } proc sudoku::simpleReduction {ref_f {reduction 1}} { upvar $ref_f f set fields {1 2 3 4 5 6 7 8 9} set t [list] for {set y 0} {$y<9} {incr y} { for {set x 0} {$x<9} {incr x} { if {[lindex $f [expr {$x+$y*9}]] eq ""} { set pos $fields lremoveAll pos [getRow $f $y] lremoveAll pos [getColumn $f $x] lremoveAll pos [getQuad $f [expr {$x/3+($y/3)*3}]] lappend t $pos if {[llength $pos]==1 && $reduction} { lset f [expr {$x+$y*9}] [lindex $pos 0] return 1 } elseif {[llength $pos]==0} { setMessage "Keine L½sung ${x}:$y" } } else { lappend t [lindex $f [expr {$x+$y*9}]] } } } if {$reduction==1} { return 0 } return $t } proc sudoku::initWindow {window} { variable win set win $window frame $win.f frame $win.b label $win.help -text "Use double click to show possible numbers" -bg green if {[lsearch [font names] espfont]<0} { font create sdkfont -family Courier -size 25 } for {set y 0} {$y<9} {incr y} { for {set x 0} {$x<9} {incr x} { set qq [expr {($x / 3) + ($y / 3)}] entry $win.e${x}_$y -width 2 -font sdkfont if {[expr {$qq % 2}]==1} { $win.e${x}_$y config -bg grey } bind $win.e${x}_$y <Double-1> [list sudoku::testPos $x $y] grid $win.e${x}_$y -in $win.f -column $x -row $y -padx 2 -pady 2 } } button $win.compute -text "Solve" -command sudoku::compute button $win.step -text "Strategy 1" -command sudoku::step button $win.lstep -text "Strategy 2" -command sudoku::lstep #label $win.lab -relief raised -border 3 label $win.lab -relief sunken -border 3 #pack $win.help -fill x pack $win.f -fill both -expand yes -padx 10 -pady 10 pack $win.compute $win.step $win.lstep -side left -in $win.b #pack $win.b $win.lab -fill x pack $win.b -fill x pack $win.help -fill x pack $win.lab -fill x menu $win.m $win.m add command -label "Clean" -command sudoku::clear $win.m add command -label "Set example" -command sudoku::myInit $win.m add separator $win.m add command -label "Load" -command sudoku::load $win.m add command -label "Save" -command sudoku::save if {$win eq ""} { . configure -menu $win.m } else { $win configure -menu $win.m } } proc sudoku::save {} { set f [tk_getSaveFile -initialfile my.sdk -filetypes {{{SuDoKu Files} *.sdk}}] if {$f eq ""} { return } set file [open $f w] puts $file [getNumbers] close $file } proc sudoku::step {} { set f [getNumbers] set t [simpleReduction f] setWin $f } proc sudoku::testPos {x y} { set f [getNumbers] set t [simpleReduction f 0] set pos [lindex $t [expr {$y*9+$x}]] setMessage "${x}:$y = $pos" } proc sudoku::try f { for {set i 0} {$i<9} {incr i} { if {[llength [lsort -unique [getColumn $f $i]]]!=9} { return 0 } if {[llength [lsort -unique [getRow $f $i]]]!=9} { return 0 } if {[llength [lsort -unique [getQuad $f $i]]]!=9} { return 0 } } return 1 } sudoku::initWindow {} sudoku::myInit
HJG Added grey background for every other block, moved help-line directly above output-label.
See also: Sudoku - Playing sudoku - sudokut - sudoku minimalistic.