[SPOILER] Perl 'Easy' Quiz of the Week #2005-03-04 - attempt 2 w/ o attachments

"Gillman John (Mr JG)" <John.Gillman-D+OxPxApEuvHeql/[email protected]> Mon, 14 Mar 2005 15:38:23 -0000
Newsgroups gmane.comp.lang.perl.qotw.discuss
Message-ID <279B06BA7F93D711ABE900902758D172025CC72D@BPRPRV03>
This is the first time I have posted a solution to any of the quizzes but I
had already been working on a sudoku solver after being shown the puzzle in
a daily newspaper. Both The Times and The Telegraph in the UK now carry
sudoku puzzles for the delectation of the reader. So, as Delia Smith would
say, here's one I prepared earlier.

The solver takes as an argument the name of a file containing a
representation of the puzzle. This file is simply 9 lines of 9 characters,
each line being a row in the puzzle. The characters are either a dot '.' to
represent a blank square or a digit 1 through 9 to represent itself.

The input file is validated as it is read and and a list of lists (LoL) data
structure is used to hold the puzzle grid. A record is kept of which cells
were set at initialisation as the solver uses Term::ANSIColor attributes to
embolden those cells when printing the solutions out. Also we initialise two
hash tables: one is a hash of lists (HoL) keyed by cell identifier, e.g.
r2c3 (zero-based counting), for those cells that are initially empty, the
value being an empty list that will eventually contain the digits possible
for this cell; the other is also a HoL keyed by the row, column or block
identifier (r1 - r9, c1 - c9 and b00, b03, b06, b30, b33, b36, b60, b63 &
b66, the two digits being row and column of top-left cell of 3x3 block), the
value being a list of empty cell identifiers that can affect the row, column
or block.

Once read in, the grid is checked for validity, i.e. no duplicate numbers in
a row, column or 3x3 block. Then before we actually start looking for a
solution we do a bit of pre-processing to fill out the empty cells HoL above
and to also construct a new one. First the new one; this is a HoL keyed by
row, column or block identifier (group of nine) with the value being a list
of possible digits that could be assigned to any empty cell in that group of
nine. Once we have that information we can construct the list of possible
digits for each empty cell by finding the intersection of the lists for the
row, column and 3x3 block for this cell.

Now we have all the information we need to go looking for solutions. We
invoke the findSolutions() routine passing to it the puzzle grid LoL and the
three HoLs we have constructed plus the identifier of the next empty cell to
try to fill in. This is supplied by the nextCellKey() routine taking as it's
argument the HoL of empty cells, which now contains the possible digits for
each empty cell; if all empty cells have been filled we have a solution and
undef is returned to findSolutions() and the solution is printed to the
screen having been checked for validity. The nice trick with nextCellKey()
is that it actually sorts the empty cells into ascending number of possible
digits and returns the first cell, i.e. the one with the fewest possible
digits.

There are two advantages to this approach: firstly it means that if there is
no solution to a puzzle (see puzzle1_borked.dat, do a diff with puzzle1.dat,
both are valid but the former has no solution) then the cell with no
possible digits is tried first and the solver immediately detects that no
solution can be found; secondly, findSolutions() fills in the possible
digits for the chosen cell one at a time, takes lexically scoped copies of
the LoL and HoLs, updates them with the new reduced possibilities now that a
digit is in the current cell, before calling itself recursively with the
copies and the return of nextCellKey() and this means that, with luck, the
next cell chosen always has just one possible digit and so no backtracking
is needed. If there is more than one possibility for a given cell, each is
tried in turn and then backtracked as the recursion unwinds. 

Try replacing one digit at a time in a .dat file to see possible solutions
multiplying. The allsolns.dat is a blank grid which when used as input will
result in the solver showing all possible grids (if you are that patient and
can read very fast). Out of interest I modified the script to just print the
solution number, ran it with allsolns.dat and left it overnight. It had got
to 3,400,000 or so by the next morning before I hit CTRL-C; this on an
Athlon 2500+ running Cygwin on W2K. The script was actually developed on a
150MHz SPARC 10 which ain't so quick. I wasn't very good a permutations and
combinations at school but are there any mathematicians out there who can
come up with the number of possible Sudoku grids?

It is best to run the solver in an xterm or similar that understands ANSI
escapes to embolden text. I know this is not a complete solution to the
whole quiz as it will not generate a new puzzle. I may have a crack at the
puzzle generator when I get some time.


John



