/* Richard A. DeVenezia * www.devenezia.com */ /* This code evolved from some originally posted * to SAS-L on August 5, 2003 * * An adjacency problem: * ---- * Consider a member, mx, with a value in the range of A to Z. * Consider data describing S1,...,Si,...,Sn containing * members m{i}{1},...,m{i}{n{i}} * * e.g * set id * 1 A * 1 B * 1 C * 2 D * 3 A * 3 F * 4 H * 4 I * 4 J * 4 K * 5 F * 5 E * * becomes * * superset id * 1_3_5 A * 1_3_5 B * 1_3_5 C * 2 D * 1_3_5 A * 1_3_5 F * 4 H * 4 I * 4 J * 4 K * 1_3_5 F * 1_3_5 E * * The problem is to find the fewest mutually exclusive * supersets S* such that each S* contains all S sharing at * least one member. * * The problem could also be construed as finding valid * paths through linked groups. * * The solution demonstrates how to program a recursive * algorithm in a SAS Data Step using GOTO and ARRAY to * emulate functional calls with stacks. * GOTO is required because Data Step only allows for * 10 levels of LINK */ /* Step 1. * Construct sample data containing groups in several mutually * exclusive divisons. Note: Four supersets is a minimum imposed * by the construction algorithm, however there may be several */ data sets (keep=set id); length set 8 id $1 group $26; foo = compress ( '1234 1243 1324 1342 1423 1432' || '2134 2143 2314 2341 2413 2431' || '3124 3142 3214 3241 3412 3421' || '4123 4132 4213 4231 4312 4321' || '12' ); retain seed 512; retain nSets 12; retain maxMembers 8; do set = 1 to nSets; superset = 1 + int(4*ranuni(seed)); nMembers = 1 + maxMembers * ranuni (seed); group = ''; ssc = put(superset,1.); do i = 1 to nMembers; do until (substr(foo,ix,1) = ssc); ix = 1 + int(26*ranuni(seed)); end; substr(group,ix,1) = byte (ix+64); end; group = compress(group); do i = 1 to length (group); id = substr (group,i); output; end; end; run; /* Step 2. * Determine number of sets in data */ proc sql; reset noprint; select count (distinct set) into :nSets from sets; %let nSets = &nSets; quit; /* Step 3. * Process the data looking for supersets. * While doing so, output information that is used to plot * the execution state. This will help you visualize * what is going on. */ data supersets (keep=sset id) stages (keep=stage x y state) anno (keep=stage x1 y1 x2 y2 ix info) ;* / debug ; length stage x1 y1 x2 y2 ix 8.; array S [ &nSets, 26 ] _temporary_; array row_stack [ %eval (26*&nSets) ] _temporary_; array col_stack [ %eval (26*&nSets) ] _temporary_; array claimedRow [ &nSets ] _temporary_; array claimedCol [ 26 ] _temporary_; retain ix jxix kxix 0; array jx_stack [ &nSets ] _temporary_; array kx_stack [ 26 ] _temporary_; /* Read all the sets data into an array * Mark each member as unclaimed (-1) */ do while (not eog); set sets end=eog; row = set; col = rank (id) - 64; * A-Z -> 1-26; S [ row, col ] = -1; end; link puts; link outputStage; x2 = .; /* Examine every member in its context * If the member is unclaimed by a superset * then increment the superset and claim the * the row the member is in * Note: Claiming a row initiates a recursive * claim of column/row/column/... */ ss = 0; do row = 1 to &nSets; if claimedRow [ row ] then continue; do col = 1 to 26; if S [ row, col ] = -1 then do; ss + 1; link claimRow; col = 26; end; end; end; link putS; /* Evaluate the matrix containing the * superset assignments */ length sset $%eval(&nSets*3); do ix = 1 to ss; sset = ''; do i = 1 to &nSets; if claimedRow [i] = ix then do; if sset = '' then sset = trim(left(put(i,4.))); else sset = trim(sset) || '_' || trim(left(put(i,4.))); end; end; do i = 1 to &nSets; if claimedRow [i] = ix then do; do j = 1 to 26; if S [ i, j ] = ix then do; id = byte (j + 64); output supersets; end; end; end; end; end; stop; outputStage: stage+1; do _row = 1 to &nSets; do _col = 1 to 26; state = S [ _row, _col ]; y = _row; x = _col; if state then output stages; end; end; return; outputAnno: x1 = x2; y1 = y2; x2 = col; y2 = row; output anno; return; pushRowCol: ix + 1; row_stack [ ix ] = row; col_stack [ ix ] = col; return; popRowCol: row = row_stack [ ix ] ; col = col_stack [ ix ] ; ix + (-1); return; pushJx: jxix + 1; jx_stack [ jxix ] = jx; return; popJx : jx = jx_stack [ jxix ]; jxix + (-1); return; pushKx: kxix + 1; kx_stack [ kxix ] = kx; return; popKx : kx = kx_stack [ kxix ]; kxix + (-1); return; %*--------------------------------------------------------------------; claimRow: link OutputStage; info = 'R'; x2=col; y2=row; x1=.; y1=.; output anno; if claimedRow [ row ] then goto claimRowReturn; claimedRow [ row ] = ss; col = 1; jx = ix; do until (col > 26); if S [ row, col ] = -1 then do; link pushRowCol; S [ row, col ] = ss; link outputStage; link outputAnno; end; col + 1; end; do while (jx < ix); link popRowCol; link pushRowCol; link pushJx; goto claimColumn; claimColumnReturn: link popJx; link popRowCol; end; if ix = 0 then return; goto claimRowReturn; %*--------------------------------------------------------------------; claimColumn: link OutputStage; info = 'C'; x2=col; y2=row; x1=.; y1=.; output anno; if claimedCol [ col ] then goto claimColumnReturn; claimedCol [ col ] = ss; row = 1; kx = ix; do until (row > &nSets); if S [ row, col ] = -1 then do; link pushRowCol; S [ row, col ] = ss; link outputStage; link outputAnno; end; row + 1; end; do while (kx < ix); link popRowCol; link pushRowCol; link pushKx; goto claimRow; claimRowReturn: link popKx; link popRowCol; end; goto claimColumnReturn; %*--------------------------------------------------------------------; putS: do ii = 1 to &nSets; put ii 2. '. ' @; do jj = 1 to 26; put S[ii,jj] 2. +1 @; end; put; end; put; return; format ix stage x1 y1 x2 y2 4.; run; /* Step 4. * Generate gifanim that demonstrates the * program flow performed */ options nosource; */*; options mprint; data annotate; set anno; retain xsys ysys '2'; length function color $8; retain xx yy xx1 xx2 yy1 yy2; if info = 'R' and y1=. and x1=. then do; color='GRAY'; xx1 = 0; yy1 = y2; xx2 = 27; yy2 = y2; xx = x2; yy = y2; end; else if info = 'C' and x1=. and y1=. then do; color='GRAY'; xx1 = x2; yy1 = 0; xx2 = x2; yy2 = &nSets. + 1; xx = x2; yy = y2; end; x=xx-.25;y=yy-.25; function='MOVE'; output; x=xx+.25;y=yy+.25; function='DRAW'; output; x=xx-.25;y=yy+.25; function='MOVE'; output; x=xx+.25;y=yy-.25; function='DRAW'; output; x=xx1; y=yy1; function = 'MOVE'; output; x=xx2; y=yy2; function = 'DRAW'; size=1; output; if x1 & x2 & y1 & y2; color='BLACK'; x = x1; y = y1; function = 'MOVE'; output; x = x2; y = y2; function = 'DRAW'; size=2; output; /* function='TEXT'; text=put(ix,2.-L); style="SWISS"; size=1; x=x2+0.25; output; */ format stage 4.; keep x y xsys ysys function stage color size ;*text style ; run; %let pct = %sysevalf (100/(&nSets+.5)); symbol1 h=&pct.pct v=plus color=CX505050; symbol2 h=&pct.pct v=square color=blue; symbol3 h=&pct.pct v=circle color=green; symbol4 h=&pct.pct v=diamond color=red; symbol5 h=&pct.pct v=square color=green; symbol6 h=&pct.pct v=circle color=red; symbol7 h=&pct.pct v=diamond color=blue; symbol8 h=&pct.pct v=square color=red; symbol9 h=&pct.pct v=circle color=blue; symbol10 h=&pct.pct v=diamond color=green; axis1 order=(0 to %eval(1+&nSets)) minor=none label=none value=none major=none; axis2 order=(0 to 27) minor=none label=none value=none major=none; legend1 label=none value=none; *goptions gsfname=gout gsfmode=replace device=gifanim delay=60; *goptions hsize=8in vsize=6in; *goptions hsize=4in vsize=3in; *filename gout "c:\temp\gif\superset.gif"; *ods listing ; *uncomment these to create individual gifs named stage001-stageNNN; *the individual gifs can be into a supercompressed gif animation *using Alchemy Mindworks GIF Construction Set; goptions device=gif; goptions hsize=8.42in vsize=6.31in; * 800x600; goptions hsize=4.21in vsize=3.15in; * 400x300; ods listing close; ods html gpath="c:\temp\gif" body="super.html"; goptions i=none goutmode=replace ftext='Arial' htext=5pct hby=0; *title h=5pct move=(1pct,1pct) "#byvar1 #byval1"; data stages; set stages; y = &nSets - y + 1; run; data annotate; set annotate; y = &nSets - y + 1; run; proc gplot data=stages uniform ; by stage; plot y * x = state / vaxis=axis1 haxis=axis2 /* autohref lautohref=33 cautohref=cxAAAAAA autovref lautovref=33 cautovref=cxAAAAAA */ nolegend anno=annotate name="stage001" noframe noaxis ; * where stage<30; run; quit; ods html close; data _null_; file gout recfm=n mod; put '3B'x; run; goptions reset=all; options noxwait noxsync; x "explorer c:\temp\gif\superset.gif"; */; options source;