Sudoku/REXX

Revision as of 19:28, 31 August 2014 by rosettacode>Gerard Schildberger (→‎$SUDOKU.REX: rewrote the REXX program is use a more flexible parameter specification.)

$SUDOKU.REX

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


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. */ /*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'  L  "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' L "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>