aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMohammad S Anwar <Mohammad.Anwar@yahoo.com>2020-11-16 07:02:08 +0000
committerGitHub <noreply@github.com>2020-11-16 07:02:08 +0000
commit6f50a66a7e1c40cdc005622f5a4b7dfb7fbb45c6 (patch)
treefbf866a89a268c6bd33cff8a891e829b04ec5239
parent539df1cb7ea7587aa2a5785dc6db32361a73f44b (diff)
parentc2e3491a7aebb0d7224d4c7caf969faf0227fcf6 (diff)
downloadperlweeklychallenge-club-6f50a66a7e1c40cdc005622f5a4b7dfb7fbb45c6.tar.gz
perlweeklychallenge-club-6f50a66a7e1c40cdc005622f5a4b7dfb7fbb45c6.tar.bz2
perlweeklychallenge-club-6f50a66a7e1c40cdc005622f5a4b7dfb7fbb45c6.zip
Merge pull request #2775 from dcw803/master
imported my solution to task 1, got stuck on suduko solver
-rw-r--r--challenge-086/duncan-c-white/README95
-rwxr-xr-xchallenge-086/duncan-c-white/perl/ch-1.pl65
-rwxr-xr-xchallenge-086/duncan-c-white/perl/ch-2.pl500
3 files changed, 633 insertions, 27 deletions
diff --git a/challenge-086/duncan-c-white/README b/challenge-086/duncan-c-white/README
index 53a59c4fc8..306809ff7f 100644
--- a/challenge-086/duncan-c-white/README
+++ b/challenge-086/duncan-c-white/README
@@ -1,50 +1,91 @@
-Task 1: "Triplet Sum
+Task 1: "Pair Difference
-You are given an array of real numbers greater than zero.
+You are given an array of integers @N and an integer $A.
-Write a script to find if there exists a triplet (a,b,c) such that 1 <
-a+b+c < 2. Print 1 if you succeed otherwise 0.
+Write a script to find find if there exists a pair of elements in the
+array whose difference is $A. Print 1 if exists otherwise 0.
Example 1:
- Input: @R = (1.2, 0.4, 0.1, 2.5)
- Output: 1 as 1 < 1.2 + 0.4 + 0.1 < 2
+ Input: @N = (10, 8, 12, 15, 5) and $A = 7
+ Output: 1 as 15 - 8 = 7
Example 2:
- Input: @R = (0.2, 1.5, 0.9, 1.1)
- Output: 0
+ Input: @N = (1, 5, 2, 9, 7) and $A = 6
+ Output: 1 as 7 - 1 = 6
Example 3:
- Input: @R = (0.5, 1.1, 0.3, 0.7)
- Output: 1 as 1 < 0.5 + 1.1 + 0.3 < 2
+ Input: @N = (10, 30, 20, 50, 40) and $A = 15
+ Output: 0
-My notes: simple, I think it means "pick any 3 elements a, b and c from the
-array and determine if any a,b,c triple sums to between 1 and 2", although both
-successful examples show 3 ADJACENT elements a, b and c...
+My notes: simple and straightforward: try all pairs of elements looking
+for abs(x-y)==A
-Task 2: "Power of Two Integers
+Task 2: "Sudoku Puzzle
-You are given a positive integer $N.
+You are given Sudoku puzzle (9x9).
-Write a script to find if it can be expressed as a ** b where a > 0 and
-b > 1. Print 1 if you succeed otherwise 0.
+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 1:
+Example:
-Input: 8
-Output: 1 as 8 = 2 ** 3
+[ _ _ _ 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 _ _ _ ]
-Example 2:
+Output:
-Input: 15
-Output: 0
+[ 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 ]
-Example 3:
+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 ]
+
+
+My notes: excuse me! a sudoku solver from scratch? that's hard!
-Input: 125
-Output: 1 as 125 = 5 ** 3
+fortunately I had already written a sudoku solver, which did clever
+deductions after working out the possible sets for each cell. But for
+this, I tried something else: I threw away all the clever deductions
+and wrote a brute force searcher instead:
+- read the puzzle (reused my existing code)
+- form the possible sets for each cell (ditto)
+- eliminate known cells from intersecting rows, columns and boxes (ditto)
+then solve by:
+- finding an unknown cell (r,c) with possible values @possval
+- for each @possval, clone the puzzle, set cell (r,c) to the chosen value and recurse,
+ checking for consistency at each stage
-My notes: clearly defined, not quite sure how to do it so.. let's brute force
+But sadly, I couldn't get it to work in the time I'd left myself (2.5 hours) - so
+no submission for that before midnight. After about 30 minutes more work, I got
+it working - there was a bug in the consistency checker.
diff --git a/challenge-086/duncan-c-white/perl/ch-1.pl b/challenge-086/duncan-c-white/perl/ch-1.pl
new file mode 100755
index 0000000000..9281cebb0a
--- /dev/null
+++ b/challenge-086/duncan-c-white/perl/ch-1.pl
@@ -0,0 +1,65 @@
+#!/usr/bin/perl
+#
+# Task 1: "Pair Difference
+#
+# You are given an array of integers @N and an integer $A.
+#
+# Write a script to find 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
+#
+# My notes: simple and straightforward: try all pairs of elements looking
+# for abs(x-y)==A
+#
+
+use strict;
+use warnings;
+use feature 'say';
+use Function::Parameters;
+use Data::Dumper;
+use Getopt::Long;
+
+my $debug = 0;
+die "Usage: pair-diff [--debug] DIFF array\n" unless
+ GetOptions( "debug" => \$debug ) &&
+ @ARGV>1;
+my( $diff, @x ) = @ARGV;
+
+#
+# my $found = finddiff( $diff, @x );
+# Attempt to find any two elements of @x with difference $diff;
+# return 1 iff such a pair of elements exists (0 otherwise).
+#
+fun finddiff( $diff, @x )
+{
+ foreach my $apos (0..$#x-1)
+ {
+ my $a = $x[$apos];
+ foreach my $bpos ($apos+1..$#x)
+ {
+ my $b = $x[$bpos];
+ next unless abs($a-$b) == $diff;
+ say "found $a and $b with diff $diff" if $debug;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+my $found = finddiff( $diff, @x );
+say $found;
diff --git a/challenge-086/duncan-c-white/perl/ch-2.pl b/challenge-086/duncan-c-white/perl/ch-2.pl
new file mode 100755
index 0000000000..eab723c310
--- /dev/null
+++ b/challenge-086/duncan-c-white/perl/ch-2.pl
@@ -0,0 +1,500 @@
+#!/usr/bin/perl
+#
+# Task 2: "Sudoku Puzzle
+#
+# 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 ]
+#
+# My notes: excuse me? write a sudoku solver in 2 and a half hours? no way!
+# fortunately I already had a solver, which did clever deductions after
+# working out the possible sets for each cell. But for this, I throw
+# away all the clever deductions and wrote a brute force searcher:
+# - read the puzzle
+# - form the possible sets
+# - eliminate known cells from intersecting rows, columns and boxes
+# then solve by:
+# - finding an unknown cell (r,c) with possible values @possval
+# - for each @possval, clone the puzzle, set cell (r,c) to the chosen value and recurse
+#
+
+use strict;
+use warnings;
+use Data::Dumper;
+use feature 'say';
+
+my $size = 9; # 9x9 puzzles always.. don't try changing this!
+
+
+#
+# my $puzref = readpuz( $fh );
+# read a puzzle from file handle $fh.
+# return a puzzle arrayref
+#
+sub readpuz ($)
+{
+ my( $infh ) = @_;
+
+ my @puz = ();
+ foreach (1..$size)
+ {
+ my $line = <$infh>;
+ chomp $line;
+ die "readpuz: line '$line' not $size chars long\n"
+ unless length($line)==$size;
+ die "readpuz: invalid line '$line'\n" unless
+ $line =~ /^(\d| ){$size}$/o;
+ my @x = split( //, $line );
+ map {
+ $_ = "123456789" if $_ eq " ";
+ } @x;
+ push( @puz, \@x );
+ }
+ #die "readpuz: puz = ". Dumper(\@puz);
+
+ return \@puz;
+}
+
+
+#
+# showpuz( $verbose, @puz );
+# show the puzzle prettily, verbosely if asked
+#
+sub showpuz ($@)
+{
+ my( $verbose, @puz ) = @_;
+ if( $verbose )
+ {
+ my @rowlet = ( "A".."I" ); # row letters
+ foreach my $row (0..$size-1)
+ {
+ my $line1 = my $line2 = " |";
+ if( $verbose )
+ {
+ $line2 = "$rowlet[$row]|";
+ }
+ foreach my $cell (@{$puz[$row]})
+ {
+ if( $verbose )
+ {
+ my $val = length($cell)==1 ? $cell : " "; # unknown if length()>1
+ my $p1 = " ";
+ my $p2 = " ";
+ if( length($cell)>1 )
+ {
+ my $p9 = substr( $cell." ", 0, 9 );
+ $p9 =~ /^(....)(.*)$/;
+ $p1 = $1;
+ $p2 = $2;
+ }
+ $line1 .= " $val $p1 |";
+ $line2 .= " $p2 |";
+ } else
+ {
+ my $val = length($cell)==1 ? $cell : " "; # unknown if length()>1
+ $line1 .= " $val |";
+ }
+ }
+ print "$line1\n";
+ next unless $verbose;
+ print "$line2\n";
+ print " ".("| " x $size);
+ print "|\n";
+ }
+ } else
+ {
+ foreach my $row (0..$size-1)
+ {
+ my $line1 = "";
+ foreach my $cell (@{$puz[$row]})
+ {
+ my $val = length($cell)==1 ? $cell : " "; # unknown if length()>1
+ $line1 .= $val;
+ }
+ print "$line1\n";
+ }
+ }
+}
+
+
+#
+# my $changed = eliminateoneknown( $row, $col, $val, @puz );
+# cell (row,col) is a known value $val - so modify the rest of the puzzle @puz
+# to reflect the things that we now know: that $val is not possible
+# in all cells in the same row, all cells in the same column
+# and all cells in the same box.
+# Return 1 iff the puzzle changes.
+#
+sub eliminateoneknown ($$$@)
+{
+ my( $row, $col, $val, @puz ) = @_;
+ die "eliminateoneknown: bad args ($row,$col,$val) - puz($row,$col) != $val\n"
+ unless $puz[$row][$col] eq $val;
+
+ my $changed = 0;
+
+ # $val is not possible in col $col
+ foreach my $r (0..$size-1)
+ {
+ next if $r == $row;
+ my $cell = $puz[$r][$col];
+ next if length($cell)==1;
+ if( $cell =~ /$val/ )
+ {
+ $cell =~ s/$val//;
+ $puz[$r][$col] = $cell;
+ $changed = 1;
+ }
+ }
+ # $val is not possible in row $row
+ foreach my $c (0..$size-1)
+ {
+ next if $c == $col;
+ my $cell = $puz[$row][$c];
+ next if length($cell)==1;
+ if( $cell =~ /$val/ )
+ {
+ $cell =~ s/$val//;
+ $puz[$row][$c] = $cell;
+ $changed = 1;
+ }
+ }
+ # $val is not possible anywhere in the same box as (row,col)
+ my $boxr1 = $row - $row%3;
+ my $boxr2 = $boxr1 + 2;
+ my $boxc1 = $col - $col%3;
+ my $boxc2 = $boxc1 + 2;
+ foreach my $c ($boxc1..$boxc2)
+ {
+ foreach my $r ($boxr1..$boxr2)
+ {
+ next if $r == $row && $c == $col;
+ my $cell = $puz[$r][$c];
+ next if length($cell)==1;
+ if( $cell =~ /$val/ )
+ {
+ $cell =~ s/$val//;
+ $puz[$r][$c] = $cell;
+ $changed = 1;
+ }
+ }
+ }
+ return $changed;
+}
+
+
+#
+# my $changed = eliminate( @puz );
+# locate all known cells, and eliminate each from
+# overlapping rows, columns and boxes.
+# Return 1 iff the puzzle changes.
+#
+sub eliminate
+{
+ my( @puz ) = @_;
+ my $changed = 0;
+ foreach my $row (0..$size-1)
+ {
+ foreach my $col (0..$size-1)
+ {
+ my $cell = $puz[$row][$col];
+ next unless length($cell)==1;
+ $changed ||= eliminateoneknown( $row, $col, $cell, @puz );
+ }
+ }
+ return $changed;
+}
+
+
+#
+# my $unknowns = countunknowns( @puz );
+# Count how many unknowns there are in @puz.
+#
+sub countunknowns
+{
+ my( @puz ) = @_;
+ my $unknowns = 0;
+ foreach my $row (0..$size-1)
+ {
+ foreach my $col (0..$size-1)
+ {
+ my $cell = $puz[$row][$col];
+ $unknowns++ if length($cell)>1;
+ }
+ }
+ return $unknowns;
+}
+
+
+#
+# my( $r, c ) = findfirstunknown(@puz);
+# Find the first unknown cell, returning it's row and column no.
+# If there are no unknown cells, return (0,0)
+#
+sub findfirstunknown
+{
+ my( @puz ) = @_;
+ foreach my $row (0..$size-1)
+ {
+ foreach my $col (0..$size-1)
+ {
+ my $cell = $puz[$row][$col];
+ return ($row,$col) if length($cell)>1;
+ }
+ }
+ return (0,0);
+}
+
+
+#
+# my $maxfreq = maxfreqinbox( $boxrn,$boxcn,@puz);
+# Check the contents of the box ($boxrn:0..2, $boxcn:0..2)
+# to find the maximum frequency of all known elements in there.
+#
+sub maxfreqinbox
+{
+ my( $boxrn, $boxcn, @puz ) = @_;
+
+ my $boxr1 = $boxrn*3;
+ my $boxr2 = $boxr1 + 2;
+ my $boxc1 = $boxcn*3;
+ my $boxc2 = $boxc1 + 2;
+ my %freq;
+ my $maxfreq = 0;
+ foreach my $c ($boxc1..$boxc2)
+ {
+ foreach my $r ($boxr1..$boxr2)
+ {
+ my $cell = $puz[$r][$c];
+ next unless length($cell)==1;
+ $freq{$cell}++;
+ $maxfreq = $freq{$cell} if $freq{$cell}>$maxfreq;
+ }
+ }
+ return $maxfreq;
+}
+
+
+#
+# my $isconsistent = consistent(@puz);
+# Return 1 iff the puzzle @puz is consistent so far.
+# Return 0 if there are any problems:
+# - a cell with 0 possibilities
+# - 2 cells in the same row/column/box with same value
+#
+sub consistent
+{
+ my( @puz ) = @_;
+ foreach my $row (0..$size-1)
+ {
+ foreach my $col (0..$size-1)
+ {
+ my $cell = $puz[$row][$col];
+ return 0 if length($cell)==0; # no possibilities
+ }
+ }
+
+ foreach my $row (0..$size-1)
+ {
+ # ok, find frequencies of all known values in this row
+ my %freq;
+ my $maxfreq=0;
+ foreach my $col (0..$size-1)
+ {
+ my $cell = $puz[$row][$col];
+ next unless length($cell)==1;
+ $freq{$cell}++;
+ $maxfreq = $freq{$cell} if $freq{$cell}>$maxfreq;
+ }
+
+ if( $maxfreq>1 )
+ {
+ say "not consistent due to max freq $maxfreq in row $row";
+ return 0;
+ }
+ }
+
+ foreach my $col (0..$size-1)
+ {
+ # ok, find frequencies of all known values in this column
+ my %freq;
+ my $maxfreq=0;
+ foreach my $row (0..$size-1)
+ {
+ my $cell = $puz[$row][$col];
+ next unless length($cell)==1;
+ $freq{$cell}++;
+ $maxfreq = $freq{$cell} if $freq{$cell}>$maxfreq;
+ }
+
+ if( $maxfreq>1 )
+ {
+ say "not consistent due to max freq $maxfreq in column $col";
+ return 0;
+ }
+ }
+
+
+ foreach my $boxrn (0..2)
+ {
+ foreach my $boxcn (0..2)
+ {
+ # count maxfreq of items in box ($boxrn,$boxcn)
+ my $maxfreq = maxfreqinbox( $boxrn,$boxcn,@puz);
+ if( $maxfreq>1 )
+ {
+ say "not consistent due to max freq $maxfreq in box $boxrn, $boxcn";
+ return 0;
+ }
+ }
+ }
+
+ # ok if we get to the end..
+ say "consistent";
+ return 1;
+}
+
+
+#
+# my @clone = clonepuz( @puz );
+# Clone the entire puzzle. Return the new identical puzzle.
+#
+sub clonepuz
+{
+ my( @puz ) = @_;
+ my @result = map { [ @$_ ] } @puz;
+ return @result;
+}
+
+
+#
+# my( $solved, @solvedpuz ) = solve( $pass, @puz );
+# Brute force solve @puz: locate the first unknown cell.
+# Then, foreach possible value of that cell, clone the puzzle,
+# set that cell to that value, and brute force again.
+# Fail whenever a cell becomes empty;
+# succeed whenever the puzzle has no unknowns.
+#
+sub solve
+{
+ my( $pass, @puz ) = @_;
+
+ my( $r, $c ) = findfirstunknown(@puz);
+ my $poss = $puz[$r][$c];
+ say "solve pass $pass: found unknown cell ($r,$c), possible values $poss";
+
+ my @poss = split(//,$poss);
+ foreach my $value (@poss)
+ {
+ my @clone = clonepuz( @puz );
+ say "solve pass $pass: try cell ($r,$c)=$value";
+ $clone[$r][$c] = $value;
+
+ # eliminate until no changes..
+ while( eliminate( @clone ) )
+ {
+ }
+
+ say "solve pass $pass: board is";
+ showpuz( 1, @clone );
+
+ my $isconsistent = consistent(@clone);
+ unless( $isconsistent )
+ {
+ say "solve pass $pass: not consistent";
+ next;
+ }
+
+ my $unknowns = countunknowns( @clone );
+ print "\nthere are $unknowns unknowns\n";
+ return (1, @clone) if $unknowns==0;
+
+ my( $solved, @solvedpuz ) = solve( $pass+1,@clone );
+ return (1, @solvedpuz) if $solved;
+ }
+ say "solve pass $pass: fail";
+ return (0);
+}
+
+
+die "Usage: bruteforcesudokusolver inputfile\n" unless @ARGV == 1;
+
+my $puzfile = shift;
+open( my $infh, '<', $puzfile ) || die;
+
+STDOUT->autoflush(1);
+STDERR->autoflush(1);
+STDIN->autoflush(1);
+
+#print "please enter your sudoku board:\n";
+my ( $puzref, @hint ) = readpuz($infh);
+$infh->close;
+my @puz = @$puzref;
+
+print "\n";
+
+print "\ninitially puzzle is:\n\n";
+showpuz( 1, @puz );
+
+# eliminate until no changes..
+while( eliminate( @puz ) )
+{
+}
+
+print "\nafter eliminations puzzle is:\n\n";
+showpuz( 1, @puz );
+
+my $unknowns = countunknowns( @puz );
+print "\nthere are $unknowns unknowns\n";
+
+my( $solved, @solvedpuz ) = solve( 1, @puz );
+
+unless( $solved )
+{
+ say "No solution found";
+} else
+{
+ print "\nafter brute force solving, puzzle is:\n\n";
+ showpuz( 0, @solvedpuz );
+}