File: rosetta-sudoku.tcl

package info (click to toggle)
nsf 2.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye
  • size: 12,628 kB
  • sloc: ansic: 32,245; tcl: 10,636; sh: 664; pascal: 176; lisp: 41; makefile: 24
file content (312 lines) | stat: -rw-r--r-- 7,438 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
#
# == 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|
#  +-----+-----+-----+