BookmarkSubscribeRSS Feed
☑ This topic is solved. Need further help from the community? Please sign in and ask a new question.
ifb10
Obsidian | Level 7

Hello everyone,

 

I have tried to generate a double loop in a macro environment with %Do %until loops, so that I can execute the code dependent on two macro variables. (Larva.sas): 

%macro Larva ( Eggs = 6
  ,time_left = 3600
  );
 
%if &Eggs. > 0 %then %do;
data work.hatchery;
%do %until(%eval(&Eggs. < 1));
%do %until(%eval(&time_left. < 1200));
%put NOTE: &Eggs.;
%let time_left = %eval(&time_left. - 1200);
%put NOTE: &time_left.;
Larva +1;
%let Eggs = %eval(&Eggs. - 1);
/* call symputx("Eggs", %sysevalf(&Eggs. - 1)); */
%end;
%end;
call symputx("Larva", Larva);
run;
%end;
 
%mend Larva;
 
%larva()
 
However, the second %do %until does not work as expected. By checking the log, I realized, that the macro variable "time_left" goes under 1200 without the loop stopping (%do %until(%eval(&time_left. < 1200)); %end):
 
NOTE: 6
NOTE: 2400
NOTE: 5
NOTE: 1200
NOTE: 4
NOTE: 0
NOTE: 3
NOTE: -1200
NOTE: 2
NOTE: -2400
NOTE: 1
NOTE: -3600
 
How could I fix this?
Thank you for your help!
 
PS: My attachment file is being removed:
  • The attachment's larva (1).sas content type (application/x-sas) does not match its file extension and has been removed.

 

1 ACCEPTED SOLUTION

Accepted Solutions
Tom
Super User Tom
Super User

First lets explain a couple of programming things.

The condition in a %IF statement (and other macro logic statements) will already add an implied %EVAL() call.  So you can save a lot of typing.

 

You pass the values of the parameters to the macro when you CALL the macro.  You only need to add default values in the %MACRO statement if you want to make the macro easier to use by having default values for some parameters.

 

You want %DO %WHILE() not %DO %UNTIL() since you want the possibility of doing nothing.

%macro Larva(eggs,time_left,larva);
%put START &=larva &=eggs &=time_left;

%do %while(&eggs>0 and &time_left>=1200);
  %let larva=%eval(&larva+1);
  %let eggs=%eval(&eggs-1);
  %let time_left=%eval(&time_left-1200);
  %put &=larva &=eggs &=time_left;
%end;

%put FINISH &=larva &=eggs &=time_left;

%mend Larva;

Let's try it with your scenario:

460  %Larva
461  (Eggs = 6
462  ,time_left = 3600
463  ,Larva = 0
464  );
START LARVA=0 EGGS=6 TIME_LEFT=3600
LARVA=1 EGGS=5 TIME_LEFT=2400
LARVA=2 EGGS=4 TIME_LEFT=1200
LARVA=3 EGGS=3 TIME_LEFT=0
FINISH LARVA=3 EGGS=3 TIME_LEFT=0

Now if you want the new values of EGGS or LARVA (or TIME_LEFT) to be returned to the caller then you will need make some changes.  Since they are the macro parameters they are by definition LOCAL macro variables.  So they will disappear when the macro finishes running.

View solution in original post

9 REPLIES 9
Kurt_Bremser
Super User

Since you do not reset &time_left to its original value, and use UNTIL, which forces at least one iteration to be done, it looks like this.

 

What do you want to achieve? Supply the rule, and show some examples for different values of eggs and time_left.

Tom
Super User Tom
Super User

I do not understand what the DATA step is there for.  You seem to be just changing the values of macro variables.  If you do need a dataset then what is the macro logic for?

 

I also do not understand are you manually programming the logic to implement iterative DO loops.

Is this what you are trying to do?

%macro Larva(eggs,time_left);
%let larva=0;
%let start=&time_left;
%do eggs=&eggs %to 0 %by -1 ; 
  %do time_left=&start %to 0 %by -1200 ;
     %let larva=%eval(&larva+1);
     %put &=larva &=eggs &=time_left ;
  %end;
  %put %str();
%end;
%mend Larva;

Example run:

383  %Larva
384  (Eggs = 6
385  ,time_left = 3600
386  );
LARVA=1 EGGS=6 TIME_LEFT=3600
LARVA=2 EGGS=6 TIME_LEFT=2400
LARVA=3 EGGS=6 TIME_LEFT=1200
LARVA=4 EGGS=6 TIME_LEFT=0

LARVA=5 EGGS=5 TIME_LEFT=3600
LARVA=6 EGGS=5 TIME_LEFT=2400
LARVA=7 EGGS=5 TIME_LEFT=1200
LARVA=8 EGGS=5 TIME_LEFT=0

LARVA=9 EGGS=4 TIME_LEFT=3600
LARVA=10 EGGS=4 TIME_LEFT=2400
LARVA=11 EGGS=4 TIME_LEFT=1200
LARVA=12 EGGS=4 TIME_LEFT=0

LARVA=13 EGGS=3 TIME_LEFT=3600
LARVA=14 EGGS=3 TIME_LEFT=2400
LARVA=15 EGGS=3 TIME_LEFT=1200
LARVA=16 EGGS=3 TIME_LEFT=0

