|
Generating Combinations and Permutations
(originally in SAS Communications 2nd Qtr 1992 p. 36+)
(modified 7/99 to allow negative and decimal numbers)
While the number of permutations and combinations of n items taken r at a time
is easy to calculate, generating the actual permutations and
combinations themselves takes a bit of coding. Following are two macros, %combo
and %permute, which will perform the respective generation.
To invoke one of the macros, simply provide the n elements, and r, as in:
%combo(r,item1,item2,item3, ... ,itemn)
%permute(r,item1,item2,item3, ... ,itemn)
where r is between 1 and n, inclusive. Some examples are:
%combo(2,a,b,c)
%permute(2,a,b,c)
which will generate all combinations of the three items a, b, and c taken 2 at a
time, and the permutations of the elements a, b, and c taken 2 at a
time. The items can be either character or numeric. Other valid invocations
follow.
%permute(2,Chip,Pete,Mike,William)
%combo(4,Chip,Mike,Nan,Greg,Bob,Owen)
Each of the macros begins with a 10-line section that utilizes the PARMBUFF
macro option and the %SCAN function to read the value of r,
the number of elements, and what the elements are. The basic idea behind the
%permute macro is to generate r variables, and let them each
loop through all n items. The above invocation would create the following loop:
do r1="Chip","Pete","Mike","William";
do r2="Chip","Pete","Mike","William";
The macro will check to see if r1 and r2 have the same value, and if they do
not, r1 and r2 will be saved as one permutation.
%macro permute(r) / parmbuff; /* the parmbuff option assigns */
%let i=2; /* the invocation parameter list to the */
%let things=; /* macro variable &syspbuff */
%do %while (%Qscan(&syspbuff,&i,%STR(,%))) ne ); /* scan the syspbuff */
%let p&i="%Qscan(&syspbuff,&i,%STR(,%)))"; /* to determine r */
%if &i=2 %then %let things=&&p&i; /* and count the number */
%else %let things=&things,&&p&i; /* of elements, n */
%let i=%eval(&i+1);
%end;
%let n=%eval(&i-2);
data permute;
drop i j copy;
array check (*) $ 10 r1-r&r; /* create a total of r */
%do m=1 %to &r; /* variables for looping */
do r&m = &things;
%end;
copy=0;
do i=2 to &r; /* look for duplicate items */
do j=1 to i-1; /* and keep the unique ones */
if check(j)=check(i) then copy+1;
end;
end;
if copy = 0 then output; /* writes to a SAS data set */
if copy = 0 then put r1-r&r; /* writes to the log */
%do m=1 %to &r;
end; /* end the r DO LOOPS */
%end;
run;
proc print uniform data=permute;
title "permutations of &n items taken &r at a time ";
run;
%mend permute;
Invoking the macro as shown above produces the following output:
permutations of 4 items taken 2 at a time
OBS R1 R2
1 Chip Pete
2 Chip Mike
3 Chip William
4 Pete Chip
5 Pete Mike
6 Pete William
7 Mike Chip
8 Mike Pete
9 Mike William
10 William Chip
11 William Pete
12 William Mike
The %combo macro is a little more complex. It also will create r variables,
however the values each of the variables takes on will depend on its
relative position with respect to the subscript on r. This is the loop that the
macro will create for the above invocation:
do r1=1 to 3;
do r2=r1+1 to 4;
do r3=r2+1 to 5;
do r4=r3+1 to 6;
Here the values of r1 through r4 will be a set of r subscripts of the relative
positions of the n items. The first time through the loop, r1=1, r2=2,
r3=3, r4=4, corresponding to the elements Chip, Mike, Nan, and Greg, since Chip
is the first item, Mike the second, Nan the third, and Greg the
forth. The very last time through the loop, r1=3, r2=4, r3=5, r4=6,
corresponding to the elements Nan, Greg, Bob, Owen. This looping will not
generate sets of subscripts with duplicate entries, and hence no item will
appear more than once for each combination. The subset of items
corresponding to each set of subscripts will be saved.
%macro combo(r)/parmbuff;
%let i=2;
%let things=;
%do %while (%Qscan(&syspbuff,&i,%STR(,%))) ne );
%let p&i="%Qscan(&syspbuff,&i,%STR(,%)))";
%if &i=2 %then %let things=&&p&i;
%else %let things=&things,&&p&i;
%let i=%eval(&i+1);
%end;
%let n=%eval(&i-2);
data combo;
keep v1-v&r;
array word $8 w1-w&n (&things);
array rr (*) r1-r&r;
array v $8 v1-v&r;
%do i=1 %to &r; /* create the DO LOOPs */
%if &i=1 %then %do;
do r&i=1 to &n-(&r-&i);
%end;
%else %do;
do r&i=r%eval(&i-1)+1 to &n-(&r-&i);
%end;
%end;
do k=1 to &r; /* select subscripted items */
v(k)=word (rr(k)); /* for a SAS data set */
put v(k) ' ' @; /* for log */
end;
put; /* writes to log */
output; /* writes to a SAS data set */
%do i=1 %to &r;
end; /* create ENDs for the DO LOOPs */
%end;
put;
run;
proc print uniform data=combo;
title "combinations of &n items taken &r at a time ";
run;
%mend combo;
Invoking the macro as above will generate the following output:
combinations of 6 items taken 4 at a time
OBS V1 V2 V3 V4
1 Chip Mike Nan Greg
2 Chip Mike Nan Bob
3 Chip Mike Nan Owen
4 Chip Mike Greg Bob
5 Chip Mike Greg Owen
6 Chip Mike Bob Owen
7 Chip Nan Greg Bob
8 Chip Nan Greg Owen
9 Chip Nan Bob Owen
10 Chip Greg Bob Owen
11 Mike Nan Greg Bob
12 Mike Nan Greg Owen
13 Mike Nan Bob Owen
14 Mike Greg Bob Owen
15 Nan Greg Bob Owen
Both macros give a choice of location for the results, either in a data set
using an output statement, or in the log (or file) with a put statement.
|