File Coverage

blib/lib/Games/Sudoku/CPSearch.pm
Criterion Covered Total %
statement 142 150 94.6
branch 37 46 80.4
condition 4 6 66.6
subroutine 23 24 95.8
pod 4 4 100.0
total 210 230 91.3


line stmt bran cond sub pod time code
1             package Games::Sudoku::CPSearch;
2              
3 9     9   222628 use warnings;
  9         23  
  9         295  
4 9     9   53 use strict;
  9         20  
  9         287  
5 9     9   216 use 5.008;
  9         31  
  9         479  
6 9     9   8516 use List::MoreUtils qw(all mesh);
  9         11406  
  9         22065  
7              
8             our $VERSION = '1.00';
9              
10             # Public methods
11              
12             sub new {
13 8     8 1 1127 my ($class, $file) = @_;
14 8         19 my $puzzle;
15 8 50       39 if (defined $file) {
16 0         0 undef $/;
17 0 0       0 open FH, $file or die "could not open puzzle file\n";
18 0         0 my $puzzle = ;
19 0         0 close FH;
20 0         0 $puzzle =~ s/\s+//;
21             }
22              
23 8         75 my $rows = [qw(A B C D E F G H I)];
24 8         50 my $cols = [qw(1 2 3 4 5 6 7 8 9)];
25 8         47 my $squares = $class->_cross($rows, $cols);
26              
27 8         26 my @unitlist = ();
28 8         54 push @unitlist, $class->_cross($rows, [$_]) for @$cols;
29 8         59 push @unitlist, $class->_cross([$_], $cols) for @$rows;
30 8         53 foreach my $r ([qw(A B C)],[qw(D E F)],[qw(G H I)]) {
31 24         96 foreach my $c ([qw(1 2 3)],[qw(4 5 6)],[qw(7 8 9)]) {
32 72         174 push @unitlist, $class->_cross($r, $c);
33             }
34             }
35              
36 8         29 my %units;
37 8         23 foreach my $s (@$squares) {
38 648         6533 $units{$s} = [];
39 648         1067 foreach my $unit (@unitlist) {
40 17496         26926 foreach my $s2 (@$unit) {
41 149688 100       350618 if ($s eq $s2) {
42 1944         2071 push @{$units{$s}}, $unit;
  1944         4252  
43 1944         3819 last;
44             }
45             }
46             }
47             }
48              
49 8         24 my %peers;
50 8         25 foreach my $s (@$squares) {
51 648         1697 $peers{$s} = [];
52 648         740 foreach my $u (@{$units{$s}}) {
  648         1257  
53 1944         3138 foreach my $s2 (@$u) {
54 17496 100       35847 push(@{$peers{$s}}, $s2) if ($s2 ne $s);
  15552         53774  
55             }
56             }
57             }
58              
59 8         138 my $self = {
60             _unitlist => \@unitlist,
61             _rows => $rows,
62             _cols => $cols,
63             _squares => $squares,
64             _units => \%units,
65             _peers => \%peers,
66             _puzzle => undef,
67             _solution => "",
68             };
69              
70 8         78 bless $self, $class;
71 8 50       43 $self->set_puzzle($puzzle) if defined $puzzle;
72 8         96 return $self;
73             }
74              
75             sub solution {
76 4     4 1 33 my ($self) = @_;
77 4         31 return $self->{_solution};
78             }
79              
80             sub solve {
81 100     100 1 805 my ($self) = @_;
82 100         412 my $solution = $self->_search($self->_propagate());
83 100 50       2622 return undef unless (defined $solution);
84 100         318 $self->{_solution} = "";
85 100         308 $self->{_solution} .= $solution->{$_} for ($self->_squares());
86 100         2652 return $self->{_solution};
87             }
88              
89             sub set_puzzle {
90 101     101 1 88790 my ($self, $puzzle) = @_;
91             return undef
92 101 50 33     1297 unless ((length($puzzle) == 81) && ($puzzle =~ /^[\d\.\-]+$/));
93 101         280 $puzzle =~ s/0/\./g; # 0 is a digit, which makes things hairy.
94 101         337 $self->{_puzzle} = $puzzle;
95 101         256 return $self->{_puzzle};
96             }
97              
98             # internal methods
99              
100             sub _unitlist {
101 0     0   0 my ($self) = @_;
102 0         0 return @{$self->{_unitlist}};
  0         0  
103             }
104              
105             sub _rows {
106 1     1   36 my ($self) = @_;
107 1         14 return $self->{_rows};
108             }
109              
110             sub _cols {
111 1     1   2 my ($self) = @_;
112 1         8 return $self->{_cols};
113             }
114              
115             sub _units {
116 196151     196151   282961 my ($self, $s) = @_;
117 196151         222900 return @{$self->{_units}{$s}};
  196151         746488  
118             }
119              
120             sub _peers {
121 57204     57204   94037 my ($self, $s) = @_;
122 57204         65606 return @{$self->{_peers}{$s}};
  57204         583316  
123             }
124              
125             sub _squares {
126 6931     6931   10501 my ($self) = @_;
127 6931         8153 return @{$self->{_squares}};
  6931         162676  
128             }
129              
130             sub _cross {
131 224     224   342 my ($class, $a, $b) = @_;
132 224         314 my @cross = ();
133 224         362 foreach my $x (@$a) {
134 1008         1454 foreach my $y (@$b) {
135 2592         6406 push @cross, "$x$y";
136             }
137             }
138 224         1131 return \@cross;
139             }
140              
141             sub _fullgrid {
142 100     100   166 my ($self) = @_;
143 100         181 my %grid;
144 100         240 $grid{$_} = "123456789" for ($self->_squares());
145 100         916 return \%grid;
146             }
147              
148             sub _propagate {
149 100     100   220 my ($self) = @_;
150 100 50       287 return undef unless defined $self->_puzzle();
151 100         379 my @d = split(//, $self->_puzzle());
152 100         555 my @s = $self->_squares();
153 100         5544 my @z = mesh @s, @d;
154 100         879 my $grid = $self->_fullgrid();
155 100         374 while (scalar(@z) > 0) {
156 8100         18310 my ($s, $d) = splice(@z,0,2);
157 8100 100       34685 next unless ($d =~ /^\d$/);
158 2051 50       5958 return undef unless defined $self->_assign($grid, $s, $d);
159             }
160 100         5168 return $grid;
161             }
162              
163             sub _assign {
164 126376     126376   232611 my ($self, $grid, $s, $d) = @_;
165 126376         363972 my @delete = grep {$_ ne $d} split(//, $grid->{$s});
  211916         510459  
166 126376 100       422205 return $grid if (scalar(@delete) == 0);
167 39553         48917 my @results;
168 39553         64249 foreach my $del (@delete) {
169 85540         189512 $grid = $self->_eliminate($grid, $s, $del);
170 85540         247401 push @results, $grid;
171             }
172 39553 100   75818   191566 return $grid if all { defined($_) } @results;
  75818         233318  
173 21499         111929 return undef;
174             }
175              
176             sub _eliminate {
177 1023460     1023460   1772438 my ($self, $grid, $s, $d) = @_;
178 1023460 100 100     7330415 return $grid
179             unless ((defined $grid->{$s}) && ($grid->{$s} =~ /$d/));
180 226031         1589982 $grid->{$s} =~ s/$d//;
181 226031         471500 my $len = length($grid->{$s});
182 226031 100       499914 return undef if ($len == 0);
183 223951 100       488378 if ($len == 1) {
184 57204         125985 foreach my $peer ($self->_peers($s)) {
185 937920         2344067 $grid = $self->_eliminate($grid, $peer, $grid->{$s});
186 937920 100       2596204 return undef unless defined $grid;
187             }
188             }
189              
190 196151         501563 foreach my $unit ($self->_units($s)) {
191 565978         978822 my @dplaces = grep { $grid->{$_} =~ /$d/ } @$unit;
  5093802         16354039  
192 565978         859814 my $locations = scalar @dplaces;
193 565978 100       1240844 return undef if ($locations == 0);
194 565099 100       1786090 if ($locations == 1) {
195 118101         306414 $grid = $self->_assign($grid, $dplaces[0], $d);
196 118101 100       457690 return undef unless defined $grid;
197             }
198             }
199 176732         490472 return $grid;
200             }
201              
202             sub _search {
203 6324     6324   10643 my ($self, $grid) = @_;
204 6324 100       17620 return undef unless defined $grid;
205 3365 100   39049   15657 return $grid if (all {length($grid->{$_}) == 1} $self->_squares());
  39049         62384  
206             # solved!
207 595998         964242 my @sorted = sort {length($grid->{$a}) <=> length($grid->{$b})}
  264465         546551  
208 3265         34727 grep {length($grid->{$_}) > 1} $self->_squares();
209 3265         28096 my $fewest_digits = shift @sorted;
210 3265         5767 my $result = undef;
211 3265         17459 foreach my $d (split(//, $grid->{$fewest_digits})) {
212 6224         258581 my %grid_copy = %$grid;
213 6224         44752 $result = $self->_search($self->_assign(\%grid_copy, $fewest_digits, $d));
214 6224 100       123961 return $result if defined $result;
215             }
216 2645         27948 return $result;
217             }
218              
219             sub _puzzle {
220 201     201   284 my ($self) = @_;
221 201         2791 return $self->{_puzzle};
222             }
223              
224             sub _verify {
225 2     2   14 my ($self, $puzzle) = @_;
226 2         8 for (1..9) {
227 18         218 my $count = () = $puzzle =~ /$_/g;
228 18 50       110 return undef unless $count == 9;
229             }
230 2         13 return 1;
231             }
232              
233             1; # End of Games::Sudoku::CPSearch
234              
235             =head1 NAME
236              
237             Games::Sudoku::CPSearch - Solve Sudoku problems quickly.
238              
239             =head1 VERSION
240              
241             Version 1.00
242              
243             =cut
244              
245             =head1 SYNOPSIS
246              
247             use Games::Sudoku::CPSearch;
248              
249             my $puzzle = <
250             4.....8.5
251             .3.......
252             ...7.....
253             .2.....6.
254             ....8.4..
255             ....1....
256             ...6.3.7.
257             5..2.....
258             1.4......
259             PUZZLE
260              
261             open FH, ">example.txt";
262             print FH $puzzle;
263             close FH;
264              
265             my $sudoku = Games::Sudoku::CPSearch->new("example.txt");
266             print $sudoku->solve(), "\n";
267              
268             =head1 DESCRIPTION
269              
270             This module solves a Sudoku puzzle using the same constraint propagation technique/algorithm explained on Peter Norvig's website (http://norvig.com/sudoku.html), and implemented there in Python.
271              
272             =head1 METHODS
273              
274             =over 4
275              
276             =item $o = Games::Sudoku::CPSearch->new()
277              
278             Initializes the sudoku solving framework.
279              
280             =item $o->solve()
281              
282             Solves the puzzle. Returns the solution as a flat 81 character string.
283              
284             =item $o->set_puzzle($puzzle)
285              
286             Sets the puzzle to be solved. The only parameter is the 81 character string
287             representing the puzzle. The only characters allowed are [0-9\.\-].
288             Sets the puzzle to be solved. You can then reuse the object:
289              
290             my $o = Games::Sudoku::CPSearch->set_puzzle($puzzle);
291             print $o->solve(), "\n";
292             $o->set_puzzle($another_puzzle);
293             print $o->solve(), "\n";
294              
295             =item $o->solution()
296              
297             Returns the solution string, or the empty string if there is no solution.
298              
299             =back
300              
301             =head1 INTERNAL METHODS
302              
303             These methods are exposed but are not intended to be used.
304              
305             =over 4
306              
307             =item $o->_fullgrid()
308              
309             Returns a hash with squares as keys and "123456789" as each value.
310              
311             =item $o->_puzzle()
312              
313             Returns the object's puzzle as an 81 character string.
314              
315             =item $o->_unitlist($square)
316              
317             Returns an list of sudoku "units": rows, columns, boxes for a given square.
318              
319             =item $o->_propagate()
320              
321             Perform the constraint propagation on the Sudoku grid.
322              
323             =item $o->_eliminate($grid, $square, $digit)
324              
325             Eliminate digit from the square in the grid.
326              
327             =item $o->_assign($grid, $square, $digit)
328              
329             Assign digit to square in grid. Mutually recursive with eliminate().
330              
331             =item $o->_rows()
332              
333             Returns array of row values: A-I
334              
335             =item $o->_cols()
336              
337             Returns array of column values: 1-9
338              
339             =item $o->_squares()
340              
341             Return list of all the squares in a Sudoku grid:
342             A1, A2, ..., A9, B1, ..., I1, ..., I9
343              
344             =item $o->_units($square)
345              
346             Return list of all the units for a given square.
347              
348             =item $o->_peers($square)
349              
350             Return list of all the peers for a given square.
351              
352             =item $o->_search()
353              
354             Perform search for a given grid after constraint propagation.
355              
356             =item $o->_cross()
357              
358             Return "cross product" of 2 arrays.
359              
360             =item $o->_verify($solution)
361              
362             Returns undef if the sudoku solution is not valid. Returns 1 if it is.
363              
364             =back
365              
366             =head1 AUTHOR
367              
368             Martin-Louis Bright, C<< >>
369              
370             =head1 BUGS
371              
372             Please report any bugs or feature requests to C, or through
373             the web interface at L. I will be notified, and then you'll
374             automatically be notified of progress on your bug as I make changes.
375              
376             =head1 SUPPORT
377              
378             You can find documentation for this module with the perldoc command.
379              
380             perldoc Games::Sudoku::CPSearch
381              
382              
383             You can also look for information at:
384              
385             =over 4
386              
387             =item * RT: CPAN's request tracker
388              
389             L
390              
391             =item * AnnoCPAN: Annotated CPAN documentation
392              
393             L
394              
395             =item * CPAN Ratings
396              
397             L
398              
399             =item * Search CPAN
400              
401             L
402              
403             =back
404              
405              
406             =head1 ACKNOWLEDGEMENTS
407              
408             Peter Norvig, for the explanation/tutorial and python code at
409             http://www.norvig.com/sudoku.html.
410              
411             =head1 COPYRIGHT & LICENSE
412              
413             Copyright 2008 Martin-Louis Bright, all rights reserved.
414              
415             This program is free software; you can redistribute it and/or modify it
416             under the same terms as Perl itself.
417              
418             =cut