Add tasks for all the new languages

This commit is contained in:
Tina Müller 2016-12-05 23:44:36 +01:00
parent 9dc3c2bb62
commit bba7bfd280
13208 changed files with 134745 additions and 0 deletions

View file

@ -0,0 +1,404 @@
!--------------------------------------------------------------------
! risolve Sudoku: in input il file SUDOKU.TXT
! Metodo seguito : cancellazioni successive e quando non possibile
! ricerca combinatoria sulle celle con due valori
! possibili - max. 30 livelli di ricorsione
! Non risolve se,dopo l'analisi per la cancellazione,
! restano solo celle a 4 valori
!--------------------------------------------------------------------
PROGRAM SUDOKU
LABEL 76,77,88,91,97,99
DIM TAV$[9,9] ! 81 caselle in nove quadranti
! cella non definita --> 0/. nel file SUDOKU.TXT
! diventa 123456789 dopo LEGGI_SCHEMA
!---------------------------------------------------------------------------
! tabelle per gestire la ricerca combinatoria
! (primo indice--> livelli ricorsione)
!---------------------------------------------------------------------------
DIM TAV2$[30,9,9],INFO[30,4]
!$INCLUDE="PC.LIB"
PROCEDURE MESSAGGI(MEX%)
CASE MEX% OF
1-> LOCATE(21,1) PRINT("Cancellazione successiva - liv. 1") END ->
2-> LOCATE(21,1) PRINT("Cancellazione successiva - liv. 2") END ->
3-> LOCATE(22,1) PRINT("Ricerca combinatoria - liv.";LIVELLO;" ") END ->
END CASE
END PROCEDURE
PROCEDURE VISUALIZZA_SCHEMA
LOCATE(1,1)
PRINT("+---+---+---+---+---+---+---+---+----+")
FOR I=1 TO 9 DO
FOR J=1 TO 9 DO
PRINT("|";)
IF LEN(TAV$[I,J])=1 THEN
PRINT(" ";TAV$[I,J];" ";)
ELSE
PRINT(" ";)
END IF
END FOR
PRINT("³")
IF I<>9 THEN PRINT("+---+---+---+---+---+---+---+---+----+") END IF
END FOR
PRINT("+---+---+---+---+---+---+---+---+----+")
END PROCEDURE
!------------------------------------------------------------------------
! in input la cella (riga,colonna)
! in output se ha un valore definito
!------------------------------------------------------------------------
PROCEDURE VALORE_DEFINITO
FLAG%=FALSE
IF LEN(TAV$[RIGA,COLONNA])=1 THEN FLAG%=TRUE END IF
END PROCEDURE
PROCEDURE SALVA_CONFIG
LIVELLO=LIVELLO+1
FOR R=1 TO 9 DO
FOR S=1 TO 9 DO
TAV2$[LIVELLO,R,S]=TAV$[R,S]
END FOR
END FOR
INFO[LIVELLO,0]=1 INFO[LIVELLO,1]=RIGA INFO[LIVELLO,2]=COLONNA
INFO[LIVELLO,3]=SECOND INFO[LIVELLO,4]=THIRD
END PROCEDURE
PROCEDURE RIPRISTINA_CONFIG
91:
LIVELLO=LIVELLO-1
IF INFO[LIVELLO,0]=3 THEN GOTO 91 END IF
FOR R=1 TO 9 DO
FOR S=1 TO 9 DO
TAV$[R,S]=TAV2$[LIVELLO,R,S]
END FOR
END FOR
RIGA=INFO[LIVELLO,1] COLONNA=INFO[LIVELLO,2]
SECOND=INFO[LIVELLO,3] THIRD=INFO[LIVELLO,4]
IF INFO[LIVELLO,0]=1 THEN
TAV$[RIGA,COLONNA]=MID$(STR$(SECOND),2)
END IF
IF INFO[LIVELLO,0]=2 THEN
IF THIRD<>0 THEN
TAV$[RIGA,COLONNA]=MID$(STR$(THIRD),2)
ELSE
GOTO 91
END IF
END IF
INFO[LIVELLO,0]=INFO[LIVELLO,0]+1
VISUALIZZA_SCHEMA
END PROCEDURE
PROCEDURE VERIFICA_SE_FINITO
COMPLETO%=TRUE
FOR RIGA=1 TO 9 DO
PRD#=1
FOR COLONNA=1 TO 9 DO
PRD#=PRD#*VAL(TAV$[RIGA,COLONNA])
END FOR
IF PRD#<>362880 THEN COMPLETO%=FALSE EXIT END IF
END FOR
IF NOT COMPLETO% THEN EXIT PROCEDURE END IF
FOR COLONNA=1 TO 9 DO
PRD#=1
FOR RIGA=1 TO 9 DO
PRD#=PRD#*VAL(TAV$[RIGA,COLONNA])
END FOR
IF PRD#<>362880 THEN COMPLETO%=FALSE EXIT END IF
END FOR
END PROCEDURE
!-------------------------------------------------------------------
! toglie i valore certi dalle celle sulla
! stessa riga-stessa colonna-stesso quadrante
!-------------------------------------------------------------------
PROCEDURE TOGLI_VALORE
!iniziamo a togliere il valore dalla stessa riga ....
FOR J=1 TO 9 DO
CH$=TAV$[RIGA,J] CH=VAL(Z$)
IF LEN(CH$)<>1 THEN
CHANGE(CH$,CH,"-"->CH$)
TAV$[RIGA,J]=CH$
END IF
END FOR
!... iniziamo a togliere il valore dalla stessa colonna ...
FOR I=1 TO 9 DO
CH$=TAV$[I,COLONNA] CH=VAL(Z$)
IF LEN(CH$)<>1 THEN
CHANGE(CH$,CH,"-"->CH$)
TAV$[I,COLONNA]=CH$
END IF
END FOR
!... iniziamo a togliere il valore dallo stesso quadrante
R=INT(RIGA/3.1)*3+1
S=INT(COLONNA/3.1)*3+1
FOR I=R TO R+2 DO
FOR J=S TO S+2 DO
CH$=TAV$[I,J] CH=VAL(Z$)
IF LEN(CH$)<>1 THEN
CHANGE(CH$,CH,"-"->CH$)
TAV$[I,J]=CH$
END IF
END FOR
END FOR
MESSAGGI(1)
END PROCEDURE
PROCEDURE ESAMINA_SCHEMA
FOR RIGA=1 TO 9 DO
FOR COLONNA=1 TO 9 DO
VALORE_DEFINITO
IF FLAG% THEN
Z$=TAV$[RIGA,COLONNA]
TOGLI_VALORE
END IF
END FOR
END FOR
END PROCEDURE
PROCEDURE IDENTIFICA_UNICO
FOR KL=1 TO 9 DO
KL$=MID$(STR$(KL),2)
NN=0
FOR H=1 TO LEN(ZZ$) DO
IF MID$(ZZ$,H,1)=KL$ THEN NN=NN+1 END IF
END FOR
IF NN=1 THEN Q=INSTR(ZZ$,KL$) KL=9 END IF
END FOR
END PROCEDURE
!----------------------------------------------------------------------------
! intercetta i valori unici per le celle ancora non definite
!----------------------------------------------------------------------------
PROCEDURE TOGLI_VALORE2
MESSAGGI(2)
! iniziamo dalle righe ....
OK%=FALSE
FOR RIGA=1 TO 9 DO
ZZ$=""
FOR COLONNA=1 TO 9 DO
IF LEN(TAV$[RIGA,COLONNA])<>1 THEN
ZZ$=ZZ$+TAV$[RIGA,COLONNA]
ELSE
ZZ$=ZZ$+STRING$(9," ")
END IF
END FOR
Q=0 IDENTIFICA_UNICO
IF Q<>0 THEN
COLONNA=INT(Q/9.1)+1
TAV$[RIGA,COLONNA]=KL$
OK%=TRUE EXIT
END IF
END FOR
IF OK% THEN GOTO 76 END IF
! .... poi dalle colonne ....
FOR COLONNA=1 TO 9 DO
ZZ$=""
FOR RIGA=1 TO 9 DO
IF LEN(TAV$[RIGA,COLONNA])<>1 THEN
ZZ$=ZZ$+TAV$[RIGA,COLONNA]
ELSE
ZZ$=ZZ$+STRING$(9," ")
END IF
END FOR
Q=0 IDENTIFICA_UNICO
IF Q<>0 THEN
RIGA=INT(Q/9.1)+1
TAV$[RIGA,COLONNA]=KL$ OK%=TRUE EXIT
END IF
END FOR
IF OK% THEN GOTO 76 END IF
!.... e infine i quadranti
FOR QUADRANTE=1 TO 9 DO
ZZ$=""
CASE QUADRANTE OF
1-> R=1 S=1 END ->
2-> R=1 S=4 END ->
3-> R=1 S=7 END ->
4-> R=4 S=1 END ->
5-> R=4 S=4 END ->
6-> R=4 S=7 END ->
7-> R=7 S=1 END ->
8-> R=7 S=4 END ->
9-> R=7 S=7 END ->
END CASE
FOR RIGA=R TO R+2 DO
FOR COLONNA=S TO S+2 DO
IF LEN(TAV$[RIGA,COLONNA])<>1 THEN
ZZ$=ZZ$+TAV$[RIGA,COLONNA]
ELSE
ZZ$=ZZ$+STRING$(9," ")
END IF
END FOR
END FOR
Q=0 IDENTIFICA_UNICO
IF Q<>0 THEN
CASE Q OF
1..9-> ALFA=R BETA=S END ->
10..18-> ALFA=R BETA=S+1 END ->
19..27-> ALFA=R BETA=S+2 END ->
28..36-> ALFA=R+1 BETA=S END ->
37..45-> ALFA=R+1 BETA=S+1 END ->
46..54-> ALFA=R+1 BETA=S+2 END ->
55..63-> ALFA=R+2 BETA=S END ->
64..72-> ALFA=R+2 BETA=S+1 END ->
OTHERWISE
ALFA=R+2 BETA=S+2
END CASE
77:
TAV$[ALFA,BETA]=KL$ EXIT
END IF
END FOR
76:
MESSAGGI(2)
END PROCEDURE
PROCEDURE CONVERTI_VALORE
FINE%=TRUE NESSUNO%=TRUE
FOR RIGA=1 TO 9 DO
FOR COLONNA=1 TO 9 DO
CH$=TAV$[RIGA,COLONNA]
IF LEN(CH$)<>1 THEN
FINE%=FALSE ! flag per fine partita -- trovati tutti
Q=0 ! conta i '-' nella stringa se ce ne sono 8,
! trovato valore
FOR Z=1 TO LEN(CH$) DO
IF MID$(CH$,Z,1)="-" THEN Q=Q+1 ELSE LAST=Z END IF
END FOR
IF Q=8 THEN
CH$=MID$(STR$(LAST),2)
TAV$[RIGA,COLONNA]=CH$
NESSUNO%=FALSE
END IF
END IF
END FOR
END FOR
END PROCEDURE
PROCEDURE LEGGI_SCHEMA
OPEN("I",1,"sudoku.txt")
FOR I=1 TO 9 DO
INPUT(LINE,#1,RIGA$)
FOR J=1 TO 9 DO
CH$=MID$(RIGA$,J,1)
IF CH$="0" OR CH$="." THEN
TAV$[I,J]="123456789"
ELSE
TAV$[I,J]=CH$
END IF
END FOR
END FOR
CLOSE(1)
END PROCEDURE
!---------------------------------------------------------------------------
! Praticamente - visita di un albero binario (caso con cella a 2 valori
! possibili)
!---------------------------------------------------------------------------
PROCEDURE RICERCA_COMBINATORIA
TRE%=TRUE
FOR RIGA=1 TO 9 DO
FOR COLONNA=1 TO 9 DO
CH$=TAV$[RIGA,COLONNA]
IF LEN(CH$)<>1 THEN
Q=0 FIRST=0 SECOND=0 THIRD=0
FOR Z=1 TO LEN(CH$) DO
IF MID$(CH$,Z,1)="-" THEN
Q=Q+1
ELSE
IF FIRST=0 THEN
FIRST=Z
ELSE
SECOND=Z
END IF
END IF
END FOR
IF Q=7 THEN
SALVA_CONFIG
TAV$[RIGA,COLONNA]=MID$(STR$(FIRST),2)
TRE%=FALSE
GOTO 97
END IF
END IF
END FOR
END FOR
IF TRE% THEN GOTO 88 END IF
97:
MESSAGGI(3)
EXIT PROCEDURE
88:
QUATTRO%=TRUE
FOR RIGA=1 TO 9 DO
FOR COLONNA=1 TO 9 DO
CH$=TAV$[RIGA,COLONNA]
IF LEN(CH$)<>1 THEN
Q=0 FIRST=0 SECOND=0 THIRD=0
FOR Z=1 TO LEN(CH$) DO
IF MID$(CH$,Z,1)="-" THEN
Q=Q+1
ELSE
IF FIRST=0 THEN
FIRST=Z
ELSE
IF SECOND=0 THEN
SECOND=Z
ELSE
THIRD=Z
END IF
END IF
END IF
END FOR
IF Q=6 THEN
SALVA_CONFIG
TAV$[RIGA,COLONNA]=MID$(STR$(FIRST),2)
QUATTRO%=FALSE
GOTO 97
END IF
END IF
END FOR
END FOR
IF QUATTRO% THEN
LIVELLO=LIVELLO+1
RIPRISTINA_CONFIG
GOTO 97
END IF
! se restano solo celle con 4 valori,forza la chiusura del ramo dell'albero
!$RCODE="STOP"
END PROCEDURE
BEGIN
CLS
LIVELLO=1 NZ%=0
LEGGI_SCHEMA
WHILE TRUE DO
VISUALIZZA_SCHEMA
99:
NZ%=NZ%+1
ESAMINA_SCHEMA
CONVERTI_VALORE
EXIT IF FINE%
IF NESSUNO% THEN
TOGLI_VALORE2
IF OK%=0 THEN
RICERCA_COMBINATORIA ! cerca altri celle da assegnare
END IF
END IF
END WHILE
VISUALIZZA_SCHEMA
VERIFICA_SE_FINITO
IF NOT COMPLETO% THEN
LIVELLO=LIVELLO+1
RIPRISTINA_CONFIG
GOTO 99
END IF
END PROGRAM

View file

@ -0,0 +1,108 @@
include "ConsoleWindow"
include "NSLog.incl"
include "Util_Containers.incl"
begin globals
dim as container gC
end globals
BeginCDeclaration
short solve_sudoku(short i);
short check_sudoku(short r, short c);
CFMutableStringRef print_sudoku();
EndC
BeginCFunction
short sudoku[9][9] = {
{3,0,0,0,0,1,4,0,9},
{7,0,0,0,0,4,2,0,0},
{0,5,0,2,0,0,0,1,0},
{5,7,0,0,4,3,0,6,0},
{0,9,0,0,0,0,0,3,0},
{0,6,0,7,9,0,0,8,5},
{0,8,0,0,0,5,0,4,0},
{0,0,6,4,0,0,0,0,7},
{9,0,5,6,0,0,0,0,3},
};
short check_sudoku( short r, short c )
{
short i;
short rr, cc;
for (i = 0; i < 9; i++)
{
if (i != c && sudoku[r][i] == sudoku[r][c]) return 0;
if (i != r && sudoku[i][c] == sudoku[r][c]) return 0;
rr = r/3 * 3 + i/3;
cc = c/3 * 3 + i%3;
if ((rr != r || cc != c) && sudoku[rr][cc] == sudoku[r][c]) return 0;
}
return -1;
}
short solve_sudoku( short i )
{
short r, c;
if (i < 0) return 0;
else if (i >= 81) return -1;
r = i / 9;
c = i % 9;
if (sudoku[r][c])
return check_sudoku(r, c) && solve_sudoku(i + 1);
else
for (sudoku[r][c] = 9; sudoku[r][c] > 0; sudoku[r][c]--)
{
if ( solve_sudoku(i) ) return -1;
}
return 0;
}
CFMutableStringRef print_sudoku()
{
short i, j;
CFMutableStringRef mutStr;
mutStr = CFStringCreateMutable( kCFAllocatorDefault, 0 );
for (i = 0; i < 9; i++)
{
for (j = 0; j < 9; j++)
{
CFStringAppendFormat( mutStr, NULL, (CFStringRef)@" %d", sudoku[i][j] );
}
CFStringAppendFormat( mutStr, NULL, (CFStringRef)@"\r" );
}
return( mutStr );
}
EndC
toolbox fn solve_sudoku( short i ) = short
toolbox fn check_sudoku( short r, short c ) = short
toolbox fn print_sudoku() = CFMutableStringRef
dim as short solution
dim as CFMutableStringRef cfRef
gC = " "
cfRef = fn print_sudoku()
fn ContainerCreateWithCFString( cfRef, gC )
print : print "Sudoku challenge:" : print : print gC
solution = fn solve_sudoku(0)
print : print "Sudoku solved:" : print
if ( solution )
gC = " "
cfRef = fn print_sudoku()
fn ContainerCreateWithCFString( cfRef, gC )
print gC
else
print "No solution found"
end if

View file

@ -0,0 +1,50 @@
sequence board = split("""
.......39
.....1..5
..3.5.8..
..8.9...6
.7...2...
1..4.....
..9.8..5.
.2....6..
4..7.....""",'\n')
function valid_move(integer y, integer x, integer ch)
for i=1 to 9 do
if ch=board[i][x] then return 0 end if
if ch=board[y][i] then return 0 end if
end for
y -= mod(y-1,3)
x -= mod(x-1,3)
for ys=y to y+2 do
for xs=x to x+2 do
if ch=board[ys][xs] then return 0 end if
end for
end for
return 1
end function
sequence solution = {}
procedure brute_solve()
for y=1 to 9 do
for x=1 to 9 do
if board[y][x]<='0' then
for ch='1' to '9' do
if valid_move(y,x,ch) then
board[y][x] = ch
brute_solve()
board[y][x] = ' '
if length(solution) then return end if
end if
end for
return
end if
end for
end for
solution = board -- (already solved case)
end procedure
atom t0 = time()
brute_solve()
printf(1,"%s\n(solved in %3.2fs)\n",{join(solution,"\n"),time()-t0})

View file

@ -0,0 +1,658 @@
-- Working directly on 81-character strings ultimately proves easier: Originally I
-- just wanted to simplify the final display, but later I realised that a 9x9 grid
-- encourages laborious indexing/looping everwhere whereas using a flat 81-element
-- approach encourages precomputation of index sets, and once you commit to that,
-- the rest of the code starts to get a whole lot cleaner. Below we create 27+18
-- sets and 5 tables of lookup indexes to locate them quickly.
sequence nines = {}, -- will be 27 in total
cols = repeat(0,9*9), -- remainder(i-1,9)+1
rows = repeat(0,9*9), -- floor((i-1)/9)+10
squares = repeat(0,9*9),
sixes = {}, -- will be 18 in total
dotcol = repeat(0,9*9), -- same col, diff square
dotrow = repeat(0,9*9) -- same row, diff square
procedure set_nines()
sequence nine, six
integer idx, ndx
for x=0 to 8 do -- columns
nine = {}
ndx = length(nines)+1
for y=1 to 81 by 9 do
idx = y+x
nine = append(nine,idx)
cols[idx] = ndx
end for
nines = append(nines,nine)
end for
for y=1 to 81 by 9 do -- rows
nine = {}
ndx = length(nines)+1
for x=0 to 8 do
idx = y+x
nine = append(nine,idx)
rows[idx] = ndx
end for
nines = append(nines,nine)
end for
if length(nines)!=18 then ?9/0 end if
for y=0 to 8 by 3 do -- small squares [19..27]
for x=0 to 8 by 3 do
nine = {}
ndx = length(nines)+1
for sy=y*9 to y*9+18 by 9 do
for sx=x to x+2 do
idx = sy+sx+1
nine = append(nine,idx)
squares[idx] = ndx
end for
end for
nines = append(nines,nine)
end for
end for
if length(nines)!=27 then ?9/0 end if
for i=1 to 9*9 do
six = {}
nine = nines[cols[i]] -- dotcol
for j=1 to length(nine) do
if squares[i]!=squares[nine[j]] then
six = append(six,nine[j])
end if
end for
ndx = find(six,sixes)
if ndx=0 then
sixes = append(sixes,six)
ndx = length(sixes)
end if
dotcol[i] = ndx
six = {}
nine = nines[rows[i]] -- dotrow
for j=1 to length(nine) do
if squares[i]!=squares[nine[j]] then
six = append(six,nine[j])
end if
end for
ndx = find(six,sixes)
if ndx=0 then
sixes = append(sixes,six)
ndx = length(sixes)
end if
dotrow[i] = ndx
end for
end procedure
set_nines()
integer improved = 0
function eliminate_in(sequence valid, sequence set, integer ch)
for i=1 to length(set) do
integer idx = set[i]
if string(valid[idx]) then
integer k = find(ch,valid[idx])
if k!=0 then
valid[idx][k..k] = ""
improved = 1
end if
end if
end for
return valid
end function
function test_comb(sequence chosen, sequence pool, sequence valid)
--
-- (see deep_logic()/set elimination)
-- chosen is a sequence of length 2..4 of integers 1..9: ordered elements of pool.
-- pool is a set of elements of the sequence valid, each of which is a sequence.
-- (note that elements of valid in pool not in chosen are not necessarily sequences)
--
sequence contains = repeat(0,9)
integer ccount = 0, ch
object set
for i=1 to length(chosen) do
set = valid[pool[chosen[i]]]
for j=1 to length(set) do
ch = set[j]-'0'
if contains[ch]=0 then
contains[ch] = 1
ccount += 1
end if
end for
end for
if ccount=length(chosen) then
for i=1 to length(pool) do
if find(i,chosen)=0 then
set = valid[pool[i]]
if sequence(set) then
-- (reverse order so deletions don't foul indexes)
for j=length(set) to 1 by -1 do
ch = set[j]-'0'
if contains[ch] then
valid[pool[i]][j..j] = ""
improved = 1
end if
end for
end if
end if
end for
end if
return valid
end function
-- from [[Combinations#Phix|Combinations]]
-- from http://rosettacode.org/wiki/Combinations#Phix
function comb(sequence pool, valid, integer needed, done=0, sequence chosen={})
-- (used by deep_logic()/set elimination)
if needed=0 then -- got a full set
return test_comb(chosen,pool,valid)
end if
if done+needed>length(pool) then return valid end if -- cannot fulfil
-- get all combinations with and without the next item:
done += 1
if sequence(valid[pool[done]]) then
valid = comb(pool,valid,needed-1,done,append(chosen,done))
end if
return comb(pool,valid,needed,done,chosen)
end function
function deep_logic(string board, sequence valid)
--
-- Create a grid of valid moves. Note this does not modify board, but instead creates
-- sets of permitted values for each cell, which can also be and are used for hints.
-- Apply standard eliminations of known cells, then try some more advanced tactics:
--
-- 1) row/col elimination
-- If in any of the 9 small squares a number can only occur in one row or column,
-- then that number cannot occur in that row or column in two other corresponding
-- small squares. Example (this one with significant practical benefit):
-- 000|000|036
-- 840|000|000
-- 000|000|020
-- ---+---+---
-- 000|203|000
-- 010|000|700
-- 000|600|400
-- ---+---+---
-- 000|410|050
-- 003|000|200
-- 600|000|000 <-- 3
-- ^-- 3
-- Naively, the br can contain a 3 in the four corners, but looking at mid-right and
-- mid-bottom leads us to eliminating 3s in column 9 and row 9, leaving 7,7 as the
-- only square in the br that can be a 3. Uses dotcol and dotrow.
-- Without this, brute force on the above takes ~8s, but with it ~0s
--
-- 2) set elimination
-- If in any 9-set there is a set of n blank squares that can only contain n digits,
-- then no other squares can contain those digits. Example (with some benefit):
-- 75.|.9.|.46
-- 961|...|352
-- 4..|...|79.
-- ---+---+---
-- 2..|6.1|..7
-- .8.|...|.2.
-- 1..|328|.65
-- ---+---+---
-- ...|...|... <-- [7,8] is {1,3,8}, [7,9] is {1,3,8}
-- 3.9|...|2.4 <-- [8,8] is {1,8}
-- 84.|.3.|.79
-- The three cells above the br 479 can only contain {1,3,8}, so the .. of the .2.
-- in column 7 of that square are {5,6} (not 1) and hence [9,4] must be a 1.
-- (Relies on plain_logic to spot that "must be a 1", and serves as a clear example
-- of why this routine should not bother to attempt updating the board itself - as
-- it spends almost all of its time looking in a completely different place.)
-- (One could argue that [7,7] and [9,7] are the only places that can hold {5,6} and
-- therefore we should eliminate all non-{5,6} from those squares, as an alternative
-- strategy. However I think that would be harder to code and cannot imagine a case
-- said complementary logic covers, that the above does not, cmiiw.)
--
-- 3) x-wings
-- If a pair of rows or columns can only contain a given number in two matching places,
-- then once filled they will occupy opposite diagonal corners, hence that said number
-- cannot occur elsewhere in those two columns/rows. Example (with a benefit):
-- .43|98.|25. <-- 6 in [1,{6,9}]
-- 6..|425|...
-- 2..|..1|.94
-- ---+---+---
-- 9..|..4|.7. <-- hence 6 not in [4,9]
-- 3..|6.8|...
-- 41.|2.9|..3
-- ---+---+---
-- 82.|5..|... <-- hence 6 not in [7,6],[7,9]
-- ...|.4.|..5 <-- hence 6 not in [8,6]
-- 534|89.|71. <-- 6 in [9,{6,9}]
-- A 6 must be in [1,6] or [1,9] and [9,6] or [9,9], hence [7,9] is not 6 and must be 9.
-- (we also eliminate 6 from [4,9], [7,6] and [8,6] to no great use)
-- In practice this offers little benefit over a single trial-and-error step, as
-- obviously trying either 6 in row 1 or 9 immediately pinpoints that 9 anyway.
--
-- 4) swordfish (not attempted)
-- There is an extension to x-wings known as swordfish: three (or more) pairs form
-- a staggered pair (or more) of rectangles that exhibit similar properties, eg:
-- 8-1|-5-|-3-
-- 953|-68|---
-- -4-|-*3|5*8
-- ---+---+---
-- 6--|9-2|---
-- -8-|-3-|-4-
-- 3*-|5-1|-*7 <-- hence [6,3] is not 9, must be 4
-- ---+---+---
-- 5*2|-*-|-8-
-- --8|37-|--9
-- -3-|82-|1--
-- ^---^---^-- 3 pairs of 9s (marked with *) on 3 rows (only)
-- It is not a swordfish if the 3 pairs are on >3 rows, I trust that is obvious.
-- Logically you can extend this to N pairs on N rows, however I cannot imagine a
-- case where this is not immediately solved by a single trial-step being invalid.
-- (eg above if you try [3,5]:=9 it is quickly proved to be invalid, and the same
-- goes for [6,8]:=9 and [7,2]:=9, since they are all entirely inter-dependent.)
-- Obviously where I have said rows, the same concept can be applied to columns.
-- Likewise there are "Alternate Pairs" and "Hook or X-Y wing" strategies, which
-- are easily solved with a single trial-and-error step, and of course the brute
-- force algorithm is going to select pairs first anyway. [Erm, no it doesn't,
-- it selects shortest - I've noted the possible improvement below.]
--
integer col, row
sequence c, r
sequence nine, prevsets, set
object vj
integer ch, k, idx, sx, sy, count
if length(valid)=0 then
-- initialise/start again from scratch
valid = repeat("123456789",9*9)
end if
--
-- First perform standard eliminations of any known cells:
-- (repeated every time so plain_logic() does not have to worry about it)
--
for i=1 to 9*9 do
ch = board[i]
if ch>'0'
and string(valid[i]) then
valid[i] = ch
valid = eliminate_in(valid,nines[cols[i]],ch)
valid = eliminate_in(valid,nines[rows[i]],ch)
valid = eliminate_in(valid,nines[squares[i]],ch)
end if
end for
--
-- 1) row/col elimination
--
for s=19 to 27 do
c = repeat(0,9) -- 0 = none seen, 1..9 this col only, -1: >1 col
r = repeat(0,9) -- "" row row
nine = nines[s]
for n=1 to 9 do
k = nine[n]
vj = valid[k]
if string(vj) then
for i=1 to length(vj) do
ch = vj[i]-'0'
col = dotcol[k]
row = dotrow[k]
c[ch] = iff(find(c[ch],{0,col})!=0?col:-1)
r[ch] = iff(find(r[ch],{0,row})!=0?row:-1)
end for
end if
end for
for i=1 to 9 do
ch = i+'0'
col = c[i]
if col>0 then
valid = eliminate_in(valid,sixes[col],ch)
end if
row = r[i]
if row>0 then
valid = eliminate_in(valid,sixes[row],ch)
end if
end for
end for
--
-- 2) set elimination
--
for i=1 to length(nines) do
--
-- Practical note: Meticulously counting empties to eliminate larger set sizes
-- would at best reduce 6642 tests to 972, not deemed worth it.
--
for set_size=2 to 4 do
--if floor(count_empties(nines[i])/2)>=set_size then -- (untested)
valid = comb(nines[i],valid,set_size)
--end if
end for
end for
--
-- 3) x-wings
--
for ch='1' to '9' do
prevsets = repeat(0,9)
for x=1 to 9 do
count = 0
set = repeat(0,9)
for y=0 to 8 do
idx = y*9+x
if sequence(valid[idx]) and find(ch,valid[idx]) then
set[y+1] = 1
count += 1
end if
end for
if count=2 then
k = find(set,prevsets)
if k!=0 then
for y=0 to 8 do
if set[y+1]=1 then
for sx=1 to 9 do
if sx!=k and sx!=x then
valid = eliminate_in(valid,{y*9+sx},ch)
end if
end for
end if
end for
else
prevsets[x] = set
end if
end if
end for
prevsets = repeat(0,9)
for y=0 to 8 do
count = 0
set = repeat(0,9)
for x=1 to 9 do
idx = y*9+x
if sequence(valid[idx]) and find(ch,valid[idx]) then
set[x] = 1
count += 1
end if
end for
if count=2 then
k = find(set,prevsets)
if k!=0 then
for x=1 to 9 do
if set[x]=1 then
for sy=0 to 8 do
if sy+1!=k and sy!=y then
valid = eliminate_in(valid,{sy*9+x},ch)
end if
end for
end if
end for
else
prevsets[y+1] = set
end if
end if
end for
end for
return valid
end function
function permitted_in(string board, sequence sets, sequence valid, integer ch)
sequence set
integer pos, idx, bch
for i=1 to 9 do
set = nines[sets[i]]
pos = 0
for j=1 to 9 do
idx = set[j]
bch = board[idx]
if bch>'0' then
if bch=ch then pos = -1 exit end if
elsif find(ch,valid[idx]) then
if pos!=0 then pos = -1 exit end if
pos = idx
end if
end for
if pos>0 then
board[pos] = ch
improved = 1
end if
end for
return board
end function
enum INVALID = -1, INCOMPLETE = 0, SOLVED = 1, MULTIPLE = 2, BRUTE = 3
function plain_logic(string board)
--
-- Responsible for:
-- 1) cells with only one option
-- 2) numbers with only one home
--
integer solved
sequence valid = {}
object vi
while 1 do
solved = SOLVED
improved = 0
valid = deep_logic(board,valid)
-- 1) cells with only one option:
for i=1 to length(valid) do
vi = valid[i]
if string(vi) then
if length(vi)=0 then return {board,{},INVALID} end if
if length(vi)=1 then
board[i] = vi[1]
improved = 1
end if
end if
if board[i]<='0' then
solved = INCOMPLETE
end if
end for
if solved=SOLVED then return {board,{},SOLVED} end if
-- 2) numbers with only one home
for ch='1' to '9' do
board = permitted_in(board,cols,valid,ch)
board = permitted_in(board,rows,valid,ch)
board = permitted_in(board,squares,valid,ch)
end for
if not improved then exit end if
end while
return {board,valid,solved}
end function
function validate(string board)
-- (sum9 should be sufficient - if you want, get rid of nine/nines)
integer ch, sum9
sequence nine, nines = tagset(9)
for x=0 to 8 do -- columns
sum9 = 0
nine = repeat(0,9)
for y=1 to 81 by 9 do
ch = board[y+x]-'0'
if ch<1 or ch>9 then return 0 end if
sum9 += ch
nine[ch] = ch
end for
if sum9!=45 then return 0 end if
if nine!=nines then return 0 end if
end for
for y=1 to 81 by 9 do -- rows
sum9 = 0
nine = repeat(0,9)
for x=0 to 8 do
ch = board[y+x]-'0'
sum9 += ch
nine[ch] = ch
end for
if sum9!=45 then return 0 end if
if nine!=nines then return 0 end if
end for
for y=0 to 8 by 3 do -- small squares
for x=0 to 8 by 3 do
sum9 = 0
nine = repeat(0,9)
for sy=y*9 to y*9+18 by 9 do
for sx=x to x+2 do
ch = board[sy+sx+1]-'0'
sum9 += ch
nine[ch] = ch
end for
end for
if sum9!=45 then return 0 end if
if nine!=nines then return 0 end if
end for
end for
return 1
end function
function solve(string board, sequence valid={})
sequence solution, solutions
integer solved
integer minopt, mindx
object vi
{solution,valid,solved} = plain_logic(board)
if solved=INVALID then return {{},INVALID} end if
if solved=SOLVED then return {{solution},SOLVED} end if
if solved=BRUTE then return {{solution},BRUTE} end if
if solved!=INCOMPLETE then ?9/0 end if
-- find the cell with the fewest options:
-- (a possible improvement here would be to select the shortest
-- with the "most pairs" set, see swordfish etc above.)
minopt = 10
for i=1 to 9*9 do
vi = valid[i]
if string(vi) then
if length(vi)<=1 then ?9/0 end if -- should be caught above
if length(vi)<minopt then
minopt = length(vi)
mindx = i
end if
end if
end for
solutions = {}
for i=1 to minopt do
board[mindx] = valid[mindx][i]
{solution,solved} = solve(board,valid)
if solved=MULTIPLE then
return {solution,MULTIPLE}
elsif solved=SOLVED
or solved=BRUTE then
if not find(solution[1],solutions)
and validate(solution[1]) then
solutions = append(solutions,solution[1])
end if
if length(solutions)>1 then
return {solutions,MULTIPLE}
elsif length(solutions) then
return {solutions,BRUTE}
end if
end if
end for
if length(solutions)=1 then
return {solutions,BRUTE}
end if
return {{},INVALID}
end function
function test_one(string board)
sequence solutions
string solution, desc
integer solved
{solutions,solved} = solve(board)
if solved=SOLVED then
desc = "(logic)"
elsif solved=BRUTE then
desc = "(brute force)"
else
desc = "???" -- INVALID/INCOMPLETE/MULTIPLE
end if
if length(solutions)=0 then
solution = board
desc = "*** NO SOLUTIONS ***"
elsif length(solutions)=1 then
solution = solutions[1]
if not validate(solution) then
desc = "*** ERROR ***" -- (should never happen)
end if
else
solution = board
desc = "*** MULTIPLE SOLUTIONS ***"
end if
return {solution,desc}
end function
--NB Blank cells can be represented by any character <'1'. Spaces are not recommended since
-- they can all too easily be converted to tabs by copy/paste/save. In particular, ? and
-- _ are NOT valid characters for representing a blank square. Use any of .0-* instead.
constant tests = {
"..............3.85..1.2.......5.7.....4...1...9.......5......73..2.1........4...9", -- (0.01s, (logic))
-- row/col elimination (was 8s w/o logic first)
"000000036840000000000000020000203000010000700000600400000410050003000200600000000", -- (0.04s, (brute force))
".......39.....1..5..3.5.8....8.9...6.7...2...1..4.......9.8..5..2....6..4..7.....", -- (1.12s, (brute force))
"000037600000600090008000004090000001600000009300000040700000800010009000002540000", -- (0.00s, (logic))
"....839..1......3...4....7..42.3....6.......4....7..1..2........8...92.....25...6", -- (0.04s, (brute force))
"..1..5.7.92.6.......8...6...9..2.4.1.........3.4.8..9...7...3.......7.69.1.8..7..", -- (0.00s, (logic))
-- (the following takes ~8s when checking for multiple solutions)
"--3------4---8--36--8---1---4--6--73---9----------2--5--4-7--686--------7--6--5--", -- (0.01s, (brute force))
"..3.2.6..9..3.5..1..18.64....81.29..7.......8..67.82....26.95..8..2.3..9..5.1.3..", -- (0.00s, (logic))
"--4-5--6--6-1--8-93----7----8----5-----4-3-----6----7----2----61-5--4-3--2--7-1--", -- (0.00s, (logic))
-- x-wings
".4398.25.6..425...2....1.949....4.7.3..6.8...41.2.9..382.5.........4...553489.71.", -- (0.00s, (logic))
".9...4..7.....79..8........4.58.....3.......2.....97.6........4..35.....2..6...8.", -- (0.00s, (logic))
-- "AL Escargot", so-called "hardest sudoku"
"1....7.9..3..2...8..96..5....53..9...1..8...26....4...3......1..4......7..7...3..", -- (0.26s, (brute force))
"12.3....435....1....4........54..2..6...7.........8.9...31..5.......9.7.....6...8", -- (0.48s, (brute force))
"12.4..3..3...1..5...6...1..7...9.....4.6.3.....3..2...5...8.7....7.....5.......98", -- (1.07s, (brute force))
"394..267....3..4..5..69..2..45...9..6.......7..7...58..1..67..8..9..8....264..735", -- (0.00s, (logic))
"4......6.5...8.9..3....1....2.7....1.9.....4.8....3.5....2....7..6.5...8.1......6", -- (0.01s, (brute force))
"5...7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79", -- (0.00s, (logic))
"503600009010002600900000080000700005006804100200003000030000008004300050800006702", -- (0.00s, (logic))
"53..247....2...8..1..7.39.2..8.72.49.2.98..7.79.....8.....3.5.696..1.3...5.69..1.", -- (0.00s, (logic))
"530070000600195000098000060800060003400803001700020006060000280000419005000080079", -- (0.00s, (logic))
-- set exclusion
"75..9..46961...3524.....79.2..6.1..7.8.....2.1..328.65.........3.9...2.484..3..79", -- (0.00s, (logic))
-- Worlds hardest sudoku:
"800000000003600000070090200050007000000045700000100030001000068008500010090000400", -- (0.21s, (brute force))
"819--5-----2---75--371-4-6-4--59-1--7--3-8--2--3-62--7-5-7-921--64---9-----2--438", -- (0.00s, (logic))
"85...24..72......9..4.........1.7..23.5...9...4...........8..7..17..........36.4.", -- (0.01s, (logic))
"9..2..5...4..6..3...3.....6...9..2......5..8...7..4..37.....1...5..2..4...1..6..9", -- (0.17s, (brute force))
"97.3...6..6.75.........8.5.......67.....3.....539..2..7...25.....2.1...8.4...73..", -- (0.00s, (logic))
-- "the beast" (an earlier algorithm took 318s (5min 18s) on this):
"000060080020000000001000000070000102500030000000000400004201000300700600000000050", -- (0.03s, (brute force))
$},
lt = length(tests),
run_one_test = 0
constant l = " x x x | x x x | x x x ",
s = "-------+-------+-------",
l3 = join({l,l,l},"\n"),
fmt = substitute(join({l3,s,l3,s,l3},"\n"),"x","%c")&"\n"
procedure print_board(string board)
printf(1,fmt,board)
end procedure
procedure test()
string board -- (81 characters)
string solution, desc
atom t0 = time()
if run_one_test then
board = tests[run_one_test]
print_board(board)
{solution,desc} = test_one(board)
if length(solution)!=0 then
printf(1,"solution:\n")
print_board(solution)
end if
printf(1,"%s, %3.2fs\n",{desc,time()-t0})
else
for i=1 to lt do
atom t1 = time()
board = tests[i]
{solution,desc} = test_one(board)
printf(1," \"%s\", -- (%3.2fs, %s)\n",{board,time()-t1,desc})
-- printf(1," \"%s\", -- (%3.2fs, %s)\n",{solution,time()-t1,desc})
end for
t0 = time()-t0
printf(1,"%d puzzles solved in %3.2fs (av %3.2fs)\n",{lt,t0,t0/lt})
end if
end procedure
test()

