Sudoku/REXX: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎$SUDOKU.REX: added the original purpose of the REXX program.)
m (→‎$SUDOKU.REX: changed the output from a intermediate display output (two places).)
Line 555: Line 555:
!.r.pc=new /*store new value into old.*/
!.r.pc=new /*store new value into old.*/
foundmatch=1 /*indicate match was found.*/
foundmatch=1 /*indicate match was found.*/
call $t !fn 'is removing a' L "from" drc(r,pc,old),
call $t !fn 'is removing a' old "from" drc(r,pc,old),
'because of a match at' drc(r,c,qq)
'because of a match at' drc(r,c,qq)
if length(new)==1 then do /*reduce if L=1*/
if length(new)==1 then do /*reduce if L=1*/
Line 578: Line 578:
!.pr.c=new /*store new value into old.*/
!.pr.c=new /*store new value into old.*/
foundmatch=1 /*indicate match was found.*/
foundmatch=1 /*indicate match was found.*/
call $t !fn 'is removing a' L "from" drc(pr,c,old),
call $t !fn 'is removing a' old "from" drc(pr,c,old),
'because of a match at' drc(r,c,qq)
'because of a match at' drc(r,c,qq)
if length(new)==1 then do /*reduce if L=1*/
if length(new)==1 then do /*reduce if L=1*/

Revision as of 19:08, 22 September 2014

$SUDOKU.REX

This is the   $SUDOKU.REX   (REXX) program and is used to solve the Rosetta Code task of "sudoku".

This REXX program was originally written to assist in sudoku puzzle solving (by giving strong hints), and not to solve the puzzle outright.
The REXX program was written to give increasing better hints and also show the possibilities (of what is possible solution for any cell),
and to partially solve the puzzle using distinct strategies (separately or in combination).   One option is to solve the puzzle.

The help for the   $SUDOKU   REXX program is included here ──► $SUDOKU.HEL.
The   $SUDOKU.REX   REXX program makes use of   $ERR.REX   REXX program which is used to display error messages (via   $T.REX).
The   $ERR.REX   REXX program is included here ──► $ERR.REX.
The   $SUDOKU.REX   REXX program makes use of   $T.REX   REXX program which is used to display text messages.
The   $T.REX   REXX program is included here ──► $T.REX.
Some older REXXes don't have a   changestr   BIF, so one is included here ──► CHANGESTR.REX.
REXX programs not included are   $H   which shows help and other documentation. <lang rexx>/*REXX program displays, can give hints, and/or solve a sudoku puzzle. */ trace off parse arg ! if !all(arg()) then exit if !cms then address signal on halt signal on noValue signal on syntax

ops=! /*remove extraneous blanks.*/ numeric digits 20 combos=1 @.=' ' /*initialize grid to blanks*/ !.= /*nullify valid empty# list*/ @abc='abcdefghijklmnopqrstuvwxyz' @abcU=@abc upper @abcU colors=!cms | !pcrexx | !r4 | !roo /*Are colors supported ? */ clear=1 /*option: clear the screen.*/ highLight=0 /*option: highlight singles*/ pruneall=0 /*option: prune all. */ prunemats=0 /*option: prune matches. */ prunesing=0 /*option: prune singles. */ pruneexcl=0 /*option: prune exclusives.*/ pruneline=0 /*option: prune lines. */ pruneonly=0 /*option: prune onlys. */ simple=0 /*option: show simple boxes*/ showoneline=0 /*option: show grid as1line*/ showgrid=1 /*option: show the grid. */ showinfo=1 /*option: show informatiion*/ showposs=0 /*option: show possible val*/ showcomb=0 /*option: show combinations*/ showrow= /*option: SHOWPOSS for rowN*/ showcol= /*option: SHOWPOSS for colN*/ showbox= /*option: SHOWPOSS for boxN*/ showcell= /*option: SHOWPOSS cellRC */ short=0 solve=0 /*option: solve the puzzle.*/ sod=lower(translate(!fn,,'$')) /*name of the puzzle. */ tellinvalid=1 /*tell err msg if invalid X*/ tops= /*option: used for $T opts.*/

