diff options
| author | Mohammad S Anwar <Mohammad.Anwar@yahoo.com> | 2020-11-16 03:52:09 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-11-16 03:52:09 +0000 |
| commit | f3cd56eb42c9297271df954f30f8b2ab00f777ea (patch) | |
| tree | 6931f16babd74d3fe3665ed095816f59852a7817 | |
| parent | 25c7b36444c96dd911fd780ee5bc4d15fcaedb21 (diff) | |
| parent | aa9377a2dbd61a383a1fa7e4dfd2109abd721ef1 (diff) | |
| download | perlweeklychallenge-club-f3cd56eb42c9297271df954f30f8b2ab00f777ea.tar.gz perlweeklychallenge-club-f3cd56eb42c9297271df954f30f8b2ab00f777ea.tar.bz2 perlweeklychallenge-club-f3cd56eb42c9297271df954f30f8b2ab00f777ea.zip | |
Merge pull request #2769 from PerlMonk-Athanasius/branch-for-challenge-086
Perl & Raku solutions to Tasks 1 & 2 of the Perl Weekly Challenge #086
| -rw-r--r-- | challenge-086/athanasius/Sudoku-Data/Empty.dat | 9 | ||||
| -rw-r--r-- | challenge-086/athanasius/Sudoku-Data/Example-completed.dat | 9 | ||||
| -rw-r--r-- | challenge-086/athanasius/Sudoku-Data/Example.dat | 9 | ||||
| -rw-r--r-- | challenge-086/athanasius/Sudoku-Data/Hard.dat | 11 | ||||
| -rw-r--r-- | challenge-086/athanasius/Sudoku-Data/Invalid-box.dat | 9 | ||||
| -rw-r--r-- | challenge-086/athanasius/Sudoku-Data/Invalid-col.dat | 9 | ||||
| -rw-r--r-- | challenge-086/athanasius/Sudoku-Data/Invalid-row.dat | 9 | ||||
| -rw-r--r-- | challenge-086/athanasius/Sudoku-Data/Unsolvable.dat | 12 | ||||
| -rw-r--r-- | challenge-086/athanasius/Sudoku-Data/Wikipedia.dat | 11 | ||||
| -rw-r--r-- | challenge-086/athanasius/perl/Sudoku.pm | 327 | ||||
| -rw-r--r-- | challenge-086/athanasius/perl/ch-1.pl | 170 | ||||
| -rw-r--r-- | challenge-086/athanasius/perl/ch-2.pl | 210 | ||||
| -rw-r--r-- | challenge-086/athanasius/raku/Sudoku.rakumod | 266 | ||||
| -rw-r--r-- | challenge-086/athanasius/raku/ch-1.raku | 143 | ||||
| -rw-r--r-- | challenge-086/athanasius/raku/ch-2.raku | 181 |
15 files changed, 1385 insertions, 0 deletions
diff --git a/challenge-086/athanasius/Sudoku-Data/Empty.dat b/challenge-086/athanasius/Sudoku-Data/Empty.dat new file mode 100644 index 0000000000..153cd87502 --- /dev/null +++ b/challenge-086/athanasius/Sudoku-Data/Empty.dat @@ -0,0 +1,9 @@ +_ _ _ _ _ _ _ _ _ +_ _ _ _ _ _ _ _ _ +_ _ _ _ _ _ _ _ _ +_ _ _ _ _ _ _ _ _ +_ _ _ _ _ _ _ _ _ +_ _ _ _ _ _ _ _ _ +_ _ _ _ _ _ _ _ _ +_ _ _ _ _ _ _ _ _ +_ _ _ _ _ _ _ _ _ diff --git a/challenge-086/athanasius/Sudoku-Data/Example-completed.dat b/challenge-086/athanasius/Sudoku-Data/Example-completed.dat new file mode 100644 index 0000000000..95d42fbea2 --- /dev/null +++ b/challenge-086/athanasius/Sudoku-Data/Example-completed.dat @@ -0,0 +1,9 @@ +4 3 5 2 6 9 7 8 1 +6 8 2 5 7 1 4 9 3 +1 9 7 8 3 4 5 6 2 +8 2 6 1 9 5 3 4 7 +3 7 4 6 8 2 9 1 5 +9 5 1 7 4 3 6 2 8 +5 1 9 3 2 6 8 7 4 +2 4 8 9 5 7 1 3 6 +7 6 3 4 1 8 2 5 9 diff --git a/challenge-086/athanasius/Sudoku-Data/Example.dat b/challenge-086/athanasius/Sudoku-Data/Example.dat new file mode 100644 index 0000000000..eba378a89f --- /dev/null +++ b/challenge-086/athanasius/Sudoku-Data/Example.dat @@ -0,0 +1,9 @@ +_ _ _ 2 6 _ 7 _ 1 +6 8 _ _ 7 _ _ 9 _ +1 9 _ _ _ 4 5 _ _ +8 2 _ 1 _ _ _ 4 _ +_ _ 4 6 _ 2 9 _ _ +_ 5 _ _ _ 3 _ 2 8 +_ _ 9 3 _ _ _ 7 4 +_ 4 _ _ 5 _ _ 3 6 +7 _ 3 _ 1 8 _ _ _ diff --git a/challenge-086/athanasius/Sudoku-Data/Hard.dat b/challenge-086/athanasius/Sudoku-Data/Hard.dat new file mode 100644 index 0000000000..59d99d6611 --- /dev/null +++ b/challenge-086/athanasius/Sudoku-Data/Hard.dat @@ -0,0 +1,11 @@ +_ 9 _ 3 _ _ _ _ _ +_ _ 7 _ _ _ 6 _ _ +_ _ _ _ 2 4 _ 3 _ +9 1 _ _ _ _ _ _ 8 +_ _ _ _ _ _ _ _ _ +4 _ _ _ _ 5 _ 2 7 +_ 5 _ 8 7 _ _ 6 _ +_ _ 1 _ _ _ 5 _ _ +_ _ _ 5 _ 6 _ 9 _ + +https://sudoku.com.au/Unsolvable-Sudoku-16.aspx diff --git a/challenge-086/athanasius/Sudoku-Data/Invalid-box.dat b/challenge-086/athanasius/Sudoku-Data/Invalid-box.dat new file mode 100644 index 0000000000..f1a12c9a5a --- /dev/null +++ b/challenge-086/athanasius/Sudoku-Data/Invalid-box.dat @@ -0,0 +1,9 @@ +_ _ _ 2 6 _ 7 _ 1 +6 8 _ _ 7 _ _ 9 _ +1 9 _ _ _ 4 5 _ _ +8 2 _ 1 _ _ _ 4 _ +_ _ 4 6 _ 2 9 _ _ +_ 5 _ _ _ 1 _ 2 8 +_ _ 9 3 _ _ _ 7 4 +_ 4 _ _ 5 _ _ 3 6 +7 _ 3 _ 1 8 _ _ _ diff --git a/challenge-086/athanasius/Sudoku-Data/Invalid-col.dat b/challenge-086/athanasius/Sudoku-Data/Invalid-col.dat new file mode 100644 index 0000000000..bdf3d11c2a --- /dev/null +++ b/challenge-086/athanasius/Sudoku-Data/Invalid-col.dat @@ -0,0 +1,9 @@ +_ _ _ 2 6 _ 7 _ 1 +6 8 _ _ 7 _ _ 9 _ +1 9 _ _ _ 4 5 _ _ +8 2 _ 1 _ _ _ 4 _ +_ _ 4 6 _ 2 9 _ _ +_ 5 _ _ _ 3 _ 2 8 +_ _ 9 3 _ _ _ 7 4 +_ 4 _ 2 5 _ _ 3 6 +7 _ 3 _ 1 8 _ _ _ diff --git a/challenge-086/athanasius/Sudoku-Data/Invalid-row.dat b/challenge-086/athanasius/Sudoku-Data/Invalid-row.dat new file mode 100644 index 0000000000..a3db0dd27f --- /dev/null +++ b/challenge-086/athanasius/Sudoku-Data/Invalid-row.dat @@ -0,0 +1,9 @@ +2 _ _ 2 6 _ 7 _ 1 +6 8 _ _ 7 _ _ 9 _ +1 9 _ _ _ 4 5 _ _ +8 2 _ 1 _ _ _ 4 _ +_ _ 4 6 _ 2 9 _ _ +_ 5 _ _ _ 3 _ 2 8 +_ _ 9 3 _ _ _ 7 4 +_ 4 _ _ 5 _ _ 3 6 +7 _ 3 _ 1 8 _ _ _ diff --git a/challenge-086/athanasius/Sudoku-Data/Unsolvable.dat b/challenge-086/athanasius/Sudoku-Data/Unsolvable.dat new file mode 100644 index 0000000000..a091212851 --- /dev/null +++ b/challenge-086/athanasius/Sudoku-Data/Unsolvable.dat @@ -0,0 +1,12 @@ +2 _ _ 9 _ _ _ _ _ +_ _ _ _ _ _ _ 6 _ +_ _ _ _ _ 1 _ _ _ +5 _ 2 6 _ _ 4 _ 7 +_ _ _ _ _ 4 1 _ _ +_ _ _ _ 9 8 _ 2 3 +_ _ _ _ _ 3 _ 8 _ +_ _ 5 _ 1 _ _ _ _ +_ _ 7 _ _ _ _ _ _ + + +https://www.reddit.com/r/sudoku/comments/7q76ay/friend_tells_me_that_this_is_unsolvable_sudoku/ diff --git a/challenge-086/athanasius/Sudoku-Data/Wikipedia.dat b/challenge-086/athanasius/Sudoku-Data/Wikipedia.dat new file mode 100644 index 0000000000..b775373a10 --- /dev/null +++ b/challenge-086/athanasius/Sudoku-Data/Wikipedia.dat @@ -0,0 +1,11 @@ +5 3 _ _ 7 _ _ _ _ +6 _ _ 1 9 5 _ _ _ +_ 9 8 _ _ _ _ 6 _ +8 _ _ _ 6 _ _ _ 3 +4 _ _ 8 _ 3 _ _ 1 +7 _ _ _ 2 _ _ _ 6 +_ 6 _ _ _ _ 2 8 _ +_ _ _ 4 1 9 _ _ 5 +_ _ _ _ 8 _ _ 7 9 + +https://en.wikipedia.org/wiki/Sudoku diff --git a/challenge-086/athanasius/perl/Sudoku.pm b/challenge-086/athanasius/perl/Sudoku.pm new file mode 100644 index 0000000000..2006ab6e10 --- /dev/null +++ b/challenge-086/athanasius/perl/Sudoku.pm @@ -0,0 +1,327 @@ +#!perl + +############################################################################### +=comment + +Perl Weekly Challenge 086 +========================= + +Task #2 +------- +*Sudoku Puzzle* + +=cut +############################################################################### + +#--------------------------------------# +# Copyright © 2020 PerlMonk Athanasius # +#--------------------------------------# + +############################################################################### +package Sudoku; +############################################################################### + +use strict; +use warnings; +use Class::Tiny qw( grid ); +use Const::Fast; +use Set::Scalar; +use constant DEBUG => 0; + +const my $EMPTY_CELL => '_'; + +#============================================================================== +# Public methods +#============================================================================== + +#------------------------------------------------------------------------------ +sub BUILD +#------------------------------------------------------------------------------ +{ + my ($self, $args) = @_; + + if (defined(my $orig = $args->{clone})) + { + $self->clone($orig); + } + elsif (defined(my $file = $args->{file})) + { + $self->_read_from_file($file); + } + else + { + die 'ERROR: Invalid constructor call, stopped'; + } +} + +#------------------------------------------------------------------------------ +sub clone +#------------------------------------------------------------------------------ +{ + my ($self, $orig) = @_; + + for my $row (0 .. 8) + { + for my $col (0 .. 8) + { + $self->{grid}[$row][$col] = $orig->{grid}[$row][$col]; + } + } +} + +#------------------------------------------------------------------------------ +sub insert +#------------------------------------------------------------------------------ +{ + my ($self, $row, $col, $digit) = @_; + + if ($digit !~ /^[1-9]$/) + { + die qq[ERROR: Invalid digit "$digit", stopped]; + } + + my $target = \$self->{grid}[$row][$col]; + + if ($$target ne $EMPTY_CELL) + { + die qq[ERROR: Cannot add "$digit" at position ($row, $col) which ] . + qq[already contains "$$target", stopped]; + } + + $$target = $digit; +} + +#------------------------------------------------------------------------------ +sub display +#------------------------------------------------------------------------------ +{ + my ($self) = @_; + my $display = ''; + + for my $row (0 .. 8) + { + $display .= sprintf " [ %s ]\n", join ' ', $self->{grid}[$row]->@*; + } + + return $display; +} + +#------------------------------------------------------------------------------ +sub is_valid +#------------------------------------------------------------------------------ +{ + my ($self) = @_; + + return $self->_rows_are_valid && + $self->_cols_are_valid && + $self->_boxes_are_valid; +} + +#------------------------------------------------------------------------------ +sub count_empty_cells +#------------------------------------------------------------------------------ +{ + my ($self) = @_; + my $count = 0; + + for my $row (0 .. 8) + { + for my $col (0 .. 8) + { + ++$count if $self->{grid}[$row][$col] eq $EMPTY_CELL; + } + } + + return $count; +} + +#------------------------------------------------------------------------------ +sub select_empty_cell +#------------------------------------------------------------------------------ +{ + my ($self) = @_; + my %cells; + + for my $row (0 .. 8) + { + for my $col (0 .. 8) + { + if ($self->{grid}[$row][$col] eq $EMPTY_CELL) + { + my @possibles = $self->_find_possible_digits($row, $col); + + my %cell = (row => $row, col => $col, digits => \@possibles); + + push $cells{scalar @possibles}->@*, \%cell; + } + } + } + + my $least = (sort { $a <=> $b } keys %cells)[0]; + + return $cells{$least}->[0]; +} + + +#============================================================================== +# Private methods +#============================================================================== + +#------------------------------------------------------------------------------ +sub _read_from_file +#------------------------------------------------------------------------------ +{ + my ($self, $file) = @_; + + open(my $fh, '<', $file) + or die qq[ERROR: Cannot open file "$file" for reading, stopped]; + + for my $row (0 .. 8) + { + my $line = <$fh>; + chomp $line; + my @digits = split /\s+/, $line; + + for my $col (0 .. 8) + { + my $digit = shift @digits; + + defined $digit + or die qq[ERROR: Missing character in file "$file", stopped]; + + $digit =~ /[1-9$EMPTY_CELL]/ + or die qq[ERROR: Invalid character "$digit" in file ] . + qq["$file", stopped]; + + $self->{grid}[$row][$col] = $digit; + } + } + + close $fh + or die qq[ERROR: Cannot close file "$file", stopped]; +} + +#------------------------------------------------------------------------------ +sub _rows_are_valid +#------------------------------------------------------------------------------ +{ + my ($self) = @_; + + for my $row (0 .. 8) + { + my %digits; + + for my $col (0 .. 8) + { + my $digit = $self->{grid}[$row][$col]; + my $count = ++$digits{$digit}; + + if ($digit ne $EMPTY_CELL && $count > 1) + { + warn qq[Rule a): Digit "$digit" occurs more than once in ] . + qq[row $row\n] if DEBUG; + + return 0; + } + } + } + + return 1; +} + +#------------------------------------------------------------------------------ +sub _cols_are_valid +#------------------------------------------------------------------------------ +{ + my ($self) = @_; + + for my $col (0 .. 8) + { + my %digits; + + for my $row (0 .. 8) + { + my $digit = $self->{grid}[$row][$col]; + my $count = ++$digits{$digit}; + + if ($digit ne $EMPTY_CELL && $count > 1) + { + warn qq[Rule b): Digit "$digit" occurs more than once in ] . + qq[column $col\n] if DEBUG; + + return 0; + } + } + } + + return 1; +} + +#------------------------------------------------------------------------------ +sub _boxes_are_valid +#------------------------------------------------------------------------------ +{ + my ($self) = @_; + + for my $row (0, 3, 6) + { + for my $col (0, 3, 6) + { + my %digits; + + for my $r ($row .. $row + 2) + { + for my $c ($col .. $col + 2) + { + my $digit = $self->{grid}[$r][$c]; + my $count = ++$digits{$digit}; + + if ($digit ne $EMPTY_CELL && $count > 1) + { + warn qq[Rule c): Digit "$digit" occurs more than ] . + qq[once in box ($row, $col)\n] if DEBUG; + + return 0; + } + } + } + } + } + + return 1; +} + +#------------------------------------------------------------------------------ +sub _find_possible_digits +#------------------------------------------------------------------------------ +{ + my ($self, $row, $col) = @_; + my $possibles = Set::Scalar->new(1 .. 9); + + for my $c (0 .. 8) + { + $possibles->delete($self->{grid}[$row][$c]); + } + + for my $r (0 .. 8) + { + $possibles->delete($self->{grid}[$r][$col]); + } + + my $box_row = $row - ($row % 3); + my $box_col = $col - ($col % 3); + + for my $r ($box_row .. $box_row + 2) + { + for my $c ($box_col .. $box_col + 2) + { + $possibles->delete($self->{grid}[$r][$c]); + } + } + + return sort { $a <=> $b } $possibles->members; +} + +############################################################################### +1; +############################################################################### diff --git a/challenge-086/athanasius/perl/ch-1.pl b/challenge-086/athanasius/perl/ch-1.pl new file mode 100644 index 0000000000..91c61dbc8c --- /dev/null +++ b/challenge-086/athanasius/perl/ch-1.pl @@ -0,0 +1,170 @@ +#!perl + +############################################################################### +=comment + +Perl Weekly Challenge 086 +========================= + +Task #1 +------- +*Pair Difference* + +Submitted by: Mohammad S Anwar + +You are given an array of integers @N and an integer $A. + +Write a script to find if there exists a pair of elements in the array whose +difference is $A. + +Print 1 if exists otherwise 0. + +Example 1: + + Input: @N = (10, 8, 12, 15, 5) and $A = 7 + Output: 1 as 15 - 8 = 7 + +Example 2: + + Input: @N = (1, 5, 2, 9, 7) and $A = 6 + Output: 1 as 7 - 1 = 6 + +Example 3: + + Input: @N = (10, 30, 20, 50, 40) and $A = 15 + Output: 0 + +=cut +############################################################################### + +#--------------------------------------# +# Copyright © 2020 PerlMonk Athanasius # +#--------------------------------------# + +#============================================================================== +=comment + +Assumption: The pair must be different (but possibly equal) elements in the + array + +Algorithm: Exhaustive comparison of each pair difference, together with its + negation, until a solution is found or all pairs have been examined + +=cut +#============================================================================== + +use strict; +use warnings; +use Const::Fast; +use Getopt::Long; +use Regexp::Common qw( number ); + +const my $EXPLAIN => 1; +const my $USAGE => +"Usage: + perl $0 -A=<Int> -- [<N> ...] + + -A=<Int> A possible difference between two elements in \@N + [<N> ...] An array of two or more integers\n"; + +#------------------------------------------------------------------------------ +BEGIN +#------------------------------------------------------------------------------ +{ + $| = 1; + print "\nChallenge 086, Task #1: Pair Difference (Perl)\n\n"; +} + +#============================================================================== +MAIN: +#============================================================================== +{ + my ($N, $A) = parse_command_line(); + + printf "Input: \@N = (%s) and \$A = %d\n", join(', ', @$N), $A; + + my ($minuend, $subtrahend) = find_pair($N, $A); + + if (defined $minuend && defined $subtrahend) + { + if ($EXPLAIN) + { + print "Output: 1 as $minuend - $subtrahend = $A\n"; + } + else + { + print "Output: 1\n"; + } + } + else + { + print "Output: 0\n"; + } +} + +#------------------------------------------------------------------------------ +sub find_pair +#------------------------------------------------------------------------------ +{ + my ($N, $A) = @_; + my ($minuend, $subtrahend); + + OUTER: + for my $i (0 .. $#$N - 1) + { + for my $j ($i + 1 .. $#$N) + { + my $difference = $N->[$i] - $N->[$j]; + + if ($difference == $A) + { + $minuend = $N->[$i]; + $subtrahend = $N->[$j]; + last OUTER; + } + elsif ($difference == -$A) + { + $minuend = $N->[$j]; + $subtrahend = $N->[$i]; + last OUTER; + } + } + } + + return ($minuend, $subtrahend); +} + +#------------------------------------------------------------------------------ +sub parse_command_line +#------------------------------------------------------------------------------ +{ + my $A; + + GetOptions('A=i' => \$A) or error('Incorrect command line argument'); + + defined $A or error('Missing $A'); + + $A =~ /\A$RE{num}{int}\z/ or error("\$A ($A) is not an integer"); + + my @N = @ARGV; + + scalar @N >= 2 or error('Too few elements in @N'); + + for my $n (@N) + { + $n =~ /\A$RE{num}{int}\z/ or error("\"$n\" is not an integer"); + } + + return (\@N, $A); +} + +#------------------------------------------------------------------------------ +sub error +#------------------------------------------------------------------------------ +{ + my ($message) = @_; + + die "ERROR: $message\n$USAGE"; +} + +############################################################################### diff --git a/challenge-086/athanasius/perl/ch-2.pl b/challenge-086/athanasius/perl/ch-2.pl new file mode 100644 index 0000000000..448d1f0b7b --- /dev/null +++ b/challenge-086/athanasius/perl/ch-2.pl @@ -0,0 +1,210 @@ +#!perl + +############################################################################### +=comment + +Perl Weekly Challenge 086 +========================= + +Task #2 +------- +*Sudoku Puzzle* + +Submitted by: Mohammad S Anwar + +You are given Sudoku puzzle (9x9). + +Write a script to complete the puzzle and must respect the following rules: + +a) Each row must have the numbers 1-9 occuring just once. +b) Each column must have the numbers 1-9 occuring just once. +c) The numbers 1-9 must occur just once in each of the 9 sub-boxes (3x3) of the + grid. + +Example: + + [ _ _ _ 2 6 _ 7 _ 1 ] + [ 6 8 _ _ 7 _ _ 9 _ ] + [ 1 9 _ _ _ 4 5 _ _ ] + [ 8 2 _ 1 _ _ _ 4 _ ] + [ _ _ 4 6 _ 2 9 _ _ ] + [ _ 5 _ _ _ 3 _ 2 8 ] + [ _ _ 9 3 _ _ _ 7 4 ] + [ _ 4 _ _ 5 _ _ 3 6 ] + [ 7 _ 3 _ 1 8 _ _ _ ] + +Output: + + [ 4 3 5 2 6 9 7 8 1 ] + [ 6 8 2 5 7 1 4 9 3 ] + [ 1 9 7 8 3 4 5 6 2 ] + [ 8 2 6 1 9 5 3 4 7 ] + [ 3 7 4 6 8 2 9 1 5 ] + [ 9 5 1 7 4 3 6 2 8 ] + [ 5 1 9 3 2 6 8 7 4 ] + [ 2 4 8 9 5 7 1 3 6 ] + [ 7 6 3 4 1 8 2 5 9 ] + +As the above puzzle respect the 3 rules including 9-sub-boxes as shown below: + + [ 4 3 5 ] [ 2 6 9 ] [ 7 8 1 ] + [ 6 8 2 ] [ 5 7 1 ] [ 4 9 3 ] + [ 1 9 7 ] [ 8 3 4 ] [ 5 6 2 ] + + [ 8 2 6 ] [ 1 9 5 ] [ 3 4 7 ] + [ 3 7 4 ] [ 6 8 2 ] [ 9 1 5 ] + [ 9 5 1 ] [ 7 4 3 ] [ 6 2 8 ] + + [ 5 1 9 ] [ 3 2 6 ] [ 8 7 4 ] + [ 2 4 8 ] [ 9 5 7 ] [ 1 3 6 ] + [ 7 6 3 ] [ 4 1 8 ] [ 2 5 9 ] + +=cut +############################################################################### + +#--------------------------------------# +# Copyright © 2020 PerlMonk Athanasius # +#--------------------------------------# + +#============================================================================== +=comment + +The module "Sudoku.pm" provides class Sudoku. An instance of this class repre- +sents a Sudoku grid, which may be empty, partially filled, or complete. The +following methods are provided: + + Name Description + ----------------- ------------------------------------------------------- + new Reads in a Sudoku puzzle from file + clone Creates a deep copy of an existing grid + insert Inserts a digit into an empty cell + display Returns a string displaying the grid as in the Example + is_valid Returns true if the grid complies with rules a), b), + and c), false otherwise + count_empty_cells Returns the number of empty cells remaining in the grid + (an integer in the range 0 to 81) + select_empty_cell Returns the coordinates (row and column) of an empty + cell for which the number of possible digits is a + minimum, together with a list of those digits + +The algorithm, implemented in sub solve(), employs backtracking (i.e., a depth- +first search of the potential search tree) via recursion. + +Note: The algorithm stops as soon as a valid solution is found. If no solution +is possible, an appropriate message is displayed. However, if more than one +solution is possible, this will not be discovered. (A "well-posed" puzzle has +only a single solution.) + +=cut +#============================================================================== + +use strict; +use warnings; +use Const::Fast; +use File::Basename; +use Regexp::Common qw( number ); +use lib qw( . ); +use Sudoku; + +const my $DEFAULT => '../Sudoku-Data/Example.dat'; +const my @SUFFIXES => qw( .dat .txt ); +const my $USAGE => +"Usage: + perl $0 [<file_path>] -- The name of a file containing a Sudoku puzzle\n"; + +#------------------------------------------------------------------------------ +BEGIN +#------------------------------------------------------------------------------ +{ + $| = 1; + print "\nChallenge 086, Task #2: Sudoku Puzzle (Perl)\n\n"; +} + +#============================================================================== +MAIN: +#============================================================================== +{ + my $file_path = parse_command_line(); + my $original = Sudoku->new(file => $file_path); + my $filename = fileparse($file_path, @SUFFIXES); + + printf "$filename:\n\n%s\n", $original->display; + + if ($original->is_valid) + { + if (my $solution = solve($original)) + { + printf "Solution:\n\n%s", $solution->display; + } + else + { + print "No solution found\n"; + } + } + else + { + print "This puzzle violates the rules: no solution is possible\n"; + } +} + +#------------------------------------------------------------------------------ +sub solve # Recursive backtracking routine +#------------------------------------------------------------------------------ +{ + my ($current) = @_; + + return if !$current->is_valid; # Failure + return $current if $current->count_empty_cells == 0; # Success + + my $cell = $current->select_empty_cell; + my @digits = $cell->{digits}->@*; + + return if scalar @digits == 0; # Failure + + for my $digit (@digits) + { + my $next = Sudoku->new(clone => $current); + + $next->insert($cell->{row}, $cell->{col}, $digit); + + my $result = solve($next); # Recursive call + + return $result if $result; # Success + } + + return; # Failure +} + +#------------------------------------------------------------------------------ +sub parse_command_line +#------------------------------------------------------------------------------ +{ + my $path = $DEFAULT; + my $args = scalar @ARGV; + + if ($args == 1) + { + $path = $ARGV[0]; + } + elsif ($args > 1) + { + error("Expected a single command-line argument, found $args"); + } + + -e $path or error(qq[The file "$path" does not exist]); + -s $path or error(qq[The file "$path" is empty]); + -f $path or error(qq[The file "$path" is not a plain file]); + + return $path; +} + +#------------------------------------------------------------------------------ +sub error +#------------------------------------------------------------------------------ +{ + my ($message) = @_; + + die "ERROR: $message\n$USAGE"; +} + +############################################################################### diff --git a/challenge-086/athanasius/raku/Sudoku.rakumod b/challenge-086/athanasius/raku/Sudoku.rakumod new file mode 100644 index 0000000000..15e3a5d044 --- /dev/null +++ b/challenge-086/athanasius/raku/Sudoku.rakumod @@ -0,0 +1,266 @@ +use v6d; + +############################################################################### +=begin comment + +Perl Weekly Challenge 086 +========================= + +Task #2 +------- +*Sudoku Puzzle* + +=end comment +############################################################################### + +#--------------------------------------# +# Copyright © 2020 PerlMonk Athanasius # +#--------------------------------------# + +############################################################################### +unit class Sudoku; +############################################################################### + +my Str constant $EMPTY-CELL = '_'; +my constant @DIGITS = Array[Str].new: '1' .. '9'; + +subset Cell of Str where { .all ⊂ @DIGITS ∪ $EMPTY-CELL }; +subset Digit of Str where { .all ⊂ @DIGITS }; + +has Cell @.grid[9; 9]; +has Str $!file; + +##============================================================================= +# Public methods +##============================================================================= + +#------------------------------------------------------------------------------ +submethod BUILD( Str:D :$!file = '' ) +#------------------------------------------------------------------------------ +{ + if $!file + { + my Str @lines = $!file.IO.lines; + + for 0 .. 8 -> UInt $row + { + my Cell @digits = @lines[$row].split: /\s+/; + + for 0 .. 8 -> UInt $col + { + @!grid[$row; $col] = @digits.shift; + } + } + } +} + +#------------------------------------------------------------------------------ +method clone( Sudoku:D $orig --> Sudoku:D ) +#------------------------------------------------------------------------------ +{ + my $new-self = Sudoku.new; + + for 0 .. 8 -> UInt $row + { + for 0 .. 8 -> UInt $col + { + $new-self.grid[$row; $col] = $orig.grid[$row; $col]; + } + } + + return $new-self; +} + +#------------------------------------------------------------------------------ +method insert( UInt:D $row, UInt:D $col, Digit:D $digit ) +#------------------------------------------------------------------------------ +{ + my Cell $target = @!grid[$row; $col]; + + $target eq $EMPTY-CELL + or die qq[ERROR: Cannot add "$digit" at position ($row, $col) which ] ~ + qq[already contains "$target"]; + + @!grid[$row; $col] = $digit; +} + +#------------------------------------------------------------------------------ +method display( --> Str:D ) +#------------------------------------------------------------------------------ +{ + my Str $display = ''; + + for 0 .. 8 -> UInt $row + { + $display ~= ' ['; + + for 0 .. 8 -> UInt $col + { + $display ~= ' ' ~ @!grid[$row; $col]; + } + + $display ~= " ]\n"; + } + + return $display; +} + +#------------------------------------------------------------------------------ +method is-valid( --> Bool:D ) +#------------------------------------------------------------------------------ +{ + return self!rows-are-valid && + self!cols-are-valid && + self!boxes-are-valid; +} + +#------------------------------------------------------------------------------ +method count-empty-cells( --> UInt:D ) +#------------------------------------------------------------------------------ +{ + my UInt $count = 0; + + for 0 .. 8 -> UInt $row + { + for 0 .. 8 -> UInt $col + { + ++$count if @!grid[$row; $col] eq $EMPTY-CELL; + } + } + + return $count; +} |
