#!/bin/perl use strict; use warnings; use Time::Stopwatch; tie my $timer, 'Time::Stopwatch'; #@ARGV = ( "1549.......8..426....8....9.....63....6.2..743421...5.4..6839.....7.5.1.6.7.9...." ); #@ARGV = ( "15.9.......8..426....8....9.....63....6.2..743421...5.4..6839.....7.5.1.6.7.9...." ); #@ARGV = ( ".........46.13.8...3..8.1.7.76...4.............5...71.7.3.6..4...4.21.35........." ); #@ARGV = ( "5......169....7.......38....79........3.2.9........54....97.......3....768......1" ); #@ARGV = ( "........1.....2.3...4..5..........5......46...17.8........1...7.2.9.....5.....4.." ); #@ARGV = ( "................................................................................." ); my(@board, @marks, @rowCnt, @colCnt, @squCnt); #---------------------------- initBoard() ------------------------------------# sub initBoard { if ( $#ARGV || (length($ARGV[0]) != 81) ) { printf("ERROR: invalid input (need %s)\n", $#ARGV ? "argument" : "81 characters"); exit(-1); } map { my $row=$_; map { $board[$row][$_] = 0 } ( 0..8 ) } ( 0..8 ); map { my $row=$_; map { my $col=$_; map { $marks[$row][$col][$_] = 0 } ( 1..9 ) } ( 0..8 ) } ( 0..8 ); map { my $idx=$_; map { $rowCnt[$idx][$_] = $colCnt[$idx][$_] = $squCnt[$idx][$_] = 9 } ( 1..9 ) } ( 0..8 ); for my $row ( 0..8 ) { for my $col ( 0..8 ) { my($val) = substr($ARGV[0], $row*9 + $col, 1); if ( ($val ne '.') && (existsInRow($row, $val) || existsInCol($col, $val) || existsInSqu($row, $col, $val)) ) { printf("ERROR: invalid input '$val' (row: %d, col: %d)\n", $row+1, $col+1); exit(-1); } setCell($row, $col, ($val eq '.') ? 0 : $val); } } } #---------------------------- nextCell() -------------------------------------# sub nextCell { my($row, $col) = @_; $row++ unless ( ($col = ($col + 1) % 9) ); return($row, $col); } #---------------------------- existsInRow() ----------------------------------# sub existsInRow { my($row, $val) = @_; map { return(1) if ( $board[$row][$_] == $val ) } ( 0..8 ); return(0); } #---------------------------- existsInCol() ----------------------------------# sub existsInCol { my($col, $val) = @_; map { return(1) if ( $board[$_][$col] == $val ) } ( 0..8 ); return(0); } #---------------------------- existsInSqu() ----------------------------------# sub existsInSqu { my($row, $col, $val) = ( 3 * int($_[0] / 3), 3 * int($_[1] / 3), $_[2] ); return(($val == $board[$row] [$col]) || ($val == $board[$row] [$col+1]) || ($val == $board[$row] [$col+2]) || ($val == $board[$row+1][$col]) || ($val == $board[$row+1][$col+1]) || ($val == $board[$row+1][$col+2]) || ($val == $board[$row+2][$col]) || ($val == $board[$row+2][$col+1]) || ($val == $board[$row+2][$col+2])); } #---------------------------- markCell() -------------------------------------# sub markCell { my($row, $col, $val) = @_; my($squIdx) = 3*int($row/3)+int($col/3); if ( $val ) { for my $tmpVal ( 1..9 ) { if ( ! $marks[$row][$col][$tmpVal] ) { $rowCnt[$row][$tmpVal]--; $colCnt[$col][$tmpVal]--; $squCnt[$squIdx][$tmpVal]--; $marks[$row][$col][$tmpVal] = 1; } } } } #---------------------------- markRow() --------------------------------------# sub markRow { my($row, $col, $val) = @_; my($squRow) = 3*int($row/3); my($cnt) = 0; if ( $val ) { for my $tmpCol ( 0..8 ) { my($squIdx) = $squRow+int($tmpCol/3); if ( ! $marks[$row][$tmpCol][$val] ) { $rowCnt[$row][$val]--; $colCnt[$tmpCol][$val]--; $squCnt[$squIdx][$val]--; $cnt++; $marks[$row][$tmpCol][$val] = 1; } } } return($cnt); } #---------------------------- markRowExceptTwo() -----------------------------# sub markRowExceptTwo { my($row, $col1, $col2, $val) = @_; my($squRow) = 3*int($row/3); my($cnt) = 0; if ( $val ) { for my $tmpCol ( 0..8 ) { next if ( ($tmpCol == $col1) || ($tmpCol == $col2) ); my($squIdx) = $squRow+int($tmpCol/3); if ( ! $marks[$row][$tmpCol][$val] ) { $rowCnt[$row][$val]--; $colCnt[$tmpCol][$val]--; $squCnt[$squIdx][$val]--; $cnt++; $marks[$row][$tmpCol][$val] = 1; } } } return($cnt); } #---------------------------- markCol() --------------------------------------# sub markCol { my($row, $col, $val) = @_; my($squCol) = int($col/3); my($cnt) = 0; if ( $val ) { for my $tmpRow ( 0..8 ) { my($squIdx) = 3*int($tmpRow/3)+$squCol; if ( ! $marks[$tmpRow][$col][$val] ) { $rowCnt[$tmpRow][$val]--; $colCnt[$col][$val]--; $squCnt[$squIdx][$val]--; $cnt++; $marks[$tmpRow][$col][$val] = 1; } } } return($cnt); } #---------------------------- markColExceptTwo() -----------------------------# sub markColExceptTwo { my($col, $row1, $row2, $val) = @_; my($squCol) = int($col/3); my($cnt) = 0; if ( $val ) { for my $tmpRow ( 0..8 ) { next if ( ($tmpRow == $row1) || ($tmpRow == $row2) ); my($squIdx) = 3*int($tmpRow/3)+$squCol; if ( ! $marks[$tmpRow][$col][$val] ) { $rowCnt[$tmpRow][$val]--; $colCnt[$col][$val]--; $squCnt[$squIdx][$val]--; $cnt++; $marks[$tmpRow][$col][$val] = 1; } } } return($cnt); } #---------------------------- markSqu() --------------------------------------# sub markSqu { my($row, $col, $val) = @_; my($snapRow, $snapCol) = ( 3*int($row/3) , 3*int($col/3) ); my($cnt) = 0; if ( $ val ) { for my $subRow ( 0..2 ) { for my $subCol ( 0..2 ) { my($tmpRow, $tmpCol) = ( $snapRow+$subRow, $snapCol+$subCol); my($squIdx) = 3*int($tmpRow/3)+int($tmpCol/3); if ( ! $marks[$tmpRow][$tmpCol][$val] ) { $rowCnt[$tmpRow][$val]--; $colCnt[$tmpCol][$val]--; $squCnt[$squIdx][$val]--; $cnt++; $marks[$tmpRow][$tmpCol][$val] = 1; } } } } return($cnt); } #---------------------------- setCell() --------------------------------------# sub setCell { my($row, $col, $val) = @_; if ( ! $board[$row][$col] ) { markCell($row, $col, $val); markRow($row, $col, $val); markCol($row, $col, $val); markSqu($row, $col, $val); $board[$row][$col] = $val; return(1); } return(0); } #---------------------------- findSinglesInRow() -----------------------------# sub findSinglesInRow { my($gotSingles) = 0; for my $val ( 1..9 ) { for my $row ( 0..8 ) { next if ( $rowCnt[$row][$val] != 1 ); for my $col ( 0..8 ) { if ( ! $marks[$row][$col][$val] ) { $gotSingles++; setCell($row, $col, $val); last; } } } } return($gotSingles); } #---------------------------- findSinglesInCol() -----------------------------# sub findSinglesInCol { my($gotSingles) = 0; for my $val ( 1..9 ) { for my $col ( 0..8 ) { next if ( $colCnt[$col][$val] != 1 ); for my $row ( 0..8 ) { if ( ! $marks[$row][$col][$val] ) { $gotSingles++; setCell($row, $col, $val); last; } } } } return($gotSingles); } #---------------------------- findSinglesInSqu() -----------------------------# sub findSinglesInSqu { my($gotSingles) = 0; my($hitRow, $hitCol); for my $val ( 1..9 ) { for my $squIdx ( 0..8 ) { my($row, $col) = ( 3*int($squIdx/3), 3*($squIdx%3) ); my($cnt) = 0; for my $subRow ( 0..2 ) { for my $subCol ( 0..2 ) { my($tmpRow, $tmpCol) = ( $row+$subRow, $col+$subCol ); if ( ! $marks[$tmpRow][$tmpCol][$val] ) { $hitRow = $tmpRow; $hitCol = $tmpCol; $cnt++; } } } if ( $cnt == 1 ) { $gotSingles++; setCell($hitRow, $hitCol, $val); } } } return($gotSingles); } #---------------------------- getZeroSubCoords() -----------------------------# sub getZeroSubCoords { my($snapRow, $snapCol, $val, $n) = @_; my(@rows, @cols); for my $subRow ( 0..2 ) { for my $subCol ( 0..2 ) { my($row, $col) = ( $snapRow+$subRow, $snapCol+$subCol ); if ( ! $marks[$row][$col][$val] ) { push(@rows, $row); push(@cols, $col); return(@rows, @cols) if ( @rows == $n ); } } } } #---------------------------- matchDoublePairs() -----------------------------# sub matchDoublePairs { my($snapRowA, $snapColA, $squIdxB, $val) = @_; my($snapRowB, $snapColB) = ( 3*int($squIdxB/3), 3*($squIdxB%3) ); my(@tmpRowB, @tmpColB, @coordsA, @coordsB); my($gotMatch) = 0; return(0) if ( ($snapRowA != $snapRowB) && ($snapColA != $snapColB) ); @coordsA = getZeroSubCoords($snapRowA, $snapColA, $val, 2); @coordsB = getZeroSubCoords($snapRowB, $snapColB, $val, 2); if ( $snapRowA == $snapRowB ) { if ( ($coordsA[0] == $coordsB[0]) && ($coordsA[1] == $coordsB[1]) && ($coordsA[2] == $coordsA[3]) && ($coordsB[2] == $coordsB[3]) ) { $gotMatch++; } } else { if ( ($coordsA[0] == $coordsA[1]) && ($coordsB[0] == $coordsB[1]) && ($coordsA[2] == $coordsB[2]) && ($coordsA[3] == $coordsB[3]) ) { $gotMatch++; } } if ( $gotMatch ) { $gotMatch = 0; $gotMatch += markRowExceptTwo($coordsA[0], $coordsA[2], $coordsB[3], $val); $gotMatch += markRowExceptTwo($coordsB[1], $coordsB[3], $coordsA[2], $val); $gotMatch += markColExceptTwo($coordsA[2], $coordsA[0], $coordsB[1], $val); $gotMatch += markColExceptTwo($coordsB[3], $coordsB[1], $coordsA[0], $val); } return($gotMatch); } #---------------------------- findDoublePairs() ------------------------------# sub findDoublePairs { my($gotMatch) = 0; for my $val ( 1..9 ) { for my $squIdxA ( 0..7 ) { next if ( $squCnt[$squIdxA][$val] != 2 ); my($snapRowA, $snapColA) = ( 3*int($squIdxA/3), 3*($squIdxA%3) ); for my $squIdxB ( ($squIdxA+1)..8 ) { next if ( $squCnt[$squIdxB][$val] != 2 ); $gotMatch += matchDoublePairs($snapRowA, $snapColA, $squIdxB, $val); } } } return($gotMatch); } #---------------------------- excludeRow() -----------------------------------# sub excludeRow { my($squIdx, $val, $subRow) = @_; my($row) = 3*int($squIdx/3) + $subRow; my($snapCol) = 3*($squIdx%3); my($gotMatch) = 0; for my $col ( 0..8 ) { next if ( ($col >= $snapCol) && ($col < ($snapCol + 3)) ); my($tmpSquIdx) = 3*int($row/3)+int($col/3); if ( ! $marks[$row][$col][$val] ) { $squCnt[$tmpSquIdx][$val]--; $gotMatch++; } $marks[$row][$col][$val] = 1; } return($gotMatch); } #---------------------------- excludeCol() -----------------------------------# sub excludeCol { my($squIdx, $val, $subCol) = @_; my($col) = 3*($squIdx%3) + $subCol; my($snapRow) = 3*int($squIdx/3); my($gotMatch) = 0; for my $row ( 0..8 ) { next if ( ($row >= $snapRow) && ($row < ($snapRow + 3)) ); my($tmpSquIdx) = 3*int($row/3)+int($col/3); if ( ! $marks[$row][$col][$val] ) { $squCnt[$tmpSquIdx][$val]--; $gotMatch++; } $marks[$row][$col][$val] = 1; } return($gotMatch); } #---------------------------- findStripeInSquare() ---------------------------# sub findStripeInSquare { my($squIdx, $val) = @_; my($snapRow, $snapCol) = ( 3*int($squIdx/3), 3*($squIdx%3) ); my($gotMatch) = 0; #--- SEARCH FOR COLUMNS ! --------------------------------------------------# for my $subCol ( 0..2 ) { my($cnt) = 0; for my $subRow ( 0..2 ) { $cnt++ if ( ! $marks[$snapRow+$subRow][$snapCol+$subCol][$val] ); } if ( $cnt && ($cnt == $squCnt[$squIdx][$val]) ) { $gotMatch += excludeCol($squIdx, $val, $subCol); } } #--- SEARCH FOR ROWS ! -----------------------------------------------------# for my $subRow ( 0..2 ) { my($cnt) = 0; for my $subCol ( 0..2 ) { $cnt++ if ( ! $marks[$snapRow+$subRow][$snapCol+$subCol][$val] ); } if ( $cnt && ($cnt == $squCnt[$squIdx][$val]) ) { $gotMatch += excludeRow($squIdx, $val, $subRow); } } return($gotMatch); } #---------------------------- findIsolatedStripes() --------------------------# sub findIsolatedStripes { my($gotMatch) = 0; for my $val ( 1..9 ) { for my $squIdx ( 0..8 ) { my($cnt) = $squCnt[$squIdx][$val]; next if ( ! $cnt || ($cnt > 3) ); $gotMatch += findStripeInSquare($squIdx, $val); } } return($gotMatch); } #---------------------------- findLastRemaining() ----------------------------# sub findLastRemaining { my($theVal, %vals); my($gotMatch) = 0; for my $row ( 0..8 ) { for my $col ( 0..8 ) { map { $vals{$_} = 1 } ( 1..9 ); for my $val ( 1..9 ) { if ( $marks[$row][$col][$val] ) { delete($vals{$val}) if ( $marks[$row][$col][$val] ); } else { $theVal = $val; } } if ( scalar(keys(%vals)) == 1 ) { setCell($row, $col, $theVal); $gotMatch++; } } } return($gotMatch); } #---------------------------- showBoard() ------------------------------------# sub showBoard { my($title) = @_; printf("------------------------ BOARD $title -------------------------\n"); printf("TIME: %s sec\n", $timer); $timer = 0; for my $row ( 0..8 ) { printf("------+-------+------\n") if ( $row && ! ($row % 3) ); printf("%s %s %s | %s %s %s | %s %s %s\n", @{$board[$row]}); } } ############################################################################### initBoard(); showBoard("initial"); my($gotMatch, $cnt); do { $gotMatch = 0; #----- FIND SINGLES WITHIN ROWS, COLS, SUQARES ! ---------------------------# while ( $cnt = findSinglesInRow() + findSinglesInCol() + findSinglesInSqu() ) { $gotMatch += $cnt } #----- FIND CORRESPONDING DOUBLE PAIRES IN DIFFERENT SQUARES ! -------------# while ( $cnt = findDoublePairs() ) { $gotMatch += $cnt } #----- FIND ROWS OF 2 OR 3 WITHIN A SQUARE AS THE ONLY HOLES ! -------------# while ( $cnt = findIsolatedStripes() ) { $gotMatch += $cnt } #----- FIND LAST NUMBER REMAINING FOR A CERTAIN CELL ! ---------------------# while ( $cnt = findLastRemaining() ) { $gotMatch += $cnt } } while ( $gotMatch ); showBoard("solved");