File Coverage

blib/lib/Games/Sudoku/Solver.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             #
2             #===============================================================================
3             #
4             # FILE: Solver.pm
5             #
6             # DESCRIPTION: Solve 9x9-Sudokus recursively.
7             #
8             # FILES: ---
9             # BUGS: ---
10             # NOTES: ---
11             # AUTHOR: Dr.-Ing. Fritz Mehner (Mn),
12             # COMPANY: Fachhochschule Suedwestfalen, Iserlohn
13             # VERSION: see $VERSION below
14             # CREATED: 04.05.2006
15             # REVISION: $Id: Solver.pm,v 1.5 2007/12/14 16:44:06 mehner Exp $
16             #===============================================================================
17              
18             package Games::Sudoku::Solver;
19              
20 2     2   44807 use strict;
  2         6  
  2         73  
21 2     2   11 use warnings;
  2         4  
  2         85  
22              
23             #===============================================================================
24             # MODULE INTERFACE
25             #===============================================================================
26             our $VERSION = '1.1.0';
27              
28 2     2   8 use Carp; # warn/die of errors
  2         7  
  2         309  
29 2     2   2085 use Clone; # recursively copy Perl datatypes
  0            
  0            
30              
31             use base qw(Exporter);
32              
33             # Symbols to be exported on request
34             our @EXPORT_OK = qw(
35             count_occupied_cells
36             get_solution_max
37             set_solution_max
38             sudoku_check
39             sudoku_print
40             sudoku_read
41             sudoku_set
42             sudoku_solve
43             );
44              
45             # Define names for sets of symbols
46             our %EXPORT_TAGS = (
47             Minimal => [ qw( sudoku_set sudoku_solve sudoku_print ) ],
48             All => [ @EXPORT_OK ],
49             );
50              
51             #===============================================================================
52             # MODULE IMPLEMENTATION
53             #===============================================================================
54             { # CLOSURE
55             my $solution_number = 0; # solution counter
56             my @col_empty; # stack of free cells (column number)
57             my @row_empty; # stack of free cells (row number)
58             my $index_last; # last index in these stacks
59             my $index_empty; # actual index in these stacks
60             my %restriction =
61             (
62             solution_max => 10, # maximal number of solutions (0=unbound)
63             diagonal_ul_lr => 0, #
64             diagonal_ll_ur => 0, #
65             );
66              
67             #=== FUNCTION ================================================================
68             # NAME: sudoku_solve
69             # PURPOSE: solve a Sudoku
70             # DESCRIPTION: solve a Sudoku by recursion
71             # PARAMETERS: (1) reference to a Sudoku (array of arrays)
72             # (2) reference to a solution array (array of arrays of arrays)
73             # (3) restrictions (optional)
74             # RETURNS: number of solutions found
75             #===============================================================================
76             sub sudoku_solve {
77             my ( $sudoku_ref, $solution_ref, %option ) = @_;
78              
79              
80             if ( %option ) {
81             _check_options( %option );
82             }
83              
84             #---------------------------------------------------------------------------
85             # initialize the stacks
86             #---------------------------------------------------------------------------
87             @row_empty = ();
88             @col_empty = ();
89             foreach my $i ( 0 .. 8 ) {
90             foreach my $j ( 0 .. 8 ) {
91             if ( $sudoku_ref->[$i][$j] == 0 ) {
92             push @row_empty, $i;
93             push @col_empty, $j;
94             }
95             }
96             }
97             $index_empty = -1;
98             $index_last = $#row_empty;
99             $solution_number = 0;
100              
101             return _sudoku_recurse( $sudoku_ref, $solution_ref );
102             } # ---------- end of subroutine sudoku_solve ----------
103              
104             #=== FUNCTION ================================================================
105             # NAME: _check_options
106             # PURPOSE: check for restrictions
107             # PARAMETERS: hash with restrictions
108             # RETURNS: ---
109             #===============================================================================
110             sub _check_options {
111             my ( %ref ) = @_;
112             while ( my ( $key, $value ) = each %ref ) {
113             $restriction{$key} = $value;
114             }
115              
116             set_solution_max( $restriction{solution_max} );
117              
118             if ( $restriction{diagonal_ul_lr} !~ m/^[01]$/xm ) {
119             $restriction{diagonal_ul_lr} = 0;
120             }
121              
122             if ( $restriction{diagonal_ll_ur} !~ m/^[01]$/xm ) {
123             $restriction{diagonal_ll_ur} = 0;
124             }
125             return ;
126             } # ---------- end of subroutine _check_options ----------
127              
128             #=== FUNCTION ================================================================
129             # NAME: _sudoku_recurse
130             # PURPOSE: organize the recursion
131             # PARAMETERS: (1) reference to a Sudoku (array of arrays)
132             # (2) reference to a solution array (array of arrays of arrays)
133             # RETURNS: number of solutions found so far
134             #===============================================================================
135             sub _sudoku_recurse {
136             my ( $sudoku_ref, $solution_ref ) = @_;
137              
138             #---------------------------------------------------------------------------
139             # check if maximal number of solutions are reached
140             #---------------------------------------------------------------------------
141             if ( $solution_number > 0 && $solution_number == $restriction{solution_max} ) {
142             return $solution_number;
143             }
144              
145             #---------------------------------------------------------------------------
146             # check for a complete solution
147             #---------------------------------------------------------------------------
148             $index_empty++; # index of next empty position
149             if ( $index_empty > $index_last ) { # Sudoku solved ?
150             push @{$solution_ref}, \@{ Clone::clone($sudoku_ref) };
151             $index_empty--; # free last position
152             return ++$solution_number;
153             }
154              
155             #---------------------------------------------------------------------------
156             # recurse over the free cells
157             #---------------------------------------------------------------------------
158             my $row = $row_empty[$index_empty];
159             my $col = $col_empty[$index_empty];
160             foreach my $i ( _find_missing_values( $sudoku_ref, $row, $col ) ) {
161             $sudoku_ref->[$row][$col] = $i; # set cell
162             _sudoku_recurse( $sudoku_ref, $solution_ref ); # recurse
163             }
164             $sudoku_ref->[$row][$col] = 0; # empty cell
165             $index_empty--; # free cell
166              
167             return $solution_number;
168             } # ---------- end of subroutine _sudoku_recurse ----------
169              
170             #=== FUNCTION ================================================================
171             # NAME: _find_missing_values
172             # PURPOSE: find possible values for a free cell
173             # PARAMETERS: (1) reference to a Sudoku (array of arrays)
174             # (2) row index of the cell
175             # (3) column index of the cell
176             # RETURNS: array with possible values
177             #===============================================================================
178             sub _find_missing_values {
179             my ( $sudoku_ref, $row, $col ) = @_;
180             my @found = ( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
181             my @not_used;
182              
183             #---------------------------------------------------------------------------
184             # check row and column
185             #---------------------------------------------------------------------------
186             foreach my $i ( 0 .. 8 ) {
187             $found[ $sudoku_ref->[$row][$i] ]++;
188             $found[ $sudoku_ref->[$i][$col] ]++;
189             }
190              
191             #---------------------------------------------------------------------------
192             # check submatrix
193             #---------------------------------------------------------------------------
194             my $smi = $row - $row % 3;
195             my $smj = $col - $col % 3;
196             foreach my $i ( $smi .. ( $smi + 2 ) ) {
197             foreach my $j ( $smj .. ( $smj + 2 ) ) {
198             $found[ $sudoku_ref->[$i][$j] ]++;
199             }
200             }
201              
202             #---------------------------------------------------------------------------
203             # RESTRICTIONS
204             # check 1. diagonal (if requested)
205             #---------------------------------------------------------------------------
206             if ( $restriction{diagonal_ul_lr} == 1 && $row == $col ) {
207             foreach my $i ( 0 .. 8 ) {
208             $found[ $sudoku_ref->[$i][$i] ]++;
209             }
210             }
211              
212             #---------------------------------------------------------------------------
213             # RESTRICTIONS
214             # check 2. diagonal (if requested)
215             #---------------------------------------------------------------------------
216             if ( $restriction{diagonal_ll_ur} == 1 && ($row + $col) == 8 ) {
217             foreach my $i ( 0 .. 8 ) {
218             $found[ $sudoku_ref->[$i][8-$i] ]++;
219             }
220             }
221              
222             #---------------------------------------------------------------------------
223             # identify the missing values
224             #---------------------------------------------------------------------------
225             foreach my $i ( 1 .. 9 ) {
226             if ( $found[$i] == 0 ) {
227             push @not_used, $i;
228             }
229             }
230              
231             return (@not_used);
232             } # ---------- end of subroutine _find_missing_values ----------
233              
234             #=== FUNCTION ================================================================
235             # NAME: set_solution_max
236             # PURPOSE: set maximal number of solutions to search for
237             # PARAMETERS: positive number (positive sign allowed)
238             # RETURNS: ---
239             #===============================================================================
240             sub set_solution_max {
241             my ( $limit ) = @_;
242             if ( $limit =~ m/^[+]?\d+$/xm && $limit > 0 ) {
243             $restriction{solution_max} = $limit;
244             }
245             return;
246             } # ---------- end of subroutine set_solution_max ----------
247              
248             #=== FUNCTION ================================================================
249             # NAME: get_solution_max
250             # PURPOSE: get maximal number of solutions to search for
251             # PARAMETERS: ---
252             # RETURNS: maximal number of solutions to search for
253             #===============================================================================
254             sub get_solution_max {
255             return $restriction{solution_max};
256             } # ---------- end of subroutine get_solution_max ----------
257              
258             } # end CLOSURE
259              
260             #=== FUNCTION ================================================================
261             # NAME: sudoku_check
262             # PURPOSE: Check Sudoku for correctness
263             # DESCRIPTION: - check rows, columns
264             # - check submatrices; numbering:
265             # +---+---+---+
266             # | 1 | 2 | 3 |
267             # +---+---+---+
268             # | 4 | 5 | 6 |
269             # +---+---+---+
270             # | 7 | 8 | 9 |
271             # +---+---+---+
272             # Die of error (croak) if Sudoku is not correct.
273             # PARAMETERS: (1) reference to a Sudoku (array of arrays)
274             # RETURNS: ---
275             #===============================================================================
276             sub sudoku_check {
277             my $sudoku_ref = shift;
278             my $key;
279              
280             #---------------------------------------------------------------------------
281             # check for doubled values in rows
282             #---------------------------------------------------------------------------
283             foreach my $i ( 0 .. 8 ) {
284             my %count;
285             foreach my $j ( 0 .. 8 ) {
286             $key = $sudoku_ref->[$i][$j];
287             $count{$key}++;
288             if ( $key > 0 && $count{$key} > 1 ) {
289             $i++;
290             croak "value repeated in line ${i} \n";
291             }
292             }
293             }
294              
295             #---------------------------------------------------------------------------
296             # check for doubled values in columns
297             #---------------------------------------------------------------------------
298             foreach my $i ( 0 .. 8 ) {
299             my %count;
300             foreach my $j ( 0 .. 8 ) {
301             $key = $sudoku_ref->[$j][$i];
302             $count{$key}++;
303             if ( $key > 0 && $count{$key} > 1 ) {
304             $i++;
305             croak "value repeated in column ${i} \n";
306             }
307             }
308             }
309              
310             #---------------------------------------------------------------------------
311             # check submatrices
312             #---------------------------------------------------------------------------
313             foreach my $ii ( 0, 3, 6 ) {
314             foreach my $jj ( 0, 3, 6 ) {
315             my %count;
316             foreach my $i ( $ii .. $ii + 2 ) {
317             foreach my $j ( $jj .. $jj + 2 ) {
318             $key = $sudoku_ref->[$j][$i];
319             $count{$key}++;
320             if ( $key > 0 && $count{$key} > 1 ) {
321             my $submat = $ii + $jj / 3 + 1;
322             croak "value repeated in submatrix $submat \n";
323             }
324             }
325             }
326             }
327             }
328             return;
329             } # ---------- end of subroutine sudoku_check ----------
330              
331             #=== FUNCTION ================================================================
332             # NAME: sudoku_print
333             # PURPOSE: print Sudoku
334             # DESCRIPTION: Simple text output
335             # PARAMETERS: (1) reference to a Sudoku (array of arrays)
336             # RETURNS: ---
337             #===============================================================================
338             sub sudoku_print {
339             my $sudoku_ref = shift;
340             foreach my $i ( 0 .. 8 ) {
341             print " @{$sudoku_ref->[$i]}\n";
342             }
343             return;
344             } # ---------- end of subroutine sudoku_print ----------
345              
346             #=== FUNCTION ================================================================
347             # NAME: sudoku_read
348             # PURPOSE: read a Sudoku from a file; check format
349             # PARAMETERS: (1) reference to a Sudoku (array of arrays)
350             # (2) name of the input file (scalar)
351             # RETURNS: ---
352             #===============================================================================
353             sub sudoku_read {
354             my ( $sdk_ref, $filename ) = @_;
355              
356             open my $INFILE, '<', $filename
357             or die "$0 : failed to open input file $filename : $!\n";
358              
359             while (<$INFILE>) {
360             if (
361             m{ ^ # start of line
362             \s* # leading whitespaces
363             (?:\d\s+){8} # 8 digits separated by whitespaces
364             \d # 9. digit
365             \s* # trailing whitespaces
366             $ # end of line
367             }xm
368             )
369             {
370             push @{$sdk_ref}, [split]; # array of arrays
371             }
372             else
373             {
374             if (
375             m{ ^ # start of line
376             \s* # leading whitespaces
377             [.\d]{9} # 9 digits or points
378             \s* # trailing whitespaces
379             $ # end of line
380             }xm
381             )
382             {
383             $_ =~ s/[.]/0/gxm;
384             $_ =~ s/(\d)/ $1/gxm;
385             push @{$sdk_ref}, [split]; # array of arrays
386             }
387             else
388             {
389             if (
390             m{ ^ # start of line
391             \s* # leading whitespaces
392             # # start of comment
393             }xm
394             )
395             {
396             next;
397             }
398             else
399             {
400             die "error in file '$filename', line ${.}.\n";
401             }
402             }
403             }
404             }
405              
406             close $INFILE
407             or warn "$0 : failed to close input file $filename : $!\n";
408              
409             sudoku_check($sdk_ref);
410              
411             return;
412             } # ---------- end of subroutine sudoku_read ----------
413              
414             #=== FUNCTION ================================================================
415             # NAME: count_occupied_cells
416             # PURPOSE: count actually occupied cells
417             # PARAMETERS: (1) reference to a Sudoku (array of arrays)
418             # RETURNS: number of occupied cells
419             #===============================================================================
420             sub count_occupied_cells {
421             my ($sdk_ref) = @_;
422             my $cells_occupied = 0;
423             foreach my $i ( 0 .. 8 ) {
424             foreach my $j ( 0 .. 8 ) {
425             if ( $sdk_ref->[$i][$j] != 0 ) {
426             $cells_occupied++;
427             }
428             }
429             }
430             return $cells_occupied;
431             } # ---------- end of subroutine count_occupied_cells ----------
432              
433             #=== FUNCTION ================================================================
434             # NAME: sudoku_set
435             # PURPOSE: store the 81 values of a Sudoku from a flat array into
436             # the internal representation (array of arrays)
437             # PARAMETERS: (1) reference to a Sudoku (array of arrays)
438             # (2) reference to flat array of 81 values (digits)
439             # RETURNS: ---
440             # COMMENTS: The Sudoku will be checked for correctness.
441             #===============================================================================
442             sub sudoku_set {
443             my ( $sdk_ref, $linarray_ref ) = @_;
444             foreach my $n ( 0 .. 8 ) {
445             ${$sdk_ref}[$n] = [ @{$linarray_ref}[ ( $n * 9 ) .. ( $n * 9 + 8 ) ] ];
446             }
447             sudoku_check($sdk_ref);
448             return;
449             } # ---------- end of subroutine sudoku_set ----------
450              
451             1; # Magic true value required at end of module
452              
453             __END__