==> sudoku <==
#!/usr/local/bin/perl -w
#
# Copyright (c) 2005, John Gillman
# All rights reserved
#
# This program is free software. It may be used, re-distributed
# and/or modified under the same terms as Perl 5.8.0 (or later),
# (see http://www.perl.com/perl/misc/Artistic.html).
#
# Force pre-declaration, import Term::ANSIColor to allow bold text
# so as to make printed representations of the puzzle grid more
# readable.
#
use strict;
use Term::ANSIColor qw(:constants);

# Check that we have a single argument which is the name of the
# file containing the puzzle data, i.e which cells have numbers
# and which don't. (And what the numbers are, obviously.)
#
die "usage: sudoku filename\a\n" unless scalar @ARGV == 1;
die "sudoko: $ARGV[0] does not exist\a\n" unless -e $ARGV[0];

# Set up formatting components using the Term::ANSIColor constants
# to embolden certain parts so as to replicate the puzzle grid as
# it appears in the newspaper.
#
our $leftEdge = " " . BOLD . "|" . RESET;
our $boldLight = BOLD . "~~~~~" . RESET . "|";
our $boldBold = BOLD . "~~~~~|" . RESET ;
our $lightBold = "-----" . BOLD . "|" . RESET;
our $lightLight = "-----|";
our $spaceBold = "     " . BOLD . "|" . RESET;
our $spaceLight = "     |";

# Use the components to construct row and column separator lines.
#
our $boldRowSep = $leftEdge . ($boldLight x 2 . $boldBold) x 3 . "\n";
our $lightRowSep = $leftEdge . ($lightLight x 2 . $lightBold) x 3 . "\n";
our $colSep = $leftEdge . ($spaceLight x 2 . $spaceBold) x 3 . "\n";

# Initialise puzzle grid to 9 rows and 9 columns of spaces and
# initialise count of solutions found. Puzzle grid is a list of
# lists, columns within rows.
#
our $rlGrid = [];
{
    my @row = ();
    push @row, ' ' for 0 .. 8;
    @{$rlGrid->[$_]} = @row for 0 .. 8;
}
our $solnCt = 0;

# Set up a hash table with, initially, an entry for each cell, the
# key being "row:column". When a cell is filled, it's corresponding
# entry in the hash is deleted.
#
our $rhEmpties = {};
foreach my $row (0 .. 8)
{
    foreach my $col (0 .. 8)
    {
        $rhEmpties->{"$row,$col"} = [];
    }
}

# Open the input file for reading.
#
our $inputFile = shift;
open IN, "<$inputFile" or die "open: $inputFile: $!\n";

# Declare another grid to hold locations in the grid where numbers
# were placed at the start, declare an anonymous hash that will hold
# locations of empty cells and declare an anonymous hash that will
# hold a list of keys of empty cells in each group of nine. Read
# data lines at the end of the script one at a time. There should
# be nine lines, one for each row, and each line should contain nine
# characters, one for each column with the digits 1 through 9 and
# dots representing blank cells.
#
our $rlInitialNos = [];
our $rhEmptyCells = {};
our $rhAffected = {};
while(<IN>)
{

    # Validate input; are there more than nine lines? Drop newline
    # and check we have exactly nine valid characters.
    #
    die "Only nine data lines required for puzzle grid\n" if $. > 9;
    chomp;
    die "Line $.: data -->$_<-- invalid\n" unless /^[1-9.]{9}$/;

    # Set row number from input data line number, split line into
    # individual characters then iterate over the characters.
    #
    my $row = $. - 1;
    my @chars = split //;
    foreach my $col (0 .. 8)
    {

        # If character is a dot, create an empty list in the hash
        # of empty cells. Otherwise, put the number found into the
        # correct cell of the puzzle grid and set same cell in the
        # grid of numbers set at initialisation.
        #
        if($chars[$col] eq '.')
        {
            my $rlGroupKeys = getKeys($row, $col);
            my $cellKey = "r${row}c${col}";
            $rhEmptyCells->{$cellKey} = [];
            push @{$rhAffected->{$_}}, $cellKey for @$rlGroupKeys
        }
        else
        {
            $rlGrid->[$row]->[$col] = $chars[$col];
            $rlInitialNos->[$row]->[$col] ++;
        }
    }
}

# Close input file. Validate puzzle grid and abort if it is a duffer.
#
close IN or die "close: $inputFile: $!\n";
if(my $gridError = validateGrid($rlGrid))
{
    print "\nERROR: $gridError\n\n";
    printGrid($rlGrid);
    die "ABORTING\n";
}

