BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
emera86
Quartz | Level 8

Hi there,

 

I'm trying to implement a simple functionality in a more complez macro and I'm having a hard time making it work properly.

The input is a variable containing several numbers concatenated in a string and separated by commas. I want to separate the numbers, assign their value with certain format to a specific column and then concatenate all the non-missing column values.

 

I'm first counting the number of elements of my input variable value and then scaning each of them in a loop so I can assign their value to a specific array element. Then, in a different loop (to be sure of following a certain order while the input could be disordered) I check non-missing values and concatenate them. Here is the code:

 

proc format;
	value charformat
	1 = 'AAA'
	2 = 'BBB'
	3 = 'CCC'
	4 = 'DDD'
	5 = 'EEE'
	6 = 'FFF'
	7 = 'GGG'
	8 = 'HHH'
	9 = 'III'
	10 = 'JJJ'
	11 = 'KKK'
	12 = 'LLL'
	13 = 'MMM'
	14 = 'NNN'
	15 = 'OOO'
	;
run;

data test1;
   infile datalines delimiter=''; 
   length testvariable $100;
   input testvariable $;
   datalines;
5,8,15 
1
1,3 
1,2,3
2,3
3,1,2
2,15
11,5
7,1,9
13,2
14
;
run;

options mlogic mprint symbolgen;

%macro multiple(var);
	data test2;
	       set test1; 
	       length all_options $200;
	       array options [15] $100;

	       /* We count the number of elements on the list */
	       elements = count(&var,',')+1;
	       call symputx('endloop', elements);
	       
	      	%if "&var" ne ' ' %then %do; 
	           /*From 1 to the number of elements*/
	           %do i=1 %to &endloop;
			/* We scan the i-th element of the list and turn it into a numeric value*/
	                item = trim(left(scan(&var,&i,',')))*1;
			/* With the scaned element we give value to the item-th element of the array options and apply an specific format */
	                if item ne . then options[item] =trim(left (put(item,charformat.)));
	           %end;
		   /* We concatenate all non-zero values from the array */
	           %do j=1 %to 15;
	                if all_options ne ' ' and options[&j] ne ' ' then all_options = trim(left(all_options)) ||'-'|| trim(left(options[&j]));
	                if all_options eq ' ' then all_options = options[&j];
	            %end;
	  	%end;
	run; 
%mend;

%multiple(testvariable);

However, the execution finishes without errors and only the first element is scanned ('item' value should be the last element of the list as it is overwritten at every loop step and it remains equal to the first element when the execution finishes, and only this element is saved in the array). See the obtained output (click to enlarge):

 

 output.PNG

 

Do you have an idea on what may be causing this issue? Something obvious in the code that I might change?

 

Thanks for any suggestions!!

1 ACCEPTED SOLUTION

Accepted Solutions
ballardw
Super User

Part of your issue is attempting to use the value of macro variable in the same step it is created. The way you are attempting doesn't do what you want (I think).

You create a data step variable ELEMENTS, stuff it into a macro variable, then attempt to use that macro variable as a loop counter.

I don't think there is any real reason to not to use Elements as the limit on the loop counter.

example:

i=1 to elements;
/* We scan the i-th element of the list and turn it into a numeric value*/
   item =input(scan(&var,ii,','),best.);
/* With the scaned element we give value to the item-th element of the array options and apply an specific format */
   if item ne . then options[item] =strip(put(item,charformat.));
end;

The STRIP function, which combines Trim (left()) has been around for 15(?) or more years. I don't know why everyone keeps using trim(left( resulting ugly-ish code)

 

Similar with the second loop,

do j=1 to 15;
    if all_options ne ' ' and options[j] ne ' ' then all_options = catx('-',all_options,options[j]);
    Else all_options = options[j];
end;

There are several concatenation functions, CAT CATT CATS CATX CATQ that are generally much cleaner than the venerable || and in the case of CATT CATS and CATX remove trailing and/or leading spaces so Strip or Left(trim()) are seldom needed. CATX places a

 

separator, the first parameter between each value (and can be any string not just a single character)

And I think the last loop isn't actually needed. Consider the result of

 

%let var=testvariable ;
data test2;
   set test1; 
   length all_options $200;
   array options [15] $100;

   /* We count the number of elements on the list */
   elements = count(&var,',')+1;
   do i=1 to elements;
