# SAS Fun: Sudoko

Here is a SAS program to help you beat others at Sudoko, and impress people. It was written by a chap named Ryan Howard in 2006, and I am thankful for him in allowing me in sharing this.You can let us know if you find a puzzle it could not solve , or if you tweak the program a bit. The code is pasted below.

Have fun !

And the SAS paper on this was at SAS Global Forum 2007- the resulting
paper, “SAS and Sudoku”, was written by Richard DeVenezia, John Gerlach,
Larry Hoyle, Talbot Katz and Rick Langston, and can be viewed at
http://www2.sas.com/proceedings/forum2007/011-2007.pdf.

(p.s. I haven’t tested this on WPS , they still dont have the SAS Macro language ,but let me know if you have any equivalent in SPSS or R 🙂   )

```*=============================================================================;
* sudoku.sas                                                                  ;
* Written by: Ryan Howard                                                     ;
* Date: Sept. 2006                                                            ;
*-----------------------------------------------------------------------------;
* Summary: This program solves sudoku puzzles consisting of a 9X9 matrix.     ;
*-----------------------------------------------------------------------------;
* Upgrade Ideas:  1. Add a GUI to collect the input numbers and display output;
*                 2. Expand logic to work for 16X16 matrices                  ;
*=============================================================================;

title;
options nodate nonumber;

data _null_;

*-----------------------------------------------------------------------------;
* input  inital values for each cell from puzzle                              ;
*-----------------------------------------------------------------------------;

_1111=9; _1112=.; _1113=.;   _1211=.; _1212=.; _1213=.;   _1311=1; _1312=.; _1313=.;
_1121=5; _1122=.; _1123=.;   _1221=.; _1222=6; _1223=.;   _1321=.; _1322=4; _1323=2;
_1131=.; _1132=.; _1133=.;   _1231=7; _1232=1; _1233=.;   _1331=5; _1332=.; _1333=.;

_2111=.; _2112=.; _2113=2;   _2211=.; _2212=.; _2213=.;   _2311=.; _2312=1; _2313=.;
_2121=.; _2122=3; _2123=.;   _2221=.; _2222=.; _2223=.;   _2321=2; _2322=9; _2323=.;
_2131=.; _2132=7; _2133=.;   _2231=.; _2232=.; _2233=6;   _2331=.; _2332=.; _2333=3;

_3111=.; _3112=2; _3113=.;   _3211=.; _3212=.; _3213=8;   _3311=.; _3312=.; _3313=.;
_3121=.; _3122=.; _3123=4;   _3221=5; _3222=.; _3223=.;   _3321=.; _3322=.; _3323=.;
_3131=.; _3132=.; _3133=.;   _3231=.; _3232=3; _3233=.;   _3331=8; _3332=.; _3333=9;

%macro printmatrix;
*----------------------------------------------------------------;
* print the result matrix                                        ;
*----------------------------------------------------------------;
*---------------------------------------------------;
* Assign column positions for printing matrix       ;
*---------------------------------------------------;
c1=1;
c2=10;
c3=20;

c4=35;
c5=45;
c6=55;

c7=70;
c8=80;
c9=90;

do _i=1 to 3;
do _j=1 to 3;
do _k= 1 to 3;
do _l = 1 to 3;
if boxrowcol(_i,_j,_k,_l) ne . then char_guess(_i,_j,_k,_l)=put(boxrowcol(_i,_j,_k,_l),1.);
end;
end;
end;
end;

put/;

put @c1 c1111  @c2 c1112  @c3 c1113    @c4 c1211  @c5 c1212  @c6 c1213   @c7 c1311  @c8 c1312  @c9 c1313 ;
put @c1 c1121  @c2 c1122  @c3 c1123    @c4 c1221  @c5 c1222  @c6 c1223   @c7 c1321  @c8 c1322  @c9 c1323 ;
put @c1 c1131  @c2 c1132  @c3 c1133    @c4 c1231  @c5 c1232  @c6 c1233   @c7 c1331  @c8 c1332  @c9 c1333 ;
put/;

put @c1 c2111  @c2 c2112  @c3 c2113    @c4 c2211  @c5 c2212  @c6 c2213   @c7 c2311  @c8 c2312  @c9 c2313 ;
put @c1 c2121  @c2 c2122  @c3 c2123    @c4 c2221  @c5 c2222  @c6 c2223   @c7 c2321  @c8 c2322  @c9 c2323 ;
put @c1 c2131  @c2 c2132  @c3 c2133    @c4 c2231  @c5 c2232  @c6 c2233   @c7 c2331  @c8 c2332  @c9 c2333 ;
put/;

put @c1 c3111  @c2 c3112  @c3 c3113    @c4 c3211  @c5 c3212  @c6 c3213   @c7 c3311  @c8 c3312  @c9 c3313 ;
put @c1 c3121  @c2 c3122  @c3 c3123    @c4 c3221  @c5 c3222  @c6 c3223   @c7 c3321  @c8 c3322  @c9 c3323 ;
put @c1 c3131  @c2 c3132  @c3 c3133    @c4 c3231  @c5 c3232  @c6 c3233   @c7 c3331  @c8 c3332  @c9 c3333 ;

put ;
%mend printmatrix;

*------array to hold unique number found for each cell-----------;
array boxRowCol(3,3,3,3) 3  _1111 _1112 _1113
_1121 _1122 _1123
_1131 _1132 _1133

_1211 _1212 _1213
_1221 _1222 _1223
_1231 _1232 _1233

_1311 _1312 _1313
_1321 _1322 _1323
_1331 _1332 _1333

_2111 _2112 _2113
_2121 _2122 _2123
_2131 _2132 _2133

_2211 _2212 _2213
_2221 _2222 _2223
_2231 _2232 _2233

_2311 _2312 _2313
_2321 _2322 _2323
_2331 _2332 _2333

_3111 _3112 _3113
_3121 _3122 _3123
_3131 _3132 _3133

_3211 _3212 _3213
_3221 _3222 _3223
_3231 _3232 _3233

_3311 _3312 _3313
_3321 _3322 _3323
_3331 _3332 _3333;

*-----array to hold list of possible digits not for each cell until a unique value is found--;
array char_guess(3,3,3,3) \$9 c1111 c1112 c1113
c1121 c1122 c1123
c1131 c1132 c1133

c1211 c1212 c1213
c1221 c1222 c1223
c1231 c1232 c1233

c1311 c1312 c1313
c1321 c1322 c1323
c1331 c1332 c1333

c2111 c2112 c2113
c2121 c2122 c2123
c2131 c2132 c2133

c2211 c2212 c2213
c2221 c2222 c2223
c2231 c2232 c2233

c2311 c2312 c2313
c2321 c2322 c2323
c2331 c2332 c2333

c3111 c3112 c3113
c3121 c3122 c3123
c3131 c3132 c3133

c3211 c3212 c3213
c3221 c3222 c3223
c3231 c3232 c3233

c3311 c3312 c3313
c3321 c3322 c3323
c3331 c3332 c3333;

file print;

*---------------------------------------------------;
*initailize character guess                         ;
*---------------------------------------------------;
do v=1 to 3;
do w=1 to 3;
do x=1 to 3;
do y=1 to 3;
if boxRowCol(v,w,x,y)=. then char_guess(v,w,x,y)='123456789';
end;
end;
end;
end;

*---marker for goto statements;
MAINLOOP:

*-----------------------------------------------------------------------------;
*remove  known values in box row or col from char guess                      ;
*-----------------------------------------------------------------------------;
do v=1 to 3;
do w=1 to 3;
do x=1 to 3;
do y=1 to 3;
*----------------------------------------------------------------;
* remove any values in box from char guess                       ;
*----------------------------------------------------------------;
do a=1 to 3;
do b=1 to 3;
if boxRowCol(v,w,a,b) ne . then
char_guess(v,w,x,y)=compress(char_guess(v,w,x,y),put(boxRowCol(v,w,a,b),3.));
end;
end;
*----------------------------------------------------------------;
* remove any values in row from char guess                       ;
*----------------------------------------------------------------;
do a=1 to 3;
do b=1 to 3;
if  boxRowCol(v,a,x,b) ne . then
char_guess(v,w,x,y)=compress(char_guess(v,w,x,y),put(boxRowCol(v,a,x,b),3.));
end;
end;
*----------------------------------------------------------------;
* remove any values in column from char guess                    ;
*----------------------------------------------------------------;
do a=1 to 3;
do b=1 to 3;
if  boxRowCol(a,w,b,y) ne . then
char_guess(v,w,x,y)=compress(char_guess(v,w,x,y),put(boxRowCol(a,w,b,y),3.));
end;
end;
*----------------------------------------------------------------;
* if only one choice left for the cell, set the value            ;
*----------------------------------------------------------------;
if (length(char_guess(v,w,x,y))=1 and char_guess(v,w,x,y) ne '' )
then do;
BoxRowCol(v,w,x,y)=input(char_guess(v,w,x,y),3.);
put  BoxRowCol(v,w,x,y)=
' set because all other values were eliminated for this cell';
char_guess(v,w,x,y)=''; *not required as this is set to blank on next itteration;

GOTO MAINLOOP; *any time new value is definite go to top of outer loop
*and reitterate using the new information           ;
end;
end;
end;
end;
end;
*-----------------------------------------------------------------------------;
* for each box,   check if there is only one place a digit                   ;
* can possibly fit.  If so, set that digit and return to mainloop            ;
*-----------------------------------------------------------------------------;
do v=1 to 3;      *block row;
do w=1 to 3;    *block  col;
do u=1 to 9; *cycle through digits;
skip=0; d_cnt=0;
do x=1 to 3;   *row w/in bock ;
do y=1 to 3;   * col w/in row w/in block;
if boxRowCol(v,w,x,y)=u then skip=1;
if skip=0 then if indexc(char_guess(v,w,x,y),put(u,1.)) then do;
dummy=   indexc(char_guess(v,w,x,y),put(u,3.));
d_cnt=d_cnt+1;
if d_cnt>1 then skip=1;
else do;
v1=v; w1=w; x1=x; y1=y;
end;
end;
end; *y;
end; *x;
*-----------------------------------------------------;
*at completion of the box   if there is only one place;
* a digit can go, place it there                     ;
*-----------------------------------------------------;
if d_cnt=1 and skip=0 then do;
boxRowCol(v1,w1,x1,y1)=u;
char_guess(v1,w1,x1,y1)='' ;  *not really required as this is reset in mainloop;
put 'Value Found as only place in box possible for digdet ' BoxRowCol(v1,w1,x1,y1)=;
GOTO MAINLOOP;
end;
end; *u;
end; *w;
end; *v;

*-----------------------------------------------------------------------------;
* for each of the 9 columns, check if there is only one place a digit        ;
* can possibly fit.  If so, set that digit and return to mainloop            ;
*-----------------------------------------------------------------------------;
do v=1 to 3;      *set of 3 blocks to take column from;
do w=1 to 3;    *current col to compaire over the 3 blocks;
do u=1 to 9; *cycle through digits;
skip=0; d_cnt=0;
do x=1 to 3;   *block row to take row from  ;
do y=1 to 3;   * row w/in row w/in block;
if boxRowCol(x,v,y,w)=u then skip=1;
if skip=0 then if indexc(char_guess(x,v,y,w),put(u,1.)) then do;
dummy=   indexc(char_guess(x,v,y,w),put(u,3.));
d_cnt=d_cnt+1;
if d_cnt>1 then skip=1;
else do;
v1=v; w1=w; x1=x; y1=y;
end;
end;
end; *y;
end; *x;
*-----------------------------------------------------;
*at completion of the column if there is only one place;
* a digit can go, place it there                     ;
*-----------------------------------------------------;
if d_cnt=1 and skip=0 then do;
boxRowCol(x1,v1,y1,w1)=u;
char_guess(x1,v1,y1,w1)='' ;  *not really required as this is reset in mainloop;
put 'Value Found as only place allowable for digdet in Column' BoxRowCol(x1,v1,y1,w1)=;
GOTO MAINLOOP;
end;
end; *u;
end; *w;
end; *v;

*-----------------------------------------------------------------------------;
* for each of the 9 Rows, check if there is only one place a digit           ;
* can possibly fit.  If so, set that digit and return to mainloop            ;
*-----------------------------------------------------------------------------;
do v=1 to 3;      *set of 3 blocks to take row    from;
do w=1 to 3;    *current row to compaire over the 3 blocks;
do u=1 to 9; *cycle through digits;
skip=0; d_cnt=0;
do x=1 to 3;   *block col to take row from  ;
do y=1 to 3;   * col w/in col w/in block;
if boxRowCol(v,x,w,y)=u then skip=1;
if skip=0 then if indexc(char_guess(v,x,w,y),put(u,1.)) then do;
dummy=   indexc(char_guess(v,x,w,y),put(u,3.));
d_cnt=d_cnt+1;
if d_cnt>1 then skip=1;
else do;
v1=v; w1=w; x1=x; y1=y;
end;
end;
end; *y;
end; *x;
*-----------------------------------------------------;
*at completion of the row if there is only one place;
* a digit can go, place it there                     ;
*-----------------------------------------------------;
if d_cnt=1 and skip=0 then do;
boxRowCol(v1,x1,w1,y1)=u;
char_guess(v1,x1,w1,y1)='' ;  *not really required as this is reset in mainloop;
put 'Value Found as only place allowable for digit in overall Row   ' BoxRowCol(v1,x1,w1,y1)=;
GOTO MAINLOOP;
end;
end; *u;
end; *w;
end; *v;
*-----------------------------------------------------------------------------;
* for each box, see if all possible locations for a digit occur                ;
* in a single  column.  if so, remove this value from the corresponding        ;
* columns in the adjacent boxes                                               ;
*-----------------------------------------------------------------------------;
do u=1 to 9; *cycle through digits;
do v=1 to 3;      *row of block;
do w=1 to 3;    *column of block ;
skip=0;
col_track=.;
do x=1 to 3;   *row  in block    ;
do y=1 to 3;   * col in block;
if boxRowCol(v,w,x,y)=u then skip=1;
if skip=0 then if indexc(char_guess(v,w,x,y),put(u,1.))>0 then do;
*-------if value is in more than one col then go to next block;
if col_track NE . and col_track ne y  then skip=1;
else col_track=y;
end;
*----if at the end of the block only one colunm can --------;
*    contain the digit, then, make that column in the boxes;
*    of the same column as the current box, remove current  ;
*    digit from their possible values                      ;
if x=3 and y=3 and skip=0 and col_track ne . then do;
update=0;
do j=1 to 3;
do k=1 to 3;                            /* only  remove from adjacent boxes*/
/* in corresponding rows           */
if indexc(char_guess(j,w,k,col_track),put(u,1.)) and j ne v  then do;
char_guess(j,w,k,col_track)=compress(char_guess(j,w,k,col_track),put(u,1.));
update=update+1;
put 'Value ' U= ' removed from ' char_guess(j,w,k,col_track)=
' because other box needs it in this column';
end;
end;
end;
if update>0 then GOTO MAINLOOP;
end;
end; *y;
end; *x;
end; *w;
end; *v;
end; *u;
*-----------------------------------------------------------------------------;
* for each box, see if all possible locations for a digit occur              ;
* in a single  row.  if so, remove this value from the corresponding         ;
* rows in the adjacent boxes                                                 ;
*-----------------------------------------------------------------------------;
do u=1 to 9; *cycle through digits;
do v=1 to 3;      *row of block;
do w=1 to 3;    *col of block ;
skip=0;
row_track=.;
do x=1 to 3;   *row in block    ;
do y=1 to 3;   * col in block;
if boxRowCol(v,w,x,y)=u then skip=1;
if skip=0 then if indexc(char_guess(v,w,x,y),put(u,1.))>0 then do;
*-------if value is in more than one col then go to next block;
if row_track NE . and row_track ne x  then skip=1;
else row_track=x;

* put char_guess(v,w,x,y)= u= x=  row_track= skip=;

end;

*----if at the end of the block only one column can --------;
*    contain the digit, then, make that column in the boxes;
*    of the same column as the current box, remove current  ;
*    digit from their possible values                      ;
if x=3 and y=3 and skip=0 and row_track ne . then do;
update=0;
do j=1 to 3;
do k=1 to 3;                            /* only  remove from adjacent boxes*/
/* in corresponding rows           */
if indexc(char_guess(v,j,row_track,k),put(u,1.)) and j ne w   then do;
char_guess(v,j,row_track,k)=compress(char_guess(v,j,row_track,k),put(u,1.));
update=update+1;
put 'Value ' U= ' removed from ' char_guess(v,j,row_track,k)=
' because other box needs it in this row';
end;
end;
end;
if update>0 then GOTO MAINLOOP;
end;
end; *y;
end; *x;
end; *w;
end; *v;
end; *u;

%*macro skipthis1;

*-----------------------------------------------------------------------------;
* if 2 cells in a box have an identical set of 2 digits, these 2 digits should;
* be removed from the list of possible values for the other (7) cells  in the ;
* box.                                                                        ;
*  this logic is also applied to rows and columns                             ;
*  this logic is also expanded to include strings or 3,4,5,6,7, and 8 digits  ;
*  however it is not likely thatmore than 2 or 3 will ever be used            ;
*-----------------------------------------------------------------------------;
do v=1 to 3;
do w=1 to 3;
*----------------------------------------------------------------;
* checking box for cells with identical list of possible values  ;
*----------------------------------------------------------------;
do x=1 to 3;
do y=1 to 3;
*current cell = w,x,y,z;
length checklist \$9 matchMatrixlist\$20;
numMatchingCells=1;
matchMatrixList='';
checklist=strip(char_guess(v,w,x,y));
updated=0;

if length(checklist)>1 then do;
do a=1 to 3;
do b=1 to 3;
if (x=a )*(b=y)=0 then do;
if strip(char_guess(v,w,a,b))=checklist then do;
numMatchingCells=numMatchingCells+1;
if matchMatrixList='' then matchMatrixList=put(x,1.)||put(y,1.) ;
matchMatrixList=trim(left(MatchMatrixList))
||put(a,1.)||put(b,1.) ;
matchMatrixList=compress(matchMatrixList,' ');
*put v= w= x= y= a= b= numMatchingCells= matchMatrixList=;
end;
end;
end;
end;
if numMatchingCells=length(checklist)then do;
do a=1 to 3;
do b=1 to 3;
ab=put(a,1.)||put(b,1.);
if substr(matchMatrixList,1,2) ne ab and
substr(matchMatrixList,3,2) ne ab and
substr(matchMatrixList,5,2) ne ab and
substr(matchMatrixList,7,2) ne ab and
substr(matchMatrixList,9,2) ne ab and
substr(matchMatrixList,11,2) ne ab and
substr(matchMatrixList,13,2) ne ab and
substr(matchMatrixList,15,2) ne ab and
substr(matchMatrixList,17,2) ne ab and
char_guess(v,w,a,b) ne ''  then do;
if indexc(char_guess(v,w,a,b),strip(checklist)) then do;
updated=1;
put 'Box Digits ' checklist= ' removed from '
char_guess(v,w,a,b)= ' because this unique set of '
NumMatchingCells ' digits is shared by '
NumMatchingCells ' Cells.';
*put  v= w= x= y= a= b= matchMatrixList=;
char_guess(v,w,a,b)=compress(char_guess(v,w,a,b),checklist);
end;
end;
end; *b;
end; *a;
if updated > 0 then GOTO MAINLOOP;

end; *numMatchingCells=length(cheklist);
end; *length checklist >1;
end; *y;
end; *x;
end; *w;
end; *v;

%*mend skipthis1;
%*macro skipthis2;

do v=1 to 3;
do w=1 to 3;
*----------------------------------------------------------------;
* checking column for cells with identical list of possible values  ;
*----------------------------------------------------------------;
do x=1 to 3;
do y=1 to 3;
*current cell = w,x,y,z;
length checklist \$9 matchMatrixlist\$20;
numMatchingCells=1;
matchMatrixList='';
checklist=strip(char_guess(x,v,y,w));
updated=0;

if length(checklist)>1 then do;
do a=1 to 3;
do b=1 to 3;
if (x=a )*(b=y)=0 then do;
if strip(char_guess(a,v,b,w))=checklist then do;
numMatchingCells=numMatchingCells+1;
if matchMatrixList='' then matchMatrixList=put(x,1.)||put(y,1.) ;
matchMatrixList=trim(left(MatchMatrixList))
||put(a,1.)||put(b,1.) ;
matchMatrixList=compress(matchMatrixList,' ');
* put v= w= x= y= a= b= numMatchingCells= matchMatrixList=;
end;
end;
end;
end;
if numMatchingCells=length(checklist)then do;
do a=1 to 3;
do b=1 to 3;
ab=put(a,1.)||put(b,1.);
if substr(matchMatrixList,1,2) ne ab and
substr(matchMatrixList,3,2) ne ab and
substr(matchMatrixList,5,2) ne ab and
substr(matchMatrixList,7,2) ne ab and
substr(matchMatrixList,9,2) ne ab and
substr(matchMatrixList,11,2) ne ab and
substr(matchMatrixList,13,2) ne ab and
substr(matchMatrixList,15,2) ne ab and
substr(matchMatrixList,17,2) ne ab and
char_guess(a,v,b,w) ne ''  then do;
if indexc(char_guess(a,v,b,w),strip(checklist)) then do;
updated=1;
put 'Column Digits ' checklist= ' removed from ' char_guess(a,v,b,w)=;
*put  a= x= v= b= y= w= matchMatrixList=;
char_guess(a,v,b,w)=compress(char_guess(a,v,b,w),checklist);
end;
end;
end; *b;
end; *a;
if updated > 0 then GOTO MAINLOOP;

end; *numMatchingCells=length(cheklist);
end; *length checklist >1;
end; *y;
end; *x;
end; *w;
end; *v;
%*mend skipthis2;

do v=1 to 3;
do w=1 to 3;
*----------------------------------------------------------------;
* checking rows for cells with identical list of possible values  ;
*----------------------------------------------------------------;
do x=1 to 3;
do y=1 to 3;
*current cell = w,x,y,z;
length checklist \$9 matchMatrixlist\$20;
numMatchingCells=1;
matchMatrixList='';
checklist=strip(char_guess(v,x,w,y));
updated=0;

if length(checklist)>1 then do;
do a=1 to 3;
do b=1 to 3;
if (x=a )*(b=y)=0 then do;
if strip(char_guess(v,a,w,b))=checklist then do;
numMatchingCells=numMatchingCells+1;
if matchMatrixList='' then matchMatrixList=put(x,1.)||put(y,1.) ;
matchMatrixList=trim(left(MatchMatrixList))
||put(a,1.)||put(b,1.) ;
matchMatrixList=compress(matchMatrixList,' ');
* put v= w= x= y= a= b= numMatchingCells= matchMatrixList=;
end;
end;
end;
end;
if numMatchingCells=length(checklist)then do;
do a=1 to 3;
do b=1 to 3;
ab=put(a,1.)||put(b,1.);
if substr(matchMatrixList,1,2) ne ab and
substr(matchMatrixList,3,2) ne ab and
substr(matchMatrixList,5,2) ne ab and
substr(matchMatrixList,7,2) ne ab and
substr(matchMatrixList,9,2) ne ab and
substr(matchMatrixList,11,2) ne ab and
substr(matchMatrixList,13,2) ne ab and
substr(matchMatrixList,15,2) ne ab and
substr(matchMatrixList,17,2) ne ab and
char_guess(v,a,w,b) ne ''  then do;
if indexc(char_guess(v,a,w,b),strip(checklist)) then do;
updated=1;
put 'Row Digits ' checklist= ' removed from ' char_guess(v,a,w,b)=;
*put  v= a= x= w= b= y= matchMatrixList=;
char_guess(v,a,w,b)=compress(char_guess(v,a,w,b),checklist);
end;
end;
end; *b;
end; *a;
if updated > 0 then GOTO MAINLOOP;

end; *numMatchingCells=length(cheklist);
end; *length checklist >1;
end; *y;
end; *x;
end; *w;
end; *v;

%*mend skipthis2;

*-----------------------------------------------------------------------------;
* if all possible spots for a digit within a box occur within 2 rows of the box;
*     and all possible spots for the same digit occur within the same 2 rows  ;
*     of an adjacent box.                                                     ;
*        then the alternate adjacent box cannot have that digit in either of  ;
*        the rows of the other 2 boxes, if this digit is listed as a possible ;
*        value in either of these rows for this box, it is deleted from       ;
*        the list of possible values.                                         ;
*                                                                             ;
*  This logic is repeated for columns as well as rows.                        ;
*-----------------------------------------------------------------------------;
updated=0;
*----------------------------------------------------------------;
* rows                                                           ;
*----------------------------------------------------------------;

do u=1 to 9;
do v=1 to 3; *down puzzle row;
*-----------------------------------------------------------------;
* create list of rows in 1st box of current row containing digit u;
*-----------------------------------------------------------------;
length store_row1 \$3;
store_row1='';
do x=1 to 3; *down  box row   ;
do y=1 to 3; *across box col;
if indexc(char_guess(v,1,x,y),put(u,1.)) then do;
if indexc(store_row1,put(x,1.))<1 then do;
store_row1=strip(store_row1)|| put(x,1.);
end;
end;
end; *y;
end; *x;
*-----------------------------------------------------------------;
* create list of rows in 2nd box of current row containing digit u;
*-----------------------------------------------------------------;
length store_row2 \$3;
store_row2='';
do x=1 to 3; *down  box row   ;
do y=1 to 3; *across box col;
if indexc(char_guess(v,2,x,y),put(u,1.)) then do;
if indexc(store_row2,put(x,1.))<1 then do;
store_row2=strip(store_row2)|| put(x,1.);
end;
end;
end; *y;
end; *x;
*-----------------------------------------------------------------;
* create list of rows in 3rd box of current row containing digit u;
*-----------------------------------------------------------------;
length store_row3 \$3;
store_row3='';
do x=1 to 3; *down  box row   ;
do y=1 to 3; *across box col;
if indexc(char_guess(v,3,x,y),put(u,1.)) then do;
if indexc(store_row3,put(x,1.))<1 then do;
store_row3=strip(store_row3)|| put(x,1.);
end;
end;
end; *y;
end; *x;
*---------------------------------------------------;
* if 2 boxes uses same 2 rows for same digit        ;
*   remove that digit from those 2 rows of other box;
*---------------------------------------------------;
if length(store_row1)=2 and store_row1=store_row2 then do;
*----only remove digit if it exists------;
if indexc(store_row3,strip(store_row1))>0 then do;
do x=1 to 3;
do y=1 to 3;
if indexc(store_row1,put(x,1.))>0 then do;
if indexc(char_guess(v,3,x,y),put(u,1.))>0 then do;
put u= 'Removed from ' char_guess(v,3,x,y)
'because adjacent boxes require this row for this digit.';
char_guess(v,3,x,y)=compress(char_guess(v,3,x,y),put(u,1.));
updated=1;
end;
end;
end; *y;
end;*x;
end;
end; *store_row1=store_row2;
if length(store_row1)=2 and store_row1=store_row3 then do;
*----only remove digit if it exists------;
if indexc(store_row2,strip(store_row1))>0 then do;
do x=1 to 3;
do y=1 to 3;
if indexc(store_row1,put(x,1.))>0 then do;
if indexc(char_guess(v,2,x,y),put(u,1.))>0 then do;
put u= 'Removed from ' char_guess(v,2,x,y)
'because adjacent boxes require this row for this digit.';
char_guess(v,2,x,y)=compress(char_guess(v,2,x,y),put(u,1.));
updated=1;
end;
end;
end; *y;
end;*x;
end;
end; *store_row1=store_row3;
if length(store_row2)=2 and store_row2=store_row3 then do;
*----only remove digit if it exists------;
if indexc(store_row1,strip(store_row2))>0 then do;
do x=1 to 3;
do y=1 to 3;
if indexc(store_row2,put(x,1.))>0 then do;
if indexc(char_guess(v,1,x,y),put(u,1.))>0 then do;
put u= 'Removed from ' char_guess(v,1,x,y)
'because adjacent boxes require this row for this digit.';
char_guess(v,1,x,y)=compress(char_guess(v,1,x,y),put(u,1.));
updated=1;
end;
end;
end; *y;
end;*x;
end;
end; *store_row2=store_row3;
end; *v;
end; *u;

*----------------------------------------------------------------;
* columns                                                        ;
*----------------------------------------------------------------;

do u=1 to 9;
do w=1 to 3; *accross puzzle columns;
*-----------------------------------------------------------------;
* create list of rows in 1st box of current row containing digit u;
*-----------------------------------------------------------------;
length store_col1 \$3;
store_col1='';
do x=1 to 3; *across box col;
do y=1 to 3; *down  box row   ;
if indexc(char_guess(1,w,y,x) ,put(u,1.)) then do;
if indexc(store_col1,put(x,1.))<1 then do;
store_col1=strip(store_col1)|| put(x,1.);
end;
end;
end; *y;
end; *x;
*-----------------------------------------------------------------;
* create list of rows in 2nd box of current row containing digit u;
*-----------------------------------------------------------------;
length store_col2 \$3;
store_col2='';
do x=1 to 3;   *across box col;
do y=1 to 3; *down  box row   ;
if indexc(char_guess(2,w,y,x),put(u,1.)) then do;
if indexc(store_col2,put(x,1.))<1 then do;
store_col2=strip(store_col2)|| put(x,1.);
end;
end;
end; *y;
end; *x;
*-----------------------------------------------------------------;
* create list of rows in 3rd box of current row containing digit u;
*-----------------------------------------------------------------;
length store_col3 \$3;
store_col3='';
do x=1 to 3;  *across box col;
do y=1 to 3; *down  box row   ;
if indexc(char_guess(3,w,y,x),put(u,1.)) then do;
if indexc(store_col3,put(x,1.))<1 then do;
store_col3=strip(store_col3)|| put(x,1.);
end;
end;
end; *y;
end; *x;
*---------------------------------------------------;
* if 2 boxes uses same 2 rows for same digit        ;
*   remove that digit from those 2 rows of other box;
*---------------------------------------------------;
if length(store_col1)=2 and store_col1=store_col2 then do;
*----only remove digit if it exists------;
if indexc(store_col3,strip(store_col1))>0 then do;
do x=1 to 3;
do y=1 to 3;
if indexc(store_col1,put(x,1.))>0 then do;
if indexc(char_guess(3,w,y,x),put(u,1.))>0 then do;
put u= 'Removed from ' char_guess(3,w,y,x)
'because adjacent boxes require this row for this digit.';
char_guess(3,w,y,x)=compress(char_guess(3,w,y,x),put(u,1.));
updated=1;
end;
end;
end; *y;
end;*x;
end;
end; *store_col1=store_col2;
if length(store_col1)=2 and store_col1=store_col3 then do;
*----only remove digit if it exists------;
if indexc(store_col2,strip(store_col1))>0 then do;
do x=1 to 3;
do y=1 to 3;
if indexc(store_col1,put(x,1.))>0 then do;
if indexc(char_guess(2,w,y,x),put(u,1.))>0 then do;
put u= 'Removed from ' char_guess(2,w,y,x)
'because adjacent boxes require this row for this digit.';
char_guess(2,w,y,x)=compress(char_guess(2,w,y,x),put(u,1.));
updated=1;
end;
end;
end; *y;
end;*x;
end;
end; *store_col1=store_col3;
if length(store_col2)=2 and store_col2=store_col3 then do;
*----only remove digit if it exists------;
if indexc(store_col1,strip(store_col2))>0 then do;
do x=1 to 3;
do y=1 to 3;
if indexc(store_col2,put(x,1.))>0 then do;
if indexc(char_guess(1,w,y,x),put(u,1.))>0 then do;
put u= 'Removed from ' char_guess(1,w,y,x)
'because adjacent boxes require this row for this digit.';
char_guess(1,w,y,x)=compress(char_guess(1,w,y,x),put(u,1.));
updated=1;
end;
end;
end; *y;
end;*x;
end;
end; *store_col2=store_col3;
end; *w;
end; *u;
put updated=;

if updated>0 then GOTO MAINLOOP;

*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;
* Check if a solution was found                                               ;
*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;

if solved=.  then   solved=0;  *binary variable to track if the puzzle is solved.;

do i=1 to 3;
do j=1 to 3;
do k= 1 to 3;
do l = 1 to 3;
if boxrowcol(i,j,k,l) =  . then do;
if tryNum=. then tryNum=1;
else             tryNum+1;
solved=0;
put 'A Solution Has Yet to be found ' trynum= boxRowCol(i,j,k,l)=;
*---set exit condition for loop sequence-----;
i=3; j=3; k=3; l=3;
end;
else solved=1;
end;
end;
end;
end;
put solved= tryNum=;

if solved=0 then do;
put solved=;
*----------------------------------------------------------------;
* This is a super-hard puzzle.  (Much harder than printed in     ;
* in newspapers and puzzle books.)                               ;
*    Trial and er ror will now be used to determine if a unique  ;
*    solution exists.  (It is possible that the puzzle has more  ;
*    than one solution, or that the puzzle has no solution at    ;
*    all.)  If a unique solution is not found print the known    ;
*    information and ask the user to check the input values.     ;
*----------------------------------------------------------------;
*------arrays used to store matrix of tried values              ;

array store_guess(45,3,3,3,3) \$9;
array store_boxRowCol(45,3,3,3,3) 3;

*----------note: in 45 tries this program will fill a completely----------;
*----------empty input matrix with consistant values----------------------;
*----------i do not forsee a worse case than this-------------------------;
if trynum>45 then do;
put / ;
put '*********************************************************************************************';
put 'ER' 'ROR: No solution was found.  There is either a flaw in the program logic, or the        ';
put '  ' '     input matrix contains a contradiction(s).';
put '*********************************************************************************************';
%printmatrix;
stop;
end;

*----------------------------------------------------------------;
* If previous guess was made, check if it caused                 ;
*    inconsistant results.  If so, remove that guess from possible;
*    values for that cell and try again.                         ;
*----------------------------------------------------------------;
inconsistant=0;
do i=1 to 3;
do j=1 to 3;
do k= 1 to 3;
do l = 1 to 3;
if boxRowCol(i,j,k,l)=. and char_guess(i,j,k,l)=' ' then do;
inconsistant=1;
end;
end; *l;
end; *k;
end; *j;
end; *i;

if tryNum=1 and inconsistant=1 then do;
put / ;
put '*********************************************************************************************';
put 'ER' 'ROR: The input table contains inconsistant values.  This puzzle has no solution.';
put '  ' '     Check the input matrix and try again.';
put '*********************************************************************************************';
%printmatrix;
stop;
end;

if inconsistant=1 then do;
do i=1 to 3;
do j=1 to 3;
do k= 1 to 3;
do l = 1 to 3;
*------undo last guess---------------------------;
boxRowCol(i,j,k,l)=store_boxRowCol((tryNum-1),i,j,k,l);
char_guess(i,j,k,l)=store_guess((tryNum-1),i,j,k,l);
if boxrowcol(i,j,k,l) = . and RemovedBadValue=0  then do;
*----removing the value that proved inconsistant-;
*----from the list of possible values for that cell;
put 'Bad Value of ' badvalue ' removed from '  char_guess(i,j,k,l) =
' because it is inconsistant with the other table cells. ';
store_guess(tryNum,i,j,k,l)=substr(left(char_guess(i,j,k,l)),2);
char_guess(i,j,k,l)=substr(left(char_guess(i,j,k,l)),2);
if length(char_guess(i,j,k,l))=1 then do;
put;
put 'Cell set to only remaining possible value ' char_guess(i,j,k,l)= ;
put;
end;
end;
*put boxrowcol(i,j,k,l)= char_guess(i,j,k,l)=;
end; *l;
end; *k;
end; *j;
end; *i;
end; *inconsistant=1;

*----------------------------------------------------------------;
* store current matrix so we can revert to it later if geuss     ;
*----------------------------------------------------------------;
do i=1 to 3;
do j=1 to 3;
do k= 1 to 3;
do l = 1 to 3;
store_boxRowCol(tryNum,i,j,k,l)= boxRowCol(i,j,k,l);
store_guess(tryNum,i,j,k,l)    =char_guess(i,j,k,l);
end; *l;
end; *k;
end; *j;
end; *i;
*----------------------------------------------------------------;
* assign guess from possible values for first cell with          ;
*   value not yet known for certain                              ;
*----------------------------------------------------------------;
do i=1 to 3;
do j=1 to 3;
do k= 1 to 3;
do l = 1 to 3;
store_boxRowCol(tryNum,i,j,k,l)= boxRowCol(i,j,k,l);
store_guess(tryNum,i,j,k,l)    =char_guess(i,j,k,l);
if boxrowcol(i,j,k,l) = . then do;
%printmatrix;
guess_len=length(char_guess(i,j,k,l));

boxRowCol(i,j,k,l)=input(substr(left(char_guess(i,j,k,l)),1,1),3.);
if length(char_guess(i,j,k,l))>1 then do;
put;
put 'Trial and Er' 'ror, Trying value ' boxRowCol(i,j,k,l)=
'of possible values ' char_guess(i,j,k,l)=;
put;
end;
lastGuessValue=boxRowCol(i,j,k,l);
char_guess(i,j,k,l)='';
GOTO MAINLOOP;
end;
end; *l;
end; *k;
end; *j;
end; *i;

end; *solved;

if solved=1 and tryNum>0 then do;
put /;
put '**********************************************************************************************';
put '* NOTE:  Multiple solutions may exist for this puzzle. Only one solution is shown.           *';
put '**********************************************************************************************';
end;
if solved=1 and tryNum=0 then do;
put /;
put '**********************************************************************************************';
put '* NOTE:  A unique solution was found.  No other solutions exist for this puzzle.             *';
put '**********************************************************************************************';
end;

%printmatrix;

run;```