# Show puzzle grid as initialised.
#
print "\nPuzzle grid to solve\n";
printGrid($rlGrid);

# Set up an anonymous hash to contain entries keyed by row, column
# or block identifier (r0 thru r8, c0 thru c8 and b00 thru b66, the
# two block digits signifying the row and column of the top-left cell
# of a 3x3 block). The value for each group of nine is an anonymous
# list of numbers that could occupy any empty cell in the group.
# Do rows and columns first, calling groupPossibles() for each.
#
our $rhGroupsOf9 = {};
foreach my $rowOrCol (0 .. 8)
{
    my $rowKey = "r$rowOrCol";
    my $colKey = "c$rowOrCol";
    $rhGroupsOf9->{$rowKey} = groupPossibles($rlGrid, $rowKey);
    $rhGroupsOf9->{$colKey} = groupPossibles($rlGrid, $colKey);
}

# Now do the 3x3 blocks.
#
foreach my $blockRow (0, 3, 6)
{
    foreach my $blockCol (0, 3, 6)
    {
        my $blockKey = "b$blockRow$blockCol";
        $rhGroupsOf9->{$blockKey} = groupPossibles($rlGrid, $blockKey);
    }
}

# Now that we know which numbers are possibilities for each group of
# nine we can now populate the lists of possible numbers for each empty
# cell.
#
foreach my $cell (keys %$rhEmptyCells)
{
    my $row = substr $cell, 1, 1;
    my $col = substr $cell, 3, 1;
    my $rlGroupKeys = getKeys($row, $col);
    my %count = ();
    foreach my $group (@$rlGroupKeys)
    {
        $count{$_} ++ for @{$rhGroupsOf9->{$group}};
    }
    foreach my $digit (keys %count)
    {
        delete $count{$digit} unless $count{$digit} == 3;
    }
    push @{$rhEmptyCells->{$cell}}, keys %count;
}

# Invoke the findSolutions() subroutine, passing it the puzzle
# grid, the groups of nine, empty cells and cells affected hashes
# and the key of the next cell to fill from the empty cells hash.
# The nextCellKey() routine chooses the cell with the fewest possible
# digits. Any solution found will be printed by the findSolutions()
# routine.
#
findSolutions($rlGrid, $rhGroupsOf9, $rhEmptyCells, $rhAffected,
   nextCellKey($rhEmptyCells));

# Print message if no solution was found. Exit program.
#
print "\nNo solution found\n\n" unless $solnCt;

exit;

#
# -:-:-:- End of main() -:-:-:-
#

#   -------------
sub findSolutions
#   -------------

# Subroutine to populate the grid, a cell at a time, trying
# a possible number then moving on to the next cell by calling
# itself recursively until either a complete solution is
# arrived at or there are no possible values for the cell
# being examined, at which point the routine returns back to
# a previous cell that has other possible numbers to examine.
# If a complete solution is found, i.e. a call to nextCell()
# returns undef because all empty cells have been filled, print
# out the solution grid then return to look for more possible
# solutions.
#