/* We scan the i-th element of the list and turn it into a numeric value*/
       item =input(scan(&var,i,','),best.);
/* With the scaned element we give value to the item-th element of the array options and apply an specific format */
       if item ne . then options[item] =strip(put(item,charformat.));
   end;
   all_options = catx('-',of options(*));
   drop i;
run; 

 

Which gives me

Obs    all_options

  1    EEE-HHH-OOO
  2    AAA
  3    AAA-CCC
  4    AAA-BBB-CCC
  5    BBB-CCC
  6    AAA-BBB-CCC
  7    BBB-OOO
  8    EEE-KKK
  9    AAA-GGG-III
 10    BBB-MMM
 11    NNN

 

 

BTW, you should test

%if "&var" ne ' ' %then %do;

outside of the data step and not even enter the data step the variable is unassigned.

View solution in original post

3 REPLIES 3
RW9
Diamond | Level 26 RW9
Diamond | Level 26

Can you post exactly what you want out.  That screenshot you show makes no sense to me, and that code is a lot of faff.  Why not just simply concatenate each item:

data want;
 set test1;
 length all_string $2000;
 do i=1 to countw(testvariable,",");
   all_string=catx(",",all_string,put(input(scan(testvariable,i,","),best.),charformat.));
 end;
run;
ballardw
Super User

Part of your issue is attempting to use the value of macro variable in the same step it is created. The way you are attempting doesn't do what you want (I think).

You create a data step variable ELEMENTS, stuff it into a macro variable, then attempt to use that macro variable as a loop counter.

I don't think there is any real reason to not to use Elements as the limit on the loop counter.

example:

i=1 to elements;
/* We scan the i-th element of the list and turn it into a numeric value*/
   item =input(scan(&var,ii,','),best.);
/* With the scaned element we give value to the item-th element of the array options and apply an specific format */
   if item ne . then options[item] =strip(put(item,charformat.));
end;

The STRIP function, which combines Trim (left()) has been around for 15(?) or more years. I don't know why everyone keeps using trim(left( resulting ugly-ish code)

 

Similar with the second loop,

do j=1 to 15;
    if all_options ne ' ' and options[j] ne ' ' then all_options = catx('-',all_options,options[j]);
    Else all_options = options[j];
end;

There are several concatenation functions, CAT CATT CATS CATX CATQ that are generally much cleaner than the venerable || and in the case of CATT CATS and CATX remove trailing and/or leading spaces so Strip or Left(trim()) are seldom needed. CATX places a

 

separator, the first parameter between each value (and can be any string not just a single character)

And I think the last loop isn't actually needed. Consider the result of

 

%let var=testvariable ;
data test2;
   set test1; 
   length all_options $200;
   array options [15] $100;

   /* We count the number of elements on the list */
   elements = count(&var,',')+1;
   do i=1 to elements;
/* We scan the i-th element of the list and turn it into a numeric value*/
       item =input(scan(&var,i,','),best.);
/* With the scaned element we give value to the item-th element of the array options and apply an specific format */
       if item ne . then options[item] =strip(put(item,charformat.));
   end;
   all_options = catx('-',of options(*));
   drop i;
run; 

 

Which gives me

Obs    all_options

  1    EEE-HHH-OOO
  2    AAA
  3    AAA-CCC
  4    AAA-BBB-CCC
  5    BBB-CCC
  6    AAA-BBB-CCC
  7    BBB-OOO
  8    EEE-KKK
  9    AAA-GGG-III
 10    BBB-MMM
 11    NNN

 

 

BTW, you should test

%if "&var" ne ' ' %then %do;

outside of the data step and not even enter the data step the variable is unassigned.

emera86
Quartz | Level 8

Thank you @ballardw,

 

Not only you solved my problem, but you also taught me a bunch of very useful tricks that I intend to apply from now on.

 

Thanks a lot again!

 

Best regards

Ready to join fellow brilliant minds for the SAS Hackathon?

Build your skills. Make connections. Enjoy creative freedom. Maybe change the world. Registration is now open through August 30th. Visit the SAS Hackathon homepage.

Register today!
How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 3 replies
  • 1571 views
  • 0 likes
  • 3 in conversation