#sudoku by Bobby Griffin, 2/13/06 #edited by Bobby Griffin, 4/24/06 (thanks to Andrew Baxter) Help:=proc(): print(`Su(M), poss(M,n,rootN,B), Su2(P,n,rootN,B)`): print(`descendants(P,n,rootN,B), doubleCell(P,n,rootN,B), remove2(P,i,left,right,n,rootN,B,type)`): print(`singleCell(P,n,rootN,B), singleVal(P,n,rootN,B), solve1(P,B,i,j,opValue,n,rootN)`): print(`whichBlock(i,j,rootN), champion(P,n), children(P,n,rootN,B,y,z)`): end: ################################################################# #Su(M): The main procedure called by the user # #Input: M, a list of lists, size n^2 by n^2, of a Sudoku puzzle # #Output: Y, a set of solutions for the given Sudoku puzzle # or ERROR, if the size is not a perfect square ################################################################# Su:=proc(M) local pos,n,rootN,B,wB,i,j,k,X,Y,Xi,Xij: option remember: n:=nops(M): rootN:=sqrt(n): if rootN <> floor(rootN) then error(`Not a valid Sudoku problem`): fi: B:=[]: for i from 1 to n do B:=[op(B), []]: od: for i from 1 to n do for j from 1 to n do wB := whichBlock(i,j,rootN): B[wB] := [op(B[wB]), [i,j]]: od: od: pos:=poss(M,n,rootN,B): X:=Su2(pos,n,rootN,B): Y:={}: for i from 1 to nops(X) do Xi:=X[i]: for j from 1 to n do Xi[j] := [op(1..n,Xi[j])]: Xij := Xi[j]: Xij := [op(1..n,Xij)]: Xij := [seq(op(Xij[k]),k=1..n)]: Xi[j] := Xij: od: Y:=Y union {Xi}: od: return Y: end: ########################################## #END Su(M) ########################################## #################################################################################### #poss(M,n,rootN,B): Takes the original input of M and returns the possibility # matrix. The possibility matrix has a set of possible elements at each # location (i,j) 1<=i,j<=n. # Also, it contains a total value of how many times a number can occur in # each division # e.g. P[i][n+1][j] gives how many times the number 'j' can occur in the 'ith' row # n+1 corresponds to rows, n+2 to columns, n+3 to blocks # #Input: M, the original input # n, the size of the Sudoku puzzle # rootN, the square root of n # B, the configuation of the n, rootN-by-rootN blocks # e.g. B[i][j] gives the coordinates of the # 'jth' element in the 'ith' block # #Output: P, the possibility matrix #################################################################################### poss:=proc(M,n,rootN,B) local P,i,j,k,Mij: option remember: P:=[]: for i from 1 to n do P:= [op(P), []]: for j from 1 to n do P[i] := [op(P[i]), {seq(k, k=1..n)}]: od: P[i] := [op(P[i]), [n$n], [n$n], [n$n]]: od: for i from 1 to n do for j from 1 to n do Mij := M[i][j]: if Mij <> 0 then P:=solve1(P,B,i,j,M[i][j],n,rootN) fi: od: od: return P: end: ################################################### #END poss(M,n,rootN,B) ################################################### ############################################################################## #Su2(P,n,rootN,B): descdendants' recursive partner, this procedure calls #singleCell,singleVal, and doubleCell until no change is detected # #Input: P, the possibility matrix # n, the size of the Sudoku puzzle # rootN, the square root of n # B, the configuation of the n, rootN-by-rootN blocks # #Output: Recursively shrinks the possibility matrix along with descendants ############################################################################## Su2:=proc(P,n,rootN,B) local P1,change,change1,Pnew,X: option remember: change:=true: P1:=P: while change do change1:=true: while change1 do X:=singleCell(P1, n, rootN, B): change:=X[2]: change1:=X[2]: P1 := X[1]: X:=singleVal(P1, n, rootN, B): change:=change or X[2]: change1:=change1 or X[2]: P1 := X[1]: od: X:=doubleCell(P1,n,rootN,B): if P1 <> X then P1:=X: change:=true: fi: od: Pnew := descendants(P1,n,rootN,B): return Pnew: end: ############################################# #END Su2(P,n,rootN,B) ############################################# #################################################################################### #descendants(P,n,rootN,B): Su2's recursive partner. # Uses champion and children to find valid solutions to a given possibility matrix # Then uses Su2 to reduce these possibilities # #Input: P, the possibility matrix # n, the size of the Sudoku puzzle # rootN, the square root of n # B, the configuation of the n, rootN-by-rootN blocks # #Output: P1, a set containing the reduced (via Su2) forms of the children of # the champion of P # or {}, if champion returns FAIL #################################################################################### descendants:=proc(P,n,rootN,B) local i,j,P1,champ,y,z,CH: option remember: champ:=champion(P,n): if champ = FAIL then return {}: fi: if champ[3] = n+1 then return {P}: fi: y:=champ[1]: z:=champ[2]: CH:=children(P,n,rootN,B,y,z): P1:={}: for i from 1 to nops(CH) do P1:=P1 union Su2(CH[i],n,rootN,B): od: return P1: end: ########################################################### #END descendants(P,n,rootN,B) ########################################################### ##################################################################################### #doubleCell(P,n,rootN,B): checks if any cells in the same division with two elements # are exactly the same. If so, it uses remove2 to remove these two elements from # other cells in the same division # #Input: P, the possibility matrix # n, the size of the Sudoku puzzle # rootN, the square root of n # B, the configuation of the n, rootN-by-rootN blocks # #Output: P1, an updated possibility matrix ##################################################################################### doubleCell:=proc(P,n,rootN,B) local i,j,left,right,P1,temp,nR,nC,nB: option remember: P1:=P: for i from 1 to n do for left from 1 to n-1 do nR:=nops(P1[i][left]): nC:=nops(P1[left][i]): nB:=nops(P1[B[i][left][1]][B[i][left][2]]): for right from left+1 to n do if nR = 2 then if P1[i][left] = P1[i][right] then P1:=remove2(P1,i,left,right,n,rootN,B,1): fi: fi: if nC = 2 then if P1[left][i] = P1[right][i] then P1:=remove2(P1,i,left,right,n,rootN,B,2): fi: fi: if nB = 2 then if (P1[B[i][left][1]][B[i][left][2]] = P1[B[i][right][1]][B[i][right][2]]) then P1:=remove2(P1,i,left,right,n,rootN,B,3): fi: fi: od: od: od: return P1: end: ########################################################################### #END doubleCell(P,n,rootN,B) ########################################################################### ############################################################################## #remove2(P,i,left,right,n,rootN,B,type): Called by doubleCell in the event # that there are two cells in the same division with only the exact same # two elements. remove2 then removes these two elements from elsewhere in # the division # #Input: P, the possibility matrix # i, the index of where the match occurs (e.g. the 'ith' row) # left, the left-most cell that matches # right, the right-most cell that matches # n, the size of the Sudoku puzzle # rootN, the square root of n # B, the configuation of the n, rootN-by-rootN blocks # type, corresponds to the different divisions # 1=row, 2=column, 3=block # #Output: P1, the updated possibility matrix ############################################################################## remove2:=proc(P,i,left,right,n,rootN,B,type) local j,P1,op1,op2,wB,y,z: option remember: P1:=P: if type = 1 then op1:=P1[i][left][1]: op2:=P1[i][left][2]: for j from 1 to n do if j<>left and j<>right then if member(op1,P1[i][j]) then P1[i][j] := P1[i][j] minus {op1}: P1[i][n+1][op1] := P1[i][n+1][op1] - 1: P1[j][n+2][op1] := P1[j][n+2][op1] - 1: wB:=whichBlock(i,j,rootN): P1[wB][n+3][op1] := P1[wB][n+3][op1] - 1: fi: if member(op2,P1[i][j]) then P1[i][j] := P1[i][j] minus {op2}: P1[i][n+1][op2] := P1[i][n+1][op2] - 1: P1[j][n+2][op2] := P1[j][n+2][op2] - 1: wB:=whichBlock(i,j,rootN): P1[wB][n+3][op2] := P1[wB][n+3][op2] - 1: fi: fi: od: fi: if type = 2 then op1:=P1[left][i][1]: op2:=P1[left][i][2]: for j from 1 to n do if j<>left and j<>right then if member(op1,P1[j][i]) then P1[j][i] := P1[j][i] minus {op1}: P1[j][n+1][op1] := P1[j][n+1][op1] - 1: P1[i][n+2][op1] := P1[i][n+2][op1] - 1: wB:=whichBlock(j,i,rootN): P1[wB][n+3][op1] := P1[wB][n+3][op1] - 1: fi: if member(op2,P1[j][i]) then P1[j][i] := P1[j][i] minus {op2}: P1[j][n+1][op2] := P1[j][n+1][op2] - 1: P1[i][n+2][op2] := P1[i][n+2][op2] - 1: wB:=whichBlock(j,i,rootN): P1[wB][n+3][op2] := P1[wB][n+3][op2] - 1: fi: fi: od: fi: if type = 3 then y:=B[i][left][1]: z:=B[i][left][2]: op1:=P1[y][z][1]: op2:=P1[y][z][2]: for j from 1 to n do if j<>left and j<>right then y:=B[i][j][1]: z:=B[i][j][2]: if member(op1,P1[y][z]) then P1[y][z] := P1[y][z] minus {op1}: P1[y][n+1][op1] := P1[y][n+1][op1] - 1: P1[z][n+2][op1] := P1[z][n+2][op1] - 1: P1[i][n+3][op1] := P1[i][n+3][op1] - 1: fi: if member(op2,P1[y][z]) then P1[y][z] := P1[y][z] minus {op2}: P1[y][n+1][op2] := P1[y][n+1][op2] - 1: P1[z][n+2][op2] := P1[z][n+2][op2] - 1: P1[i][n+3][op2] := P1[i][n+3][op2] - 1: fi: fi: od: fi: return P1: end: ################################################################## #END remove2(P,i,left,right,n,rootN,B,type) ################################################################## ################################################################################ #singleCell(P,n,rootN,B): checks for any cells with only one possible entry # #Input: P, the possibility matrix # n, the size of the Sudoku puzzle # rootN, the square root of n # B, the configuation of the n, rootN-by-rootN blocks # #output: a list containing: # P1, the updated possibility matrix # flag, a boolean indicating whether or not a change has occurred ################################################################################ singleCell:=proc(P,n,rootN,B) local i,j,P1,temp,flag: option remember: P1:=P: flag:=false: for i from 1 to n do for j from 1 to n do temp := P1[i][j]: if nops(temp)=1 and P1[i][n+1][op(temp)]>-1 then P1:=solve1(P1,B,i,j,op(temp),n,rootN): flag:=true: fi: od: od: return [P1,flag]: end: ################################################################### #END singleCell(P,n,rootN,B) ################################################################### ############################################################################### #singleVal(P,n,rootN,B): checks within every division to see if a number # can occur only once # i.e. checks P[i][n+k][j], i=1..n, k=1..3, j=1..n to see if the value is '1' # #Input: P, the possibility matrix # n, the size of the Sudoku puzzle # rootN, the square root of n # B, the configuation of the n, rootN-by-rootN blocks # #Output: a list containing: # P1, the updated possibility matrix # flag, a boolean indicating whether or not a change has occurred ############################################################################### singleVal:=proc(P,n,rootN,B) local i,j,k,l, P1, temp, flag,y,z: option remember: P1:=P: flag:=false: for i from 1 to n do for j from 1 to n do for k from 1 to 3 do if P1[i][n+k][j] = 1 then for l from 1 to n do if k = 1 then if member(j,P1[i][l]) then P1:=solve1(P1,B,i,l,j,n,rootN): flag:=true: fi: else if k = 2 then if member(j,P1[l][i]) then P1:=solve1(P1,B,l,i,j,n,rootN): flag:=true: fi: else y:=B[i][l][1]: z:=B[i][l][2]: if member(j,P1[y][z]) then P1:=solve1(P1,B,y,z,j,n,rootN): flag:=true: fi: fi: fi: od: fi: od: od: od: return[P1,flag]: end: ####################################################### #END singleVal(P,n,rootN,B) ####################################################### ############################################################################## #solve1(P,B,i,j,opValue,n,rootN): solve1 makes it so opValue is the solution # to the (i,j)th location in the possibility matrix # A dummy value of -1 is given to P[i][n+1][opValue] (similarly for column # and block) in this case # #Input: P, the possibility matrix # B, the configuation of the n, rootN-by-rootN blocks # i, the row in which we are solving # j, the column in which we are solving # opValue, the value for which we are solving # n, the size of the Sudoku puzzle # rootN, the square root of n # #Output: P1, the updated possibility matrix # or ERROR, if opValue does not appear in the (i,j)th location of P ############################################################################## solve1:=proc(P,B,i,j,opValue,n,rootN) local P1,k,value,wB,wB2,temp,y,z: option remember: P1:=P: value:={opValue}: wB:=whichBlock(i,j,rootN): temp:=P1[i][j]: if not member(opValue,temp) then error(`Not a valid Sudoku problem`) fi: if nops(temp)<>1 then for k from 1 to nops(temp) do P1[i][j] := temp minus {temp[k]}: P1[i][n+1][temp[k]] := P1[i][n+1][temp[k]] - 1: P1[j][n+2][temp[k]] := P1[j][n+2][temp[k]] - 1: P1[wB][n+3][temp[k]] := P1[wB][n+3][temp[k]] - 1: od: fi: P1[i][j] := value: P1[i][n+1][opValue] := -1: P1[j][n+2][opValue] := -1: P1[wB][n+3][opValue] := -1: for k from 1 to n do temp:=P1[i][k]: if member(opValue,temp) then P1[i][k] := temp minus value: P1[i][n+1][opValue] := P1[i][n+1][opValue] - 1: P1[k][n+2][opValue] := P1[k][n+2][opValue] - 1: wB2:=whichBlock(i,k,rootN): P1[wB2][n+3][opValue] := P1[wB2][n+3][opValue] - 1: fi: temp:=P1[k][j]: if member(opValue,temp) then P1[k][j] := temp minus value: P1[k][n+1][opValue] := P1[k][n+1][opValue] - 1: P1[j][n+2][opValue] := P1[j][n+2][opValue] - 1: wB2:=whichBlock(k,j,rootN): P1[wB2][n+3][opValue] := P1[wB2][n+3][opValue] - 1: fi: y:=B[wB][k][1]: z:=B[wB][k][2]: temp:=P1[y][z]: if member(opValue,temp) then P1[y][z] := temp minus value: P1[y][n+1][opValue] := P1[y][n+1][opValue] - 1: P1[z][n+2][opValue] := P1[z][n+2][opValue] - 1: wB2:=whichBlock(y,z,rootN): P1[wB2][n+3][opValue] := P1[wB2][n+3][opValue] - 1: fi: od: P1[i][j]:=value: P1[i][n+1][opValue]:=-1: P1[j][n+2][opValue] := -1: P1[wB][n+3][opValue] := -1: return P1: end: ###################################################################### #END solve1(P,B,i,j,opValue,n,rootN) ###################################################################### ################################################################### #whichBlock(i,j,rootN): returns the ordinal of the block where the # (i,j)th location falls # #Input: i, the row # j, the column # rootN, the square root of the size of the Sudoku puzzle ################################################################### whichBlock:=proc(i,j,rootN) local b,ii,jj: option remember: ii:=ceil(i/rootN): jj:=ceil(j/rootN): RETURN((ii-1)*rootN+jj): end: ############################################# #END whichBlock(i,j,rootN) ############################################# ################################################################################## #champion(P,n): finds the location in the possibility matrix with the # smallest number of possibilities (>1) # #Input: P, the possibility matrix # n, the size of the Sudoku puzzle # #Output: a list containing: # minI, the row of the least possibilities # minJ, the column of the least possibilities # min, the number of possibilities in this location # or FAIL, if a location in the possibility matrix has ZERO possibilities ################################################################################## champion:=proc(P,n) local i,j,min,minI,minJ,nopsPij: option remember: minI := 0: minJ := 0: min := n+1: for j from 1 to n do for i from 1 to n do nopsPij := nops(P[i][j]): if nopsPij = 0 then return FAIL: fi: if nopsPij < min and nopsPij > 1 then minI := i: minJ := j: min := nopsPij: fi: od: od: return [minI,minJ,min]: end: ######################################################## #END champion(P,n) ######################################################## ############################################################################ #children(P,n,rootN,B,y,z): returns the children of the (y,z)th location in # the possibility matrix # #Input: P, the possibility matrix # n, the size of the Sudoku puzzle # rootN, the square root of n # B, the configuation of the n, rootN-by-rootN blocks # y, the row of the location to find the children # z, the column of the location to find the children # #Output: P1, a set containing the possibility matrices of all the children ############################################################################ children:=proc(P,n,rootN,B,y,z) local i, j, P1: option remember: P1:={}: for i from 1 to nops(P[y][z]) do P1:= P1 union {solve1(P,B,y,z,P[y][z][i],n,rootN)}: od: return P1: end: ######################################################## #END(children(P,n,rootN,B,y,z) ########################################################