gridindents=3 /*# spaces grid is indented*/ gridindent=left(,gridindents) /*spaces indented for grid.*/ gridwidth=7 /*grid cell interior width.*/ gridbar='b3'x /*bar for the grid (cells).*/ gridlt='da'x /*grid cell left top. */ gridrt='bf'x /*grid cell right top. */ gridlb='c0'x /*grid cell left bottom. */ gridrb='d9'x /*grid cell right bottom. */ gridline='c4'x /*grid cell line (hyphen). */ gridlin=copies(gridline,gridwidth) /*grid cell total line. */ gridemp=left(,gridwidth) /*grid cell empty (spaces).*/ griddj='c2'x /*grid cell down junction.*/ griduj='c1'x /*grid cell up junction.*/ gridlj='c3'x /*grid cell left junction.*/ gridrj='b4'x /*grid cell right junction.*/ gridcross='c5'x /*grid cell cross junction.*/

 do  while ops\==                         /*parse any and all options*/
 parse var ops _1 2 1 _ . 1 _o ops
 upper _
   select
   when _==','                    then nop
   when _1=='.' & pos("=",_)\==0  then tops=tops _o
   when  abb('PUZzle')            then      /*do PUZZ (whole) placement*/
        do
        puzz=na()
        if length(puzz)>81  then call er 30,puzz 'PUZZLE 1───►81'
                        do j=1  for length(puzz)
                        q=substr(puzz,j,1)
                        if q=='.'  then iterate
                        call vern q,'PUZZLE_digit'
                        c=j//9
                        if c==0  then c=9
                        r=(j-1)%9 + 1
                        @.r.c=q
                        end   /*j*/
        end
   when _=='CELL' then                      /*do CELL (grid) placement.*/
        do
        rc=nai()
        if length(rc)\==2                   then call er 30,y 'CELL'rc 2
        y=na()
        if length(y)>1                      then call er 30,y 'CELL'rc 1
        r=left(rc,1)
        c=right(rc,1)
        call vern r,'CELLrow'
        call vern c,'CELLcolumn'
        call vern y,'CELLdigit'
        @.r.c=y
        end
   when  abb('COLumn')  then                /*do ROW (grid) placement. */
        do
        n=nai()
        y=na()
        call vern n,'column'
        ly=length(y)
        if ly>9  then call er 30,y 'column'n '1───>9'
                        do j=1  for ly
                        x=substr(y,j,1)
                        if x== | x=="_" | x=='*' | x=="."  then iterate
                        if \isInt(x)  then call er 92,x 'cell_for_column'n
                        @.j.n=x
                        end   /*j*/
        end
   when  abb('ROW')  then                   /*do ROW (grid) placement. */
        do
        n=nai()
        y=na()
        call vern n,'row'
        ly=length(y)
        if ly>9  then call er 30,y 'row'n '1───>9'
                        do j=1  for ly
                        x=substr(y,j,1)
                        if x== | x=="_" | x=='*' | x=="."  then iterate
                        if \isInt(x)  then call er 92,x 'cell_for_row_'n
                        @.n.j=x
                        end   /*j*/
        end
   when abbn('CLearscreen')        then clear=no()
   when abbn('HIGHLightsingles')   then highLight=no()
   when abbn('PRUNEALL')           then pruneall=no()
   when abbn('PRUNEONLYs')         then pruneonly=no()
   when abbn('PRUNEEXclusives')    then pruneexcl=no()
   when abbn('PRUNELINEs')         then pruneline=no()
   when abbn('PRUNEMATches')       then prunemats=no()
   when abbn('PRUNESINGles')       then prunesing=no()
   when abbn('SIMPle')             then simple=no()
   when  abb('SHOWBOXes')|,
         abb('SHOWBOXs')           then showbox=nai()
   when  abb('SHOWCELLs')          then showcell=translate(na(),,',')
   when  abb('SHOWCOLs')           then showcol=nai()
   when abbn('SHOWCOMBinations')   then showcomb=no()
   when abbn('SHOWGrid')           then showgrid=no()
   when abbn('SHOWINFOrmation')    then showinfo=no()
   when abbn('SHOWONELINE')        then showoneline=no()
   when abbn('SHOWPOSSibles') then showposs=no()
   when  abb('SHOWROWs')           then showrow=nai()
   when abbn('SHortgrid')          then short=no()
   when abbn('SOLvepuzzle')        then solve=no()
   otherwise                       call er 55,_o
   end   /*select*/
 end     /*while ops¬==*/

if solve then pruneall=1 /*if solving, use PRUNEALL.*/

if pruneall then do /*if pruneAll, set ON other*/

                 pruneexcl=1
                 pruneonly=1
                 pruneline=1
                 prunemats=1
                 prunesing=1
                 end

aprune = , /*is there a PRUNExxx on ? */

                 pruneexcl |,
                 pruneonly |,
                 pruneline |,
                 prunemats |,
                 prunesing

if highLight then do /*HIGHLIGHTSINGLES opt on? */

                  hLl='-'
                  hLr='-'
                  if colors  then do
                                  hLl='('
                                  hLr=')'
                                  tops='.H=yell' tops
                                  end
                  end