{
    # Get the puzzle grid, the hash of possible numbers for each
    # row, column or block, the hash of empty cells, the hash of
    # cells affected by group and the key of the current empty cell
    # that we are about to fill in. If the current cell key is undef
    # then there were no empty cells left when findSolutions() was
    # called this time so we have a solution. Print it then validate
    # it, die if bad, otherwise return.
    #
    my($rlGrid, $rhGroupsOf9, $rhEmptyCells, $rhAffected,
       $currentCellKey) = @_;
    unless($currentCellKey)
    {
        print "Solution no. ", ++ $solnCt, "\n";
        printGrid($rlGrid);
        if(my $gridError = validateGrid($rlGrid))
        {
            print "\nERROR: $gridError\n\n";
            die "ABORTING\n";
        }
        return;
    }

    # Extract row and column from key. Get keys of the groups of nine
    # affected by the current cell.
    #
    my $row = substr $currentCellKey, 1, 1;
    my $col = substr $currentCellKey, 3, 1;
    my $rlGroupKeys = getKeys($row, $col);

    # Iterate over the possible values for the current cell as held in
    # the $rhEmptyCells hash.
    #
    foreach my $try (@{$rhEmptyCells->{$currentCellKey}})
    {
        # Construct a regular expression used to remove the number
        # from lists.
        #
        my $rxEliminate = qr{[^$try]};

        # Replicate $rlGrid, $rhGroupsOf9, $rhEmptyCells and $rhAffected
        # ready to fill in the current cell with it's possible values
        # and update the data structures.
        #
        my $rlNewGrid = replicateGrid($rlGrid);
        my $rhNewGroupsOf9 = replicateHoL($rhGroupsOf9);
        my $rhNewEmptyCells = replicateHoL($rhEmptyCells);
        my $rhNewAffected = replicateHoL($rhAffected);

        # Place the number we are trying this time in the replica puzzle
        # grid. Remove the current cell from the replica empty cells
        # hash.
        #
        $rlNewGrid->[$row]->[$col] = $try;
        delete $rhNewEmptyCells->{$currentCellKey};

        # Iterate over the groups of nine that are affected by the current
        # cell, modifying the replica anonymous hashes with the number.
        #
        foreach my $group (@$rlGroupKeys)
        {
            # Update this group to remove the number from it's list of
            # possible numbers.
            #
            @{$rhNewGroupsOf9->{$group}} =
               grep /$rxEliminate/, @{$rhNewGroupsOf9->{$group}};

            # Delete the current cell key from the list of affected cells
            # for this group of nine.
            #
            @{$rhNewAffected->{$group}} =
               grep {$_ !~ /$currentCellKey/} @{$rhNewAffected->{$group}};

            # Now update the possible numbers for each remaining empty cell
            # in this group of nine.
            #
            foreach my $cellKey (@{$rhNewAffected->{$group}})
            {
                @{$rhNewEmptyCells->{$cellKey}} =
                   grep /$rxEliminate/, @{$rhNewEmptyCells->{$cellKey}};
            }
        }

        # Now call findSolutions() recursively with the modified data
        # structures and the next cell to examine.
        #
        findSolutions($rlNewGrid, $rhNewGroupsOf9, $rhNewEmptyCells,
           $rhNewAffected, nextCellKey($rhNewEmptyCells));
    }

    # Return now that we have tried all possibilities.
    #
    return;
}

#
# -:-:-:- End of findSolutions() -:-:-:-
#

#   -------
sub getKeys
#   -------

# Subroutine to generate the keys into the $rhGroupsOf9 anonymous
# hash for a given cell row and column and return a list reference.
#
{
    my($row, $col) = @_;
    my $rowKey = "r$row";
    my $colKey = "c$col";
    my $blockKey = "b" . int($row / 3) * 3 . int($col / 3) * 3;
    return [$rowKey, $colKey, $blockKey];
}

#
# -:-:-:- End of getKeys() -:-:-:-
#

#   --------------
sub groupPossibles
#   --------------

# Subroutine to return a reference to a list of possible numbers for
# any empty cells in a given row, column or 3x3 block
#
{
    # Get puzzle grid and group of 9 key. Initialise a list of numbers
    # found for this group. Test whether we are dealing with a row,
    # column or block.
    #
    my($rlGrid, $key) = @_;
    my @found = ();
    if($key =~ /r(\d)/)
    {
        # It's a row, move along the row pushing any numbers found
        # onto the list.
        #
        my $row = $1;
        foreach my $col (0 .. 8)
        {
            next if $rlGrid->[$row]->[$col] eq ' ';
            push @found, $rlGrid->[$row]->[$col];
        }
    }
    elsif($key =~ /c(\d)/)
    {
        # It's a column so move down this time.
        #
        my $col = $1;
        foreach my $row (0 .. 8)
        {
            next if $rlGrid->[$row]->[$col] eq ' ';
            push @found, $rlGrid->[$row]->[$col];
        }
    }
    elsif($key =~ /b(\d)(\d)/)
    {
        # This one's a 3x3 block. Traverse block by columns within
        # rows
        #
        my $row = $1;
        my $col = $2;
        for (
           my $blockRow = $row;
           $blockRow < $row + 3;
           $blockRow ++)
        {
            for (
               my $blockCol = $col;
               $blockCol < $col + 3;
               $blockCol ++)
            {
                next if $rlGrid->[$blockRow]->[$blockCol] eq ' ';
                push @found, $rlGrid->[$blockRow]->[$blockCol];
            }
        }
    }
    else
    {
        # If we get here then the row/column/block argument is duff.
        #
        die "groupPossibles(): key not recognised: $key\n";
    }

    # Set up a hash with all digits from 1 to 9 as keys. Then delete
    # a hash slice of those numbers already found. The remaining keys
    # are the possible numbers so return them as a list reference.
    #
    my %possibles = ();
    $possibles{$_} ++ for (1 .. 9);
    delete @possibles{@found};
    return [keys %possibles];
}

