ElBaronRojo
ElBaronRojo

Reputation: 19

How to use SAS Macro call in a %let

I want use a macro in a %let call, Below is the Macro code and how I want to invoke it. Please help me achieve it.

%macro xscan(string, delimiter, word_number);

%let len1=%length(&string); /*Computing the length of the string*/
%let len=%eval(&len1+1);
%let sub=%scan(&string,&word_number,"&delimiter"); /*Fetch the string specified by     word_number*/

%if &word_number ge 0 %then %do;
%let pos=%index(&string,&sub); /* Locate the position while reading left to right*/
%end;

%if &word_number lt 0 %then %do;
data _null_;
pos=find("&string","&sub",-&len); /* Locate the position while reading from right to left*/
call symput("pos",pos);
run;
%end;

%let strg=%substr(&string,&pos); /* Extract the substring*/

%put the string is &strg;
%mend;

%let sub_str = %xscan(a bb ccc dddd bb eeeee, %str( ), -2);
%put The value of sub_str = &sub_str;

Desired implementation:

 data work.in_data;
length in_string $50;
in_string = “a bb ccc dddd bb eeeee”; 
output;
in_string = “aa b cc aa dee”; 
output;
  run;

  data work.out_data;
set work.in_data;
length sub_str $50;
start_word_num = -(_n_ +1);
sub_str = %xscan(in_string,’ ‘, start_word_num);
  run;

  proc print; run;

Upvotes: 1

Views: 1657

Answers (2)

Joe
Joe

Reputation: 63424

I'm posting a new answer since the other answer answers a slightly different question.

Here, your macro really is intended to perform data step techniques, not macro techniques. You cannot (easily) use a macro to edit variable contents; a macro is intended to write SAS code, not to modify variables. You could use PROC FCMP to solve this problem, and I may well do so if I have more time, but for now here's the proper solution with just data step techniques and a normal (non-functional) macro.

First, write the data step technique to accomplish it. This is a fairly messy but effective solution. It only works for negative start_word_num; if left or right is desired it would need some modification to the loop parameters. I suggest using this as a starting point and improving it for your needs.

data work.out_data;
set work.in_data;
length sub_str $50;
start_word_num = -(_n_ +1);
do _t = countc(trimn(in_string),' ')+1 to countc(trimn(in_string),' ')+start_word_num+2 by -1;
    sub_str = catx(' ',scan(in_string,_t,' '),sub_str);
    put _t= sub_str=;
end;
put in_string= sub_str=;
run;

Now, move the loop into a macro.

%macro xscan(word_num, initial_string, result);
&result.=' ';
do _t = countc(trimn(&initial_string.),' ')+1 to countc(trimn(&initial_string.),' ')+&word_num.+2 by -1;
    &result. = catx(' ',scan(&initial_string.,_t,' '),&result.);
end;
%mend xscan;


data work.out_data;
set work.in_data;
length sub_str $50;
start_word_num = -(_n_ +1);
%xscan(start_word_num,in_string,sub_str);
put in_string= sub_str=;
run;

Upvotes: 1

Joe
Joe

Reputation: 63424

You have two problems. First off, a function-style macro must not contain any data steps (or procs or anything else). If you do need to execute a data step, you have to use FCMP with run_macro. However, here you can use %SYSFUNC to accomplish what you are doing in the data step.

Second, you need to actually return the value. Ultimately a macro resolves to text, so you need to resolve

%let x = %xscan(...);

to

%let x = bb eeeee;

So you need to simply have bb eeeee as open text in your macro.

This should accomplish both things:

options mprint symbolgen;

%macro xscan(string, delimiter, word_number);
%local len1 len sub pos;

%let len1=%length(&string); /*Computing the length of the string*/
%let len=%eval(&len1+1);
%let sub=%scan(&string,&word_number,"&delimiter"); /*Fetch the string specified by     word_number*/

%if &word_number ge 0 %then %do;
%let pos=%index(&string,&sub); /* Locate the position while reading left to right*/
%end;

%else %if &word_number lt 0 %then %do;
%let pos=%sysfunc(find(&string,&sub,-&len)); /* Locate the position while reading from right to left*/
%end;

%substr(&string,&pos) /* Extract the substring*/

%mend;

%let sub_str = %xscan(a bb ccc dddd bb eeeee, %str( ), -2);
%put The value of sub_str = &sub_str;

(Note, I don't necessarily know this does what you really want, but it does what the code appears to be doing.)

Some tips for function-style macros, courtesy of Rob Penridge:

  1. Define all of your macro variables using a %local statement like so: %local len1 len sub pos;. That way you do not overwrite global macro variables.
  2. Use /* THIS STYLE FOR COMMENTING */. Using other comment styles may cause the line to end.
  3. The secret to making the macro work is the line that uses %substr at the end. This resolves to bb eeeeee being left in open code. Since that is all that is left, that is what calling the macro resolves to.
  4. Do not put a semicolon on the line that is actually returned, as it may be undesirable when the function-style macro is used.

Upvotes: 1

Related Questions