I would like to slice and loop through a VARCHAR
this is what I try :
%MACRO test;
data
%DO i_=1 %TO 4;
%LET extract&i_.=%SCAN(&string.,&i_.," ");
%PUT extract&i_.=&&extract&i_.._hash;
%END;
%MEND test;
this work but the problem is that I have to put the number of words in my code for exemple here it's 4 but if I have a new string with 5 or 6 words this doesn't work.
Use the COUNTW() function.
%DO i_=1 %TO %sysfunc(countw(&string.," "));
PS Are you really using quote and space as delimiter characters in the value of &STRING? That is what you are telling %SCAN() to use.
Related
I have 5 separate datasets(actually many more but i want to shorten the code) named dk33,dk34,dk35,dk51,dk63, each dataset contains a numeric field: surv_probs. I would like to load the values into 5 arrays and then use the arrays in a datastep(result), however, I need advice what is the best way to do it.
I am getting error when I use the macro: setarrays: (code below)
WARNING: The quoted string currently being processed has become more than 262 characters long. You might have unbalanced quotation
marks.
WARNING: The quoted string currently being processed has become more than 262 characters long. You might have unbalanced quotation
marks.
ERROR: Illegal reference to the array dk33_arr.
Here is the main code.
%let var1 = dk33;
%let var2 = dk34;
%let var3 = dk35;
%let var4 = dk51;
%let var5 = dk63;
%let varN = 5;
/*put length of each column into macro variables */
%macro getlength;
%do i=1 %to &varN;
proc sql noprint;
select count(surv_probs)
into : &&var&i.._rows
from work.&&var&i;
quit;
%end;
%mend;
/*load values of column:surv_probs into macro variables*/
%macro readin;
%do i=1 %to &varN;
proc sql noprint;
select surv_probs
into: &&var&i.._list separated by ","
from &&var&i;
quit;
%end;
%mend;
data _null_;
call execute('%readin');
call execute('%getlength');
run;
/* create arrays*/
%macro setarrays;
%do i=1 %to 1;
j=1;
array &&var&i.._arr{&&&&&&var&i.._rows};
do while(scan("&&&&&&var&i.._list",j,",") ne "");
&&var&i.._arr = scan("&&&&&&var&i.._list",j,",");
j=j+1;
end;
%end;
%mend;
data result;
%setarrays
put dk33_arr(1);
* some other statements where I use the arrays*
run;
Answer to toms question:
*macro getlength(when executed) creates 5 macro variables named: dk33_rows,dk34_rows,dk35_rows,dk51_rows,dk63_rows
*the macro readin(when executed):creates 5 macro variables dk33_list,dk34_list,dk35_list,dk51_list,dk63_list. Each containing a string which is comma separates the values from the column: eg.: 0.99994,0.1999,0.1111
*the macro setarrays creates 5 arrays,when executed, dk33_arr,dk34_arr,... holding the parsed values from the macro variables created by readin
I find that "macro arrays" like VAR1,VAR2,.... are generally more trouble than they are worth. Either keep your list of dataset names in an actual dataset and generate code from that. Or if the list is short enough put the list into a single macro variable and use %SCAN() to pull out the items as you need them.
But either way it is also better to avoid trying to write macro code that needs more than three &'s. Build up the reference in multiple steps. Build a macro variable that has the name of the macro you want to reference and then pull the value of that into another macro variable. It might take more lines of code, but you can more easily understand what is happening.
%let i=1 ;
%let mvarname=var&i;
%let dataset_name=&&&mvarname;
Before you begin using macro code (or other code generation techniques) make sure you know what code you are trying to generate. If you want to load a variable into a temporary array you can just use a DO loop. There is no need to macro code, or copying values, or even counts, into macro variables. For example instead of getting the count of the observations you could just make your temporary array larger than you expect to ever need.
data test1 ;
if _n_=1 then do;
do i=1 to nobs_dk33;
array dk33 (1000) _temporary_;
set dk33 nobs=nobs_dk33 ;
dk33(i)=surv_probs;
end;
do i=1 to nobs_dk34;
array dk34 (1000) _temporary_;
set dk34 nobs=nobs_dk34 ;
dk34(i)=surv_probs;
end;
end;
* What ever you are planning to do with the DK33 and DK34 arrays ;
run;
Or you could transpose the dataset first.
proc transpose data=dk33 out=dk33_t prefix=dk33_ ;
var surv_probs ;
run;
Then your later step is easier since you can just use a SET statement to read in the one observation that has all of the values.
data test;
if _n_=1 then do;
set dk33_t ;
array dk33 dk33_: ;
end;
....
run;
I have a lot of 6-digit numbers in a SAS program:
898300 898311 898312 898313 898314 898315 898316 898317 898321 898322 898323 898324 898331 898332 898333 898341 898342 898343
898400 898401 898402 898403 898500 898501 898502 898503 898600 898601 898602 898603 898604 898605 898606 898607 898608 898609
898610 898611 898612 898613 898614 898615 898616 898617 898700 898701 898702 898703 898704 898705 898706 898800 898801 898901
I would like to do a quick find and replace using Ctrl+H such that alle the 6-digit numbers are "quoted":
"898300" "898311" "898312" ...
etc.
I think doing a regular expression search is the way to go, but I am not able to identify the specific syntax. Anyone who knows what to do?
Thanks
Am sure this could be done in notepad (replace all multiple spaces with one, then replace a single space with " ") but seeing as you tagged SAS here is a SAS solution.
first, compile this macro:
/***
Converts a space delimited string into one with custom quotes / delimiters
#usage
%put %get_quoted_str(in_str=blah blah blah
,dlm=%str(,)
,quote=%str(%') );
returns: 'blah','blah','blah'
##
***/
%macro get_quoted_str(IN_STR=,DLM=,QUOTE=);
%local i item buffer;
%let i=1;
%do %while (%qscan(&IN_STR,&i,%str( )) ne %str() ) ;
%let item=%scan(&IN_STR,&i,%str( ));
%if %bquote("E) ne %then %let item="E%trim(&item)"E;
%else %let item=%trim(&item);
%if (&i = 1) %then %let buffer =&item;
%else %let buffer =&buffer&DLM&item;
%let i = %eval(&i+1);
%end;
&buffer
%mend;
then call as follows:
%put %get_quoted_str(IN_STR=898300 898311 898312 898313 898314 898315 898316 898317 898321 898322 898323 898324
898331 898332 898333 898341 898342 898343 898400 898401 898402 898403 898500 898501 898502 898503 898600 898601
898602 898603 898604 898605 898606 898607 898608 898609 898610 898611 898612 898613 898614 898615 898616 898617
898700 898701 898702 898703 898704 898705 898706 898800 898801 898901
,DLM=%str( ),QUOTE=%str(%")
);
which gives:
"898300" "898311" "898312" "898313" "898314" "898315" "898316" "898317" "898321" "898322" "898323" "898324" "898331" "898332"
"898333" "898341" "898342" "898343" "898400" "898401" "898402" "898403" "898500" "898501" "898502" "898503" "898600" "898601"
"898602" "898603" "898604" "898605" "898606" "898607" "898608" "898609" "898610" "898611" "898612" "898613" "898614" "898615"
"898616" "898617" "898700" "898701" "898702" "898703" "898704" "898705" "898706" "898800" "898801" "898901"
The above can then be copy pasted back into the program..
As long as the result is shorter than %SYSFUNC() limits I normally just use TRANWRD() function call for this. Compress multiple blanks to one first using COMPBL().
%let list=A B C D ;
%let qlist="%sysfunc(tranwrd(%sysfunc(compbl(&list)),%str( )," "))" ;
I want to use a Macro call in a data step. Below is the macro and its invocation in a data step but this isnt working. Can you guys please suggest a way to make it work.
%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");
%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);
call symput("pos",pos);
run;
%end;
%let strg=%substr(&string,&pos); /* Extract the substring*/
%put the string is &strg;
%mend;
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;
If the macro is to be used inside a datastep, write it more simply using just datastep functions instead of making it complicated with macro functions. There are plenty of SAS string functions which will allow you to accomplish what it appears you want in far less code.
Data steps are very flexible and they allow you to manipulate your data in a very flexible day, I would recommend you trying to refactor the code to use only a datastep, however, if you still want to use it the way you have it right now use call execute .
Here is an example:
data _null_ ;
input name $ value !$ ;
call execute
( ‘%global ‘
llname~l ‘;’ II
‘%let’ Ilnamell =’ [1
value II ‘;’
);
cards ;
abc xyz
;
Refer to these docs for further reading :
http://www2.sas.com/proceedings/sugi30/154-30.pdf and
http://www2.sas.com/proceedings/sugi22/CODERS/PAPER70.PDF
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;
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;
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:
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.
Use /* THIS STYLE FOR COMMENTING */. Using other comment styles may cause the line to end.
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.
Do not put a semicolon on the line that is actually returned, as it may be undesirable when the function-style macro is used.
Hi I have a program to use one macro to call another one.
I have two month(jun12 and jul12) and each month has two parts(1 & 2), I want to do a loop which I construct a macro called"Loop", Inside it, I constructed a Array, and used Do comment do call a macro "try".
Seems like it doesn't work. Can someone help me with it? Thank you!
LIBNAME EC100006 "G:\sample";
%MACRO try(month=,part=);
...FROM EC100006.monthitsum&month.lag&part AS t1
%MEND try;
%Macro test;
ARRAY Mon(2) jun12 jul12;
%Do i=1 %to 2;
%Do j=1 %to 2
%try(month=Mon(i),part=j)
%End
%End
%Mend test;
%test
You can't have an array of macro variables.
The simplest way to repeatedly call a macro with a list of parameters is to make a dataset with those parameters and call it from the dataset, either with CALL EXECUTE or using PROC SQL to create a macro list of macro calls.
data call;
input month $ part;
datalines;
jun12 1
jul12 2
;;;;
run;
proc sql;
select cats('%try(month=',month,',part=',part,')') into :mcalllist
separated by ' '
from call;
quit;
&mcalllist;
That only works if you have less than 20,000 characters worth of calls or so - if it's more than that you need to try a different option (%include file or call execute).
So right now it's something like this
LIBNAME EC100006 "G:\sample";
%MACRO try(month=,part=);
...FROM EC100006.monthitsum&month.lag&part AS t1
%MEND try;
Data Array
ARRAY Mon{2} jun12 jul12;
RUN;
%Macro test;
%Do i=1 %to 2;
%Do j=1 %to 2
%try(month=Mon(i),part=j)
%End
%End
%Mend test;
%test