tops=space(tops) box.=

 do j=1  for 9                              /*build the box bounds.    */
 rr=(((j*3)%10)+1)*3-2                      /*compute row lower bound. */
 cc=(((j-1)//3)+1)*3-2                      /*compute col lower bound. */
 boxr.j=rr
 boxc.j=cc
                       do   r=rr  to rr+2   /*build boxes with cell #s.*/
                         do c=cc  to cc+2
                         rc=r || c
                         box.j=box.j rc
                         box.rc=j
                         end   /*c*/
                       end     /*r*/
 box.j=strip(box.j)
 end   /*j*/

rowlb.=10 /*row R, low box number=b.*/ collb.=10 /*col R, low box number=b.*/ boxlr.=10 /*box B, low row number=r.*/ boxlc.=10 /*box B, low col number=c.*/

 do   r=1  for 9
   do c=1  for 9
   rc=r || c
   b=box.rc                                 /*what box is this R,C in ?*/
   rowlb.r=min(rowlb.r,b)                   /*find min box # for row R.*/
   collb.c=min(collb.c,b)                   /*find min box # for col C.*/
   boxlr.b=min(boxlr.b,r)                   /*find min row # for box B.*/
   boxlc.b=min(boxlc.b,c)                   /*find min col # for box B.*/
   end   /*c*/
 end     /*r*/
do j=1  to 9                                /*for each box, row, col...*/
rowhb.j=rowlb.j+2                           /*compute row's high box #.*/
colhb.j=collb.j+6                           /*compute col's high box #.*/
boxhr.j=boxlr.j+2                           /*compute box's high row #.*/
boxhc.j=boxlc.j+6                           /*compute box's high col #.*/
end   /*j*/

if showgrid then call showgrid 'the puzzle' /*show the grid to screen ?*/ if \validall() then exit /*validate specified digits*/ tellinvalid=0 /*don't tell err messages. */ !.= /*nullify valid empty# list*/ call buildposs /*build possible values. */ if showposs then call showgrid 'puzzle possibles' /*show 1st possibles?*/ if \validate(1) then exit /*validate the puzzle. */

if showoneline then do /*show grid as line line ? */

                    _=                      /*start with a clean slate.*/
                          do   r=1  for 9
                            do c=1  for 9
                            _=_ || @.r.c    /*build the string ...     */
                            end   /*c*/
                          end     /*r*/
                    _=translate(strip(_,'T'),".",' ')
                    if showinfo  then call $T 'one-line grid:'
                    call $T _
                    end

if aprune |,

  showposs then do
                call pruneposs              /*go build poss, then prune*/
                if showposs then call showgrid 'possibles' /*show grid.*/
                if \validate(1) then exit   /*validate the puzzle.     */
                end

if combos==1 then call $t sod 'puzzle solved.'

             else  if showcomb  then call $t 'combinations='comma(combos)

exit /*stick a fork in it, we're done.*/

/*─────────────────────────────vern subroutine──────────────────────────*/ vern: parse arg v,w /*verify a digit for an opt*/ if v== then call er 35,v w if \isInt(v) then call er 92,v w if v<1 | v>9 then call er 81,1 9 v w return

/*─────────────────────────────buildposs subroutine─────────────────────*/ buildposs: !.= /*nullify possibilities. */ combos=1

 do   rp=1  for 9                           /*build table of valid #s. */
   do cp=1  for 9                           /*step through each column.*/
   if @.rp.cp\==' '  then iterate           /*not blank?  Keep looking.*/
                         do jd=1  for 9     /*try each digit.          */
                         @.rp.cp=jd
                         if validx(rp,cp) then !.rp.cp=!.rp.cp || jd
                         end   /*jd*/
   combos=combos*length(!.rp.cp)            /*calculate # combinations.*/
   @.rp.cp=' '                              /*restore the point (blank)*/
   end      /*cp*/
 end        /*rp*/

return

/*─────────────────────────────showgrid subroutine──────────────────────*/ showgrid: parse arg title if clear then !cls /*clear the screen ? */ if title\== & showinfo then call $t !fn 'is showing' title gtail=copies3(gridlb || gridlin || copies2(griduj || gridlin) || gridrb) ghead=copies3(gridlt || gridlin || copies2(griddj || gridlin) || gridrt) call tg ghead gemp=copies3(copies3(gridbar || gridemp)gridbar) grid=copies3(gridlj || gridlin || copies2(gridcross || gridlin)gridrj) anyshow= \ ((showcell || showcol || showrow || showbox)\==)

 do jr=1  for 9
 if \short  then call tg gemp
 gnum=
   do jc=1  for 9
   _=@.jr.jc
   if _\==' ' & highLight  then _=hLl || _ || hLr
   if _==' ' & ,
      showposs  then do
                     jrjc=jr || jc
                     showit=anyshow
                     if showcell\== then if wordpos(jrjc,showcell)\==0 then showit=1
                     if showcol\==  then if pos(jc,showcol)\==0  then showit=1
                     if showrow\==  then if pos(jr,showrow)\==0  then showit=1
                            do jb=1  while  showbox\==
                            b=substr(showbox,jb,1)
                            if b==' '                   then leave
                            if wordpos(jrjc,box.b)\==0  then showit=1
                            end   /*jb*/
                     if showit  then _=strip(left(!.jr.jc,gridwidth),'T')
                     end
   gnum=gnum || gridbar || centre(_,gridwidth)
   if jc//3==0  then gnum=gnum || gridbar
   end   /*jc*/
 call tg gnum
 if \short  then call tg gemp
 if jr//3==0 then do
                  call tg gtail
                  if jr\==9  then call tg ghead
                  end
             else call tg grid
 end   /*jr*/

call $t return

/*─────────────────────────────validate subroutine──────────────────────*/ validate: /*are all empties possible?*/

 do   r=1  for 9                            /*step through each row.   */
   do c=1  for 9                            /*step through each column.*/
   if @.r.c==' ' & ,
      !.r.c==  then do                     /*no legal digit here.     */
                      if arg(1)==1  then call $t sod "puzzle isn't valid !"
                      return 0
                      end
   end   /*c*/
 end     /*r*/                              /*sub requires possibles.  */

return 1 /*indicate puzzle is valid.*/

/*─────────────────────────────validall subroutine──────────────────────*/ validall: /*validate all Q specified.*/

 do   r=1  for 9                            /*step through each row.   */
   do c=1  for 9                            /*step through each column.*/
   if @.r.c==' '  then iterate              /*if blank, then it's ok.  */
   y=                                       /*the rest of the row.     */
   rc=r||c
               do kc=1  for 9               /*compare to #s in column. */
               if kc\==c  then y=y|| @.r.kc /*build the rest of the row*/
               end   /*kc*/
   q=@.r.c
   if pos(q,y)\==0  then return tem(r,c,'row')    /*same # in same row?*/
   y=                                       /*the rest of the column.  */
               do kr=1  for 9               /*compare to #s in column. */
               if kr\==r then y=y || @.kr.c /*build the rest of the col*/
               end      /*kr*/
   if pos(q,y)\==0  then return tem(r,c,'col')    /*same # in same col?*/
   y=                                       /*the rest of the box.     */
   b=box.rc
     do   br=boxr.b  to boxr.b+2            /*compare to #s of the box.*/
       do bc=boxc.b  to boxc.b+2            /*build the rest of the box*/
       if br\==r & bc\==c  then y=y || @.br.bc
       end  /*bc*/
     end    /*br*/
   if pos(q,y)\==0  then return tem(r,c,'box')    /*same # in same box?*/
   end   /*c*/
 end     /*r*/

return 1 /*indicate all are valid.*/

/*─────────────────────────────validx subroutine────────────────────────*/ validx: arg r,c rc=r || c y= /*the rest of the row. */

          do kc=1  for 9                    /*compare to #s in column. */
          if kc\==c  then y=y || @.r.kc     /*build the rest of the row*/
          end   /*kc*/

q=@.r.c /*get the digit at r,c */ if pos(q,y)\==0 then return tem(r,c,'row') /*same number in same row ?*/ y= /*the rest of the column. */

     do kr=1  for 9                         /*compare to #s in column. */
     if kr\==r  then y=y || @.kr.c          /*build the rest of the col*/
     end   /*kr*/

if pos(q,y)\==0 then return tem(r,c,'col') /*same # in same column ?*/ y= /*the rest of the box. */ b=box.rc

          do   br=boxr.b  to boxr.b+2       /*compare to #s of the box.*/
            do bc=boxc.b  to boxc.b+2       /*build the rest of the box*/
            if br==r & bc==c  then iterate
            y=y || @.br.bc
            end   /*br*/
          end     /*bc*/

if pos(q,y)\==0 then return tem(r,c,'box') /*same # in same box ? */ return 1 /*indicate X (r,c) is valid*/

/*─────────────────────────────pruneposs subroutine─────────────────────*/ pruneposs: if \(prunesing | pruneexcl | prunemats | pruneline) then return call buildposs

 do prunes=1
 call $t !fn 'is starting prune pass #' prunes
 found=0                                    /*indicate no prunes so far*/
 if prunesing then do                       /*prune puzzle for singles.*/
                   _=prunesing()            /*find any singles ?       */
                   found=found | _          /*track if anything found. */
                   if _ then if showgrid then call showgrid /*show grid*/
                   end
 if pruneexcl then do                       /*prune puzzle for singles.*/
                   _=pruneexcl()            /*find any excluives ?     */
                   found=found | _          /*track if anything found. */
                   if _ then if showgrid then call showgrid /*show grid*/
                   end
 if pruneonly then do                       /*prune puzzle for onlys.  */
                   _=pruneonly()            /*find any onlys ?         */
                   found=found | _          /*track if anything found. */
                   if _ then if showgrid then call showgrid /*show grid*/
                   end
 if prunemats then do jpm=2 to 8            /*prune puzzle for matches.*/
                   _=prunemats(jpm)         /*find any matches (len=j)?*/
                   found=found | _          /*track if anything found. */
                   if _ then if showgrid then call showgrid /*show grid*/
                   end
 if pruneline then do                       /*prune puzzle for lines.  */
                   _=pruneline()            /*find 2 or more on a line?*/
                   found=found | _          /*track if anything found. */
                   if _ then if showgrid then call showgrid /*show grid*/
                   end
 if \found then leave                       /*nothing found this time ?*/
 end    /*prunes*/

return

/*─────────────────────────────prunesing subroutine─────────────────────*/ prunesing: foundsing=0

   do   r=1  for 9
     do c=1  for 9
     _=length(!.r.c)                        /*get length of possible.  */
     if _==0   then iterate                 /*if null, then ignore it. */
     if _\==1  then iterate                 /*if not one digit, ignore.*/
     @.r.c=!.r.c                            /*it's 1 digit, a solution.*/
     !.r.c=                                 /*erase the old possible.  */
     foundsing=1
     call $t !fn 'found a single digit at cell' drc(r,c,@.r.c)
     end   /*c*/
   end     /*r*/

if foundsing then call buildposs /*re-build the possibles. */ return foundsing

/*─────────────────────────────pruneexcl subroutine─────────────────────*/ pruneexcl: foundexcl=0

 do exclusives=1                            /*keep building possibles. */
   do   r=1  for 9
     do c=1  for 9
     z=!.r.c
     lz=length(z)                           /*get length of possible.  */
     if lz==0  then iterate                 /*if null, then ignore it. */
     y=
     rc=r || c
     b=box.rc
               do   br=boxr.b  to boxr.b+2  /*compare to #s of the box.*/
                 do bc=boxc.b  to boxc.b+2  /*build the rest of the box*/
                 if br==r & bc==c  then iterate
                 y=y || @.br.bc || !.br.bc
                 end   /*bc*/
               end     /*br*/
                                            /*test for reduction.      */
      do t=1  for lz
      q=substr(z,t,1)
      if pos(q,y)==0 then do
                          foundexcl=1
                          @.r.c=q           /*it's a singularity, a sol*/
                          !.r.c=            /*erase old possibleity.   */
                          call $t !fn 'found the digit' q,
                                 "by exclusiveness at cell" drc(r,c,z)
                          call buildposs    /*re-build the possibles.  */
                          iterate exclusives
                          end
      end  /*t*/
     end   /*c*/
   end     /*r*/
 leave
 end       /*exclusives*/

return foundexcl

/*─────────────────────────────prunemats subroutine─────────────────────*/ prunemats: foundmatch=0 /*no matches found so far. */ parse arg L /*length of match, L=2,pair*/

 do matches=1
   do   r=1  for 9
     do c=1  for 9
     _=length(!.r.c)                        /*get length of possible.  */
     if _==0   then iterate                 /*if null, then ignore it. */
     if _\==L  then iterate                 /*not right length, ignore.*/
     qq=!.r.c
     m=0                                    /*count of matches so far. */
            do _c=1  for 9                  /*nother match in same row?*/
            if qq==!.r._c  then m=m+1       /*up count if it's a match.*/
            end  /*_c*/
     if m>=L then do pc=1  for 9            /*squish other possibles.  */
                  old=!.r.pc                /*save the "old" value.    */
                  if old==qq   then iterate /*if match, then ignore it.*/
                  if old==   then iterate /*if null poss, then ignore*/
                  new=squish(old,qq)        /*remove mat's digs from X.*/
                  if new==old  then iterate /*if no change,keep looking*/
                  !.r.pc=new                /*store new value into old.*/
                  foundmatch=1              /*indicate match was found.*/
                  call $t !fn 'is removing a'  old "from"  drc(r,pc,old),
                              'because of a match at'     drc(r,c,qq)
                  if length(new)==1 then do             /*reduce if L=1*/
                                         @.r.pc=new     /*store single.*/
                                         !.r.pc=        /*delete poss. */
                                         call buildposs /*re-build poss*/
                                         iterate matches  /*start over.*/
                                         end
                  end    /*pc*/
     m=0                                    /*count of matches so far. */
            do _r=1  for 9                  /*nother match in same col?*/
            if qq==!._r.c  then m=m+1       /*up count if it's a match.*/
            end   /*_r*/
     if m>=L then do pr=1  for 9            /*squish other possibles.  */
                  old=!.pr.c                /*save the "old" value.    */
                  if old==qq then iterate   /*if match, then ignore it.*/
                  if old== then iterate   /*if null poss, then ignore*/
                  new=squish(old,qq)        /*remove mat's digs from X.*/
                  if new==old then iterate  /*if no change,keep looking*/
                  !.pr.c=new                /*store new value into old.*/
                  foundmatch=1              /*indicate match was found.*/
                  call $t !fn 'is removing a'  old  "from" drc(pr,c,old),
                              'because of a match at' drc(r,c,qq)
                  if length(new)==1 then do             /*reduce if L=1*/
                                         @.pr.c=new     /*store single.*/
                                         !.pr.c=        /*delete poss. */
                                         call buildposs /*re-build poss*/
                                         iterate matches  /*start over.*/
                                         end
                  end   /*pr*/
     end                /*c*/
   end                  /*r*/
 leave
 end                    /*matches*/

return foundmatch

/*─────────────────────────────pruneonly subroutine─────────────────────*/ pruneonly: foundmatch=0 /*no matches found so far. */

 do findonlys=1                             /*keep searching ...       */
 _row.=                                     /*build str for each row . */
          do   r=1  for 9
            do c=1  for 9
            if !.r.c\==  then _row.r=_row.r !.r.c
            end   /*c*/
          end     /*r*/
 _col.=                                     /*build str for each boxcol*/
          do   c=1  for 9
            do r=1  for 9
            if !.r.c\==  then _col.c=_col.c !.r.c
            end   /*r*/
          end     /*c*/
   do   r=1  for 9
     do c=1  for 9
     q=!.r.c
     if q==  then iterate                 /*if empty, then ignore it.*/
       do j=1  to length(q)                 /*step through each digit. */
       k=substr(q,j,1)
       if kount1(k,_row.r) |,               /*is this the ONLY digit K?*/
          kount1(k,_col.c) then do i=1  to length(q)    /*prune others.*/
                                foundmatch=1
                                _=substr(q,i,1)
                                if _==k  then iterate   /*if=K, ignore.*/
                                o=squish(q,_)           /*remove others*/
                                !.r.c=o
                                call $t !fn  'removed part of an only',
                                             _   "from cell"   drc(r,c,q)
                                if length(o)==1  then   /*reduce if L=1*/
                                  do
                                  @.r.c=o               /*store single.*/
                                  !.r.c=                /*delete poss. */
                                  call buildposs        /*re-build poss*/
                                  iterate findonlys     /*start over.  */
                                  end
                                end   /*i*/
       end   /*j*/
     end     /*c*/
   end       /*r*/
 leave
 end     /*findonlys*/

return foundmatch

/*─────────────────────────────pruneline subroutine─────────────────────*/ pruneline: foundmatch=0 /*no matches found so far. */

do findlines=1                              /*keep searching ...       */
_boxr.=                                     /*build str for each boxrow*/
          do   r=1  for 9
            do c=1  for 9
            rc=r || c
            b=box.rc
            if !.r.c\==  then _boxr.r.b=strip(_boxr.r.b !.r.c)
            end   /*c*/
          end     /*r*/
 _boxc.=                                    /*build str for each boxcol*/
          do   c=1  for 9
            do r=1  for 9
            rc=r || c
            b=box.rc
            if !.r.c\==  then _boxc.c.b=strip(_boxc.c.b !.r.c)
            end   /*r*/
          end     /*c*/
 do r=1  for 9                              /*search all rows for twins*/
   do b=rowlb.r  to rowhb.r                 /*for each row, search box.*/
   aline=_boxr.r.b                          /*get a  row  in the box.  */
   if aline==  then iterate               /*if empty, ignore the line*/
   w=words(aline)                           /*W  is # of words in aline*/
   if w<2        then iterate               /*if < 2 words, ignore line*/
     do k=1  for 9                          /*search for each digit.   */
     f=pos(k,aline)                         /*pos of the 1st digit:  k */
     if f==0  then  iterate                 /*no dig k, so keep looking*/
     s=pos(k,aline,f+1)                     /*pos of the 2nd digit:  k */
     if s==0  then  iterate                 /*no 2nd k, so keep looking*/
       do jr=rowlb.r  to rowhb.r            /*look at the other 2 rows.*/
       if jr==r  then  iterate              /*if the same row, ignore. */
       if pos(k,_boxr.jr.b)\==0  then iterate k /*if no digit K, ignore*/
       end   /*jr*/
                                            /*found 2 Ks in row R box B*/
        do jb=rowlb.r  to rowhb.r           /*search boxes row R for K.*/
        if jb==b  then iterate              /*ignore if in the same box*/
        if pos(k,_boxr.r.jb)==0  then iterate
        foundmatch=1                        /*found a K in col C box JB*/
          do kc=1  for 9                    /*find which cell  K is in.*/
          rc=r || kc
          if box.rc==b        then iterate  /*ignore if in the same box*/
          _=!.r.kc
          if _==            then iterate  /*ignore if no possible.   */
          if pos(k,_)==0      then iterate  /*if no digit  K,  ignore. */
          call $t  !fn   'is row-line pruning digit' k,
                         'from cell'    drc(r,kc,!.r.kc)
          !.r.kc=squish(_,k)                /*remove mat's digs from X.*/
          if length(!.r.kc)==1  then do     /*pruned down to one digit?*/
                                     @.r.kc=!.r.kc  /*make a true digit*/
                                     !.r.kc=        /*erase possibility*/
                                     call buildposs /*rebuild possibles*/.
                                     iterate findlines
                                     end
          end   /*kc*/
        end     /*jb*/
     end        /*k*/
   end          /*b*/
 end            /*r*/
 do c=1  for 9                              /*search all cols for twins*/
   do b=collb.c  to colhb.c  by 3           /*for each col, search box.*/
   aline=_boxc.c.b                          /*get a  column in the box.*/
   if aline==  then iterate               /*if empty, ignore line*/
   w=words(aline)
   if w<2  then iterate                     /*if < 2 words, ignore line*/
     do k=1  for 9                          /*search for each digit.   */
     f=pos(k,aline)                         /*pos of the 1st digit:  k */
     if f==0  then iterate                  /*no dig k, so keep looking*/
     s=pos(k,aline,f+1)                     /*pos of the 2nd digit:  k */
     if s==0  then iterate                  /*no 2nd k, so keep looking*/
       do jc=boxlc.b  to boxhc.b            /*look at the other 2 cols.*/
       if jc==c  then iterate               /*if the same col, ignore. */
       if pos(k,_boxc.jc.b)\==0  then iterate k /*if no digit K, ignore*/
       end   /*jc*/
                                            /*found 2 Ks in col C box B*/
        do jb=collb.c  to colhb.c by 3      /*search boxes col C for K.*/
        if jb==b  then iterate              /*ignore if in the same box*/
        if pos(k,_boxc.c.jb)==0  then iterate
        foundmatch=1                        /*found a K in col C box JB*/
          do kr=1  for 9                    /*find which cell  K is in.*/
          rc=kr || c
          if box.rc==b       then iterate   /*ignore if in the same box*/
          _=!.kr.c
          if _==           then iterate   /*ignore if no possible.   */
          if pos(k,_)==0     then iterate   /*if no digit  K,  ignore. */
          call $t !fn  'is col-line pruning digit'  k,
                       'from cell'    drc(kr,c,!.kr.c)
          !.kr.c=squish(_,k)                /*remove mat's digs from X.*/
          if length(!.kr.c)==1  then do     /*pruned down to one digit?*/
                                     @.kr.c=!.kr.c  /*make a true digit*/
                                     !.kr.c=        /*erase possibility*/
                                     call buildposs /*rebuild possibles*/.
                                     iterate findlines
                                     end
          end   /*kr*/
        end     /*jb*/
     end        /*k*/
   end          /*b*/
 end            /*c*/
leave
end     /*findlines*/

return foundmatch

/*═════════════════════════════general 1-line subs════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════*/ !all:!!=!;!=space(!);upper !;call !fid;!nt=right(!var('OS'),2)=='NT';!cls=word('CLS VMFCLEAR CLRSCREEN',1+!cms+!tso*2);if arg(1)\==1 then return 0;if wordpos(!,'? ?SAMPLES ?AUTHOR ?FLOW')==0 then return 0;!call=']$H';call '$H' !fn !;!call=;return 1 !cal: if symbol('!CALL')\=="VAR" then !call=; return !call !env: !env='ENVIRONMENT'; if !sys=='MSDOS' | !brexx | !r4 | !roo then !env='SYSTEM'; if !os2 then !env='OS2'!env; !ebcdic=1=='f0'x; if !crx then !env='DOS'; return !fid: parse upper source !sys !fun !fid . 1 . . !fn !ft !fm .; call !sys; if !dos then do; _=lastpos('\',!fn); !fm=left(!fn,_); !fn=substr(!fn,_+1); parse var !fn !fn '.' !ft; end; return word(0 !fn !ft !fm,1+('0'arg(1))) !rex: parse upper version !ver !vernum !verdate .; !brexx='BY'==!vernum; !kexx='KEXX'==!ver; !pcrexx='REXX/PERSONAL'==!ver | 'REXX/PC'==!ver; !r4='REXX-R4'==!ver; !regina='REXX-REGINA'==left(!ver,11); !roo='REXX-ROO'==!ver; call !env; return !sys: !cms=!sys=='CMS'; !os2=!sys=='OS2'; !tso=!sys=='TSO' | !sys=='MVS'; !vse=!sys=='VSE'; !dos=pos('DOS',!sys)\==0 | pos('WIN',!sys)\==0 | !sys=='CMD'; !crx=left(!sys,6)=='DOSCRX'; call !rex; return !var: call !fid; if !kexx then return space(dosenv(arg(1))); return space(value(arg(1),,!env)) $fact!: procedure; parse arg x _ .; l=length(x); n=l-length(strip(x,'T',"!")); if n<=-n | _\== | arg()\==1 then return x; z=left(x,l-n); if z<0 | \isInt(z) then return x; return $fact(z,n) $fact: procedure; parse arg x _ .; arg ,n ! .; n=p(n 1); if \isInt(n) then n=0; if x<-n | \isInt(x) | n<1 | _ || !\== | arg()>2 then return x || copies("!",max(1,n)); !=1; s=x//n; if s==0 then s=n; do j=s to x by n; !=!*j; end; return ! $sfxa: parse arg ,s,m; arg u,c; if pos(left(s,2),u)\==0 then do j=length(s) to compare(s,c)-1 by -1; if right(u,j)\==left(c,j) then iterate; _=left(u,length(u)-j); if isNum(_) then return m*_; leave; end; return arg(1) $sfxf: parse arg y; if right(y,1)=='!' then y=$fact!(y); if \isNum(y) then y=$sfxz(); if isNum(y) then return y; return $sfxm(y) $sfxm: parse arg z; arg w; b=1000; if right(w,1)=='I' then do; z=shorten(z); w=z; upper w; b=1024; end; p=pos(right(w,1),'KMGTPEZYXWVU'); if p==0 then return arg(1); n=shorten(z); r=num(n,f,1); if isNum(r) then return r*b**p; return arg(1) $sfxz: return $sfxa($sfxa($sfxa($sfxa($sfxa($sfxa(y,'PAIRs',2),'DOZens',12),'SCore',20),'GREATGRoss',1728),'GRoss',144),'GOOGOLs',1e100) $t: if tops== then say arg(1); else do; !call=']$T'; call "$T" tops arg(1); !call=; end; return ab: arg ab,abl; return abbrev(ab,_,abl) abb: arg abbu; parse arg abb; return abbrev(abbu,_,abbl(abb)) abbl: return verify(arg(1)'a',@abc,'M')-1 abbn: parse arg abbn; return abb(abbn) | abb('NO'abbn) abn: arg ab,abl; return abbrev(ab,_,abl) | abbrev('NO'ab,_,abl+2) comma: procedure; parse arg _,c,p,t; c=pickBlank(c,","); o=p(p 3); p=abs(o); t=p(t 999999999); if \isInt(p) | \isInt(t) | p==0 | arg()>4 then return _; n=_'.9'; #=123456789; k=0; return comma_() comma_: if o<0 then do; b=verify(_,' '); if b==0 then return _; e=length(_)-verify(reverse(_),' ')+1; end; else do; b=verify(n,#,"M"); e=verify(n,#'0',,verify(n,#"0.",'M'))-p-1; end; do j=e to b by -p while k<t; _=insert(c,_,j); k=k+1; end; return _ copies2: return copies(arg(1),2) copies3: return copies(arg(1),3) drc: procedure; parse arg r,c,p; _=r","c; if p\== then _=_ "("p')'; return _ er: parse arg _1,_2; call '$ERR' "14"p(_1) p(word(_1,2) !fid(1)) _2; if _1<0 then return _1; exit result err: call er '-'arg(1),arg(2); return erx: call er '-'arg(1),arg(2); exit halt: call er .1 int: int=num(arg(1),arg(2)); if \isInt(int) then call er 92,arg(1) arg(2); return int/1 isInt: return datatype(arg(1),'W') isNum: return datatype(arg(1),'N') kount1: parse arg qd,string; k1=pos(qd,string); if k1==0 then return 0; return pos(qd,string,k1+1)==0 lower: return translate(arg(1),@abc,translate(@abc)) na: if arg(1)\== then call er 01,arg(2); parse var ops na ops; if na== then call er 35,_o; return na nai: return int(na(),_o) nail: return squish(int(translate(na(),0,','),_o)) nan: return num(na(),_o) no: if arg(1)\== then call er 01,arg(2); return left(_,2)\=='NO' noValue:!sigl=sigl; call er 17,!fid(2) !fid(3) !sigl condition('D') sourceline(!sigl) num: procedure; parse arg x .,f,q; if x== then return x; if isNum(x) then return x/1; x=space(translate(x,,','),0); if \isNum(x) then x=$sfxf(x); if isNum(x) then return x/1; if q==1 then return x; if q== then call er 53,x f; call erx 53,x f p: return word(arg(1),1) pickBlank: procedure; parse arg x,y; arg xu; if xu=='BLANK' then return ' '; return p(x y) shorten:procedure; parse arg a,n; return left(a,max(0,length(a)-p(n 1))) simple: return translate(arg(1),'.||--%<>AV'copies('+',25),"·│║─═☼◄►↑↓┤┐└┴┬├┼┘┌╔╗╚╝╟╢╞╡╫╪╤╧╥╨╠╣") squish: return space(translate(arg(1),,word(arg(2) ',',1)),0) syntax: !sigl=sigl; call er 13,!fid(2) !fid(3) !sigl !cal() condition('D') sourceline(!sigl) tem: parse arg r,c,w; if tellinvalid then say '***error!*** row' r", col" c '('@.r.c") is a duplicate of another in the same" w'.'; return 0 tg: arg tg; if simple then tg=simple(tg); call $t gridindent || tg; return</lang>