View file

@ -0,0 +1,53 @@
func check(i, j) is cached {
var (id, im) = i.divmod(9)
var (jd, jm) = j.divmod(9)
jd == id && return true
jm == im && return true
var id2 = id//3
var jd2 = jd//3
jd2 == id2 || return false
jm//3 == im//3
}
func solve(board) {
for i in ^board {
board[i] && next
var *t = board[^board -> grep {|j| check(i, j) }]
{ |k|
t.contains(k) && next
board[i] = k
solve(board)
} * 9
board[i] = 0
return nil
}
for i in ^board {
print "#{board[i]} ";
print " " if (3 -> divides(i+1))
print "\n" if (9 -> divides(i+1))
print "\n" if (27 -> divides(i+1))
}
}
var board = %i(
5 3 0 0 2 4 7 0 0
0 0 2 0 0 0 8 0 0
1 0 0 7 0 3 9 0 2
0 0 8 0 7 2 0 4 9
0 2 0 9 8 0 0 7 0
7 9 0 0 0 0 0 8 0
0 0 0 0 3 0 5 0 6
9 6 0 0 1 0 3 0 0
0 5 0 6 9 0 0 1 0
)
solve(board)

View file

@ -0,0 +1,125 @@
import Foundation
typealias SodukuPuzzle = [[Int]]
class Soduku {
let mBoardSize:Int!
let mBoxSize:Int!
var mBoard:SodukuPuzzle!
var mRowSubset:[[Bool]]!
var mColSubset:[[Bool]]!
var mBoxSubset:[[Bool]]!
init(board:SodukuPuzzle) {
mBoard = board
mBoardSize = board.count
mBoxSize = Int(sqrt(Double(mBoardSize)))
mRowSubset = [[Bool]](count: mBoardSize, repeatedValue: [Bool](count: mBoardSize, repeatedValue: false))
mColSubset = [[Bool]](count: mBoardSize, repeatedValue: [Bool](count: mBoardSize, repeatedValue: false))
mBoxSubset = [[Bool]](count: mBoardSize, repeatedValue: [Bool](count: mBoardSize, repeatedValue: false))
initSubsets()
}
func computeBoxNo(i:Int, _ j:Int) -> Int {
let boxRow = i / mBoxSize
let boxCol = j / mBoxSize
return boxRow * mBoxSize + boxCol
}
func initSubsets() {
for i in 0..<mBoard.count {
for j in 0..<mBoard.count {
let value = mBoard[i][j]
if value != 0 {
setSubsetValue(i, j, value, true);
}
}
}
}
func isValid(i:Int, _ j:Int, var _ val:Int) -> Bool {
val--
let isPresent = mRowSubset[i][val] || mColSubset[j][val] || mBoxSubset[computeBoxNo(i, j)][val]
return !isPresent
}
func printBoard() {
for i in 0..<mBoardSize {
if i % mBoxSize == 0 {
println(" -----------------------")
}
for j in 0..<mBoardSize {
if j % mBoxSize == 0 {
print("| ")
}
print(mBoard[i][j] != 0 ? String(mBoard[i][j]) : " ")
print(" ")
}
println("|")
}
println(" -----------------------")
}
func setSubsetValue(i:Int, _ j:Int, _ value:Int, _ present:Bool) {
mRowSubset[i][value - 1] = present
mColSubset[j][value - 1] = present
mBoxSubset[computeBoxNo(i, j)][value - 1] = present
}
func solve() {
solve(0, 0)
}
func solve(var i:Int, var _ j:Int) -> Bool {
if i == mBoardSize {
i = 0
j++
if j == mBoardSize {
return true
}
}
if mBoard[i][j] != 0 {
return solve(i + 1, j)
}
for value in 1...mBoardSize {
if isValid(i, j, value) {
mBoard[i][j] = value
setSubsetValue(i, j, value, true)
if solve(i + 1, j) {
return true
}
setSubsetValue(i, j, value, false)
}
}
mBoard[i][j] = 0
return false
}
}
let board = [
[4, 0, 0, 0, 0, 0, 0, 6, 0],
[5, 0, 0, 0, 8, 0, 9, 0, 0],
[3, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 2, 0, 7, 0, 0, 0, 0, 1],
[0, 9, 0, 0, 0, 0, 0, 4, 0],
[8, 0, 0, 0, 0, 3, 0, 5, 0],
[0, 0, 0, 2, 0, 0, 0, 0, 7],
[0, 0, 6, 0, 5, 0, 0, 0, 8],
[0, 1, 0, 0, 0, 0, 0, 0, 6]
]
let puzzle = Soduku(board: board)
puzzle.solve()
puzzle.printBoard()