LARVA=17 EGGS=2 TIME_LEFT=3600
LARVA=18 EGGS=2 TIME_LEFT=2400
LARVA=19 EGGS=2 TIME_LEFT=1200
LARVA=20 EGGS=2 TIME_LEFT=0

LARVA=21 EGGS=1 TIME_LEFT=3600
LARVA=22 EGGS=1 TIME_LEFT=2400
LARVA=23 EGGS=1 TIME_LEFT=1200
LARVA=24 EGGS=1 TIME_LEFT=0

LARVA=25 EGGS=0 TIME_LEFT=3600
LARVA=26 EGGS=0 TIME_LEFT=2400
LARVA=27 EGGS=0 TIME_LEFT=1200
LARVA=28 EGGS=0 TIME_LEFT=0

 

 

ifb10
Obsidian | Level 7

Hi again @Tom ,

 

you are right, I do not need the data step for this macro. This was again just to test call symputx. The code should result in the generation of "larva" dependent on both conditions: Eggs should be greater than 0 and will be consumed per larva and Time for the transformation of Egg into Larva is taking 20 min. Hence if I have 6 Eggs but only 1 hour (3600 s) I should obtain 3 Larva with 3 Eggs remaining. I thought this would be a nice example to couple the two conditions using %do %until Loops:

%macro Larva ( Eggs = 6
			  ,time_left = 3600
			  ,Larva = 0
			  );

%if &Eggs. > 0 %then %do;

	%do %until((%eval(&Eggs. < 1)) and (%eval(&time_left. < 1200)));
			%let time_left = %eval(&time_left. - 1200);
			%let Larva = %eval(&larva. +1);
			%let Eggs = %eval(&Eggs. - 1);			
			%put &=Larva. &=Eggs. &=time_left.;
	%end;
%end;

%mend Larva;

%larva()

 But this results in:

 LARVA=1 EGGS=5 TIME_LEFT=2400
 LARVA=2 EGGS=4 TIME_LEFT=1200
 LARVA=3 EGGS=3 TIME_LEFT=0
 LARVA=4 EGGS=2 TIME_LEFT=-1200
 LARVA=5 EGGS=1 TIME_LEFT=-2400
 LARVA=6 EGGS=0 TIME_LEFT=-3600

Which is not the intent, since the time factor is being ignored. Hence I tried to execute the two loos sequentially but that lead to the same result. 

Tom
Super User Tom
Super User

First lets explain a couple of programming things.

The condition in a %IF statement (and other macro logic statements) will already add an implied %EVAL() call.  So you can save a lot of typing.

 

You pass the values of the parameters to the macro when you CALL the macro.  You only need to add default values in the %MACRO statement if you want to make the macro easier to use by having default values for some parameters.

 

You want %DO %WHILE() not %DO %UNTIL() since you want the possibility of doing nothing.

%macro Larva(eggs,time_left,larva);
%put START &=larva &=eggs &=time_left;

%do %while(&eggs>0 and &time_left>=1200);
  %let larva=%eval(&larva+1);
  %let eggs=%eval(&eggs-1);
  %let time_left=%eval(&time_left-1200);
  %put &=larva &=eggs &=time_left;
%end;

%put FINISH &=larva &=eggs &=time_left;

%mend Larva;

Let's try it with your scenario:

460  %Larva
461  (Eggs = 6
462  ,time_left = 3600
463  ,Larva = 0
464  );
START LARVA=0 EGGS=6 TIME_LEFT=3600
LARVA=1 EGGS=5 TIME_LEFT=2400
LARVA=2 EGGS=4 TIME_LEFT=1200
LARVA=3 EGGS=3 TIME_LEFT=0
FINISH LARVA=3 EGGS=3 TIME_LEFT=0

Now if you want the new values of EGGS or LARVA (or TIME_LEFT) to be returned to the caller then you will need make some changes.  Since they are the macro parameters they are by definition LOCAL macro variables.  So they will disappear when the macro finishes running.

ifb10
Obsidian | Level 7

Thank you @Tom ! Learned a lot today thanks to you. Especially the implied %EVAL is useful to know.

Quentin
Super User

Looks like you want OR on the %DO %UNTIL, not AND:

%do %until((%eval(&Eggs. < 1)) or (%eval(&time_left. < 1200)));

You want to stop when you are out of eggs or out of time.

The Boston Area SAS Users Group is hosting free webinars!
Next webinar will be in January 2025. Until then, check out our archives: https://www.basug.org/videos. And be sure to subscribe to our our email list.
ifb10
Obsidian | Level 7

Thank you @Quentin ! I did not even think of this option, since in other languages the and operator meant that both conditions have to be fulfilled. But it makes sense here, I do it until one of the conditions is reached. Funny that it works the other way around with while, which also makes sense after thinking about it. 

Tom
Super User Tom
Super User

@ifb10 wrote:

Thank you @Quentin ! I did not even think of this option, since in other languages the and operator meant that both conditions have to be fulfilled. But it makes sense here, I do it until one of the conditions is reached. Funny that it works the other way around with while, which also makes sense after thinking about it. 


Look up De Morgan's law.

 

SAS Innovate 2025: Register Now

Registration is now open for SAS Innovate 2025 , our biggest and most exciting global event of the year! Join us in Orlando, FL, May 6-9.
Sign up by Dec. 31 to get the 2024 rate of just $495.
Register now!

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.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 9 replies
  • 1744 views
  • 6 likes
  • 4 in conversation