#
# -:-:-:- End of groupPossibles() -:-:-:-
#

#   -----------
sub nextCellKey
#   -----------

# Subroutine to return the key into the empty cells hash of the next
# cell to be examined, or undef if there are no empty cells left. Each
# cell is examined to see how many numbers there are possible for it
# and the results are sorted so that those with the fewest are chosen
# next.
#

{
    # Get anonymous hash of empty cells, return undef if there are
    # no empty cells left,
    #
    my $rhEmptyCells = shift;
    return undef unless my @keys = keys %$rhEmptyCells;

    # Sort the empty cell into ascending count of possible numbers so
    # that cells with fewest possibilities are first, returning the
    # first element of the sorted list of keys.
    #
    return
    (
       map {$_->[0]}
       sort {$a->[1] <=> $b->[1]}
       map {[$_, scalar @{$rhEmptyCells->{$_}}]}
       @keys
    )[0];
}

#
# -:-:-:- End of nextCell() -:-:-:-
#

#   ---------
sub printGrid
#   ---------

# Subroutine to print the supplied grid to STDOUT with formatting to
# improve readability.
#

{
    # Get grid to print.
    #
    my $rlGrid = shift;

    # Print top two lines of the grid that form just the top border and
    # the first of the column separators. Initialise row count.
    #
    print "\n", $boldRowSep, $colSep;
    my $rowNo = 0;

    # Iterate over the rows in the puzzle grid calling the printRow
    # routine to output the line with numbers in.
    #
    foreach my $rlRow (@$rlGrid)
    {
        printRow($rlRow, $rowNo);

        # Increment row number. Print a column separator then a light
        # or bold row separator depending whether we have just done a
        # block of three rows. Print another column separator ready
        # the next row of data unless we are at the end of the grid.
        #
        $rowNo ++;
        print $colSep, $rowNo % 3 ? $lightRowSep : $boldRowSep;
        print $colSep unless $rowNo == 9;
    }

    # Print a newline after the grid then return.
    #
    print "\n";
    return;
}

#
# -:-:-:- End of printGrid() -:-:-:-
#

#   --------
sub printRow
#   --------

# Subroutine to print the grid rows that contain the actual numbers of
# the puzzle. If the number to be printed in a particular cell is one
# that was set at the beginning, it is printed in bold to stand out.
#

{
    # Get a reference to the row to be printed and it's number so that
    # the $rlInitialNos list of lists can be queried. Print the left
    # edge of the grid. Initialise column number.
    #
    my($rlRow, $rowNo) = @_;
    print " " . BOLD . "|" . RESET;
    my $colNo = 0;

    # Iterate along the row printing each number, bold or otherwise, in
    # the correct column with the relevant separator between.
    #
    foreach my $col (@$rlRow)
    {
        print "  ";
        print $rlInitialNos->[$rowNo]->[$colNo] ?
           BOLD . $col . RESET :
           $col;
        $colNo ++;
        print $colNo % 3 ?
           "  |" :
           "  " . BOLD . "|" . RESET;
    }

    # Print newline to end the row and return.
    #
    print "\n";
    return;
}

#
# -:-:-:- End of printRow() -:-:-:-
#

#   ------------
sub replicateHoL
#   ------------

# Subroutine to replicate a hash of lists structure as found in the
# $rhGroupsOf9 and $rhEmptyCells anonymous hashes and return a reference
# to a new hash of lists containing the same data.
#
{
    # Get original grid, initialise the copy.
    #
    my $rhOriginal = shift;
    my $rhReplica = {};

    # Iterate over the keys of the original, setting the values of
    # the replica to anonymous lists containing the de-referenced
    # contents of the original's lists.
    #
    foreach my $key (keys %$rhOriginal)
    {
        $rhReplica->{$key} = [@{$rhOriginal->{$key}}];
    }

    # Return the replica hash of lists.
    #
    return $rhReplica;
}

#
# -:-:-:- End of replicateHoL() -:-:-:-
#

#   -------------
sub replicateGrid
#   -------------

# Subroutine to replicate the supplied grid and return a reference to a
# new list of lists containing the same data.
#
{
    # Get original grid, initialise the copy.
    #
    my $rlOriginal = shift;
    my $rlReplica = [];

    # Iterate over the rows in the original pushing a reference to a new
    # list onto the replica list of lists containing the dereferenced
    # contents of the original row.
    #
    foreach my $row (@$rlOriginal)
    {
        push @$rlReplica, [@$row];
    }

    # Return the completed grid replica.
    #
    return $rlReplica;
}

#
# -:-:-:- End of replicateGrid() -:-:-:-
#

#   ------------
sub validateGrid
#   ------------

# Subroutine to validate the puzzle grid just read from data file.
#

{
    # Get grid to validate. Initialise hash that will be used to
    # hold the numbers found in each row, column or block so that
    # duplicate numbers can be spotted.
    #
    my $rlGrid = shift;
    my($row, $col, %numbersSeen);

    # First, examine grid row by row for duplicates.
    #
    for $row (0 .. 8)
    {
        # Reset numbers seen hash for each row; move along, column
        # by column, incrementing hash entry for numbers seen. Ignore
        # blank cells.
        #
        %numbersSeen = ();
        for $col (0 .. 8)
        {
            next if $rlGrid->[$row]->[$col] eq " ";
            $numbersSeen{$rlGrid->[$row]->[$col]} ++;
        }

        # Now check each hash entry for multiple numbers. Return an
        # error string if a multiple is found.
        #
        for (keys %numbersSeen)
        {
            return "Row @{[$row + 1]} has more than one $_" if
               $numbersSeen{$_} > 1;
        }
    }

    # Do the same for columns.
    #
    for $col (0 .. 8)
    {
        # Reset hash for each column this time; move down column, row by
        # row.
        #
        %numbersSeen = ();
        for $row (0 .. 8)
        {
            next if $rlGrid->[$row]->[$col] eq " ";
            $numbersSeen{$rlGrid->[$row]->[$col]} ++;
        }

        # Again, check for multiples, returning an error string if
        # appropriate.
        #
        for (keys %numbersSeen)
        {
            return "Column @{[$col + 1]} has more than one $_" if
               $numbersSeen{$_} > 1;
        }
    }

    # Now examine the 3x3 blocks. Check in the order top-left, top-
    # centre, top-right, middle-left, middle-centre, middle-right,
    # bottom-left, bottom-centre, bottom-right.
    #
    my @blockNames = qw(
       Top-left
       Top-centre
       Top-right
       Middle-left
       Middle-centre
       Middle-right
       Bottom-left
       Bottom-centre
       Bottom-right);

    # Take every third column within every third row as the top-left
    # cell in each of our 3x3 blocks.
    #
    for $row (0, 3, 6)
    {
        for $col (0, 3, 6)
        {
            # Reset hash, get current block name.
            #
            %numbersSeen = ();
            my $blockname = shift @blockNames;

            # For this block, move across the three columns within
            # the three rows recording which numbers we see.
            #
            for (
               my $blockRow = int($row / 3) * 3;
               $blockRow < (int($row / 3) + 1) * 3;
               $blockRow ++)
            {
                for (
                   my $blockCol = int($col / 3) * 3;
                   $blockCol < (int($col / 3) + 1) * 3;
                   $blockCol ++)
                {
                    next if $rlGrid->[$blockRow]->[$blockCol] eq " ";
                    $numbersSeen{$rlGrid->[$blockRow]->[$blockCol]} ++;
                 }
            }

            # Check, and return error string if multiple found. Only
            # detects first error, there may be more.
            #
            for (keys %numbersSeen)
            {
                return "$blockname block has more than one $_" if
                   $numbersSeen{$_} > 1;
            }
        }
    }

    # If all tests have been passed, return 0 (false) so that no error
    # is flagged.
    #
    return 0;
}

#
# -:-:-:- End of validateGrid() -:-:-:-
#

==> puzzle1.dat <==
...1..8..
.1..4.25.
59.6.3...
..4...1.9
.7.....4.
9.1...3..
...5.6.27
.52.1..6.
..9..8...

==> puzzle1_borked.dat <==
...1..8..
.1..4.95.
59.6.3...
..4...1.9
.7.....4.
9.1...3..
...5.6.27
.52.1..6.
..9..8...

==> puzzle2.dat <==
.28..5.46
...21..3.
397.8....
.83.6.71.
...9.3...
.64.7.59.
....2.685
.1..54...
75.3..42.

==> puzzle3.dat <==
..21.64..
..93875..
7...2...8
..1...7..
.9..3..6.
..5...8..
8...6...5
..34786..
..49.13..

==> allsolns.dat <==
.........
.........
.........
.........
.........
.........
.........
.........
.........




The original of this email was scanned for viruses by the Government Secure Intranet (GSi) virus scanning service supplied exclusively by Energis in partnership with MessageLabs.

On leaving the GSi this email was certified virus-free