Nested loop in SAS macro - macros

I got error when I was trying to use macro nested loop in SAS:
%Let t1=30;
%Let t2=40;
%Let t3=50;
%Let t4=60;
%Let t5=70;
%macro Age(time);
%Do I = 1 %to &time.;
data Milk&I;
set Milk;
/*If Age is less than 30, MilkF and MilkA after 30 should be 0, same for 40-70*/
where (age<&&t&I. and (%Do I = &I. %to 5;
MilkF&&t&I. ne 0 or MilkA&&t&I. ne 0 ;
%end;) ) ;
run;
%end;
%mend Age;
%Age(5)
The error shows behind the last "ne 0;" Syntax Error.
What's the problem? Thanks for your help!
UPDATE:
The output from macro I want is (takeing t1=30 as an example):
where (age<30 and (
MilkF30 ne 0 or MilkA30 ne 0 or
MilkF40 ne 0 or MilkA40 ne 0 or
MilkF50 ne 0 or MilkA50 ne 0 or
MilkF60 ne 0 or MilkA60 ne 0 or
MilkF70 ne 0 or MilkA70 ne 0
) ) ;
So I have changed my code to
where (age<&&t&I. and
(%Do I = &I. %to 5;
MilkFreq&&t&I. ne 0 or MilkAmnt&&t&I. ne 0 or
%end;
) ) ;
Error:
) ) ; run;
-
22
76
"ERROR: Syntax error while parsing WHERE clause."
So what happened now?

I see a few different things wrong. Consider:
where (age<&&t&I. and (%Do I = &I. %to 5;
MilkF&&t&I. ne 0 or MilkA&&t&I. ne 0 ;
%end;) ) ;
This would generate:
where (age<30 and (
MilkF30 ne 0 or MilkA30 ne 0 ;
MilkF40 ne 0 or MilkA40 ne 0 ;
MilkF50 ne 0 or MilkA50 ne 0 ;
MilkF60 ne 0 or MilkA60 ne 0 ;
MilkF70 ne 0 or MilkA70 ne 0 ;
) ) ;
If you look at the generated WHERE statement, it's invalid due to the extra semicolons. This causes the error message. To fix it, remove the semicolon in your macro at the end of MilkA&&t&I. ne 0 ;
The logic of those OR clauses doesn't look to me like what you describe in the comment, so you might want to check.
As #Reeza points out, you are using the counter &i inside a loop that is using a counter &i. This typically won't cause a syntax error, but will cause either the outer loop to exit prematurely, or continue infinite looping. So you could use &j as the iterator for the inner loop. Also a good idea to declare &i and &j as %LOCAL to the macro, to avoid collisions with any similarly named macro variables in outer scopes.
UPDATE:
Suggest you turn on OPTIONS MPRINT, and look at the code generated by the macro. With your edit:
where (age<&&t&I. and
(%do I = &I. %to 5;
MilkFreq&&t&I. ne 0 or MilkAmnt&&t&I. ne 0 or
%end;
) ) ;
you now have one too many ORs, because it will generate an extra OR at the end, i.e. MilkFreq70 ne 0 or MilkAmnt70 ne 0 or ) )
You could try something like:
where (age < &&t&i and
(%do j = &i %to 5;
MilkFreq&&t&j ne 0 or MilkAmnt&&t&j ne 0
%if &j ne 5 %then or;
%end;
) ) ;

Related

need to use pop function twice to remove last element from the array (perl)

I've just started to learn Perl and joined Euler project to practice coding. This is the first exercise I did. The task was: "If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below 1000." My solution:
use strict;
use warnings;
my #numbers = (1..1000);
my $counter = 0;
my #all_array = ();
my $total = 0;
foreach $counter (#numbers) {
if (($numbers[$counter] % 3 == 0) or ($numbers[$counter] % 5 == 0)) {
push (#all_array, $numbers[$counter]);
}
}
pop (#all_array); #after that the last digit is still in place
pop (#all_array); # only now the number 1000 is removed
my $tot = eval join '+', #all_array; #returns correct value
print $tot;
The final element of the array is 1000. It seems as if it is followed by a space so to remove the number and get the correct result I have to use the pop function twice. The use of local $"='' changes nothing. Besides, I'm getting a message: Use of uninitialized value within #numbers in modulus (%) at C:\Users\Greg\Documents\perl\unt.pl line 10.
What am I doing wrong and how to fix it?
Let's go through your code:
#numbers is an array with the numbers from 1 to 1000
Why do you include 1000 in the list when the exercise says "less than N"?
the for loop
assigns each of the numbers to $counter, i.e. 1, 2, ...
you use $counter as index into #numbers
why do you do that when $counter is already the number you are looking for?
Perl arrays start at index 0, so you have an off-by-one error
you never check 1 because your first number will be $numbers[1] == 2 (OK, doesn't cause an incorrect result for the task at hand...)
you access one element behind the array, i.e. $numbers[1000] == undef
calculating with undef will cause a warning
undef % 3 == 0 is true, hence...
the first pop() will remove undef (from $counter == 1000)
the second pop() will remove 1000 (from $counter == 999)
then you use eval on a string 3 + 5 + 6 + ... a very inefficient way to do a sum :-)
Wouldn't it be just a simpler approach to calculate the sum while running over the numbers from 1 to N-1? F.ex.:
#!/usr/bin/perl
use strict;
use warnings;
foreach my $arg (#ARGV) {
my $sum = 0;
foreach my $number (1..$arg - 1) {
$sum += $number
if ($number % 3 == 0) || ($number % 5 == 0);
}
print "${arg}: ${sum}\n";
}
exit 0;
Test run:
$ perl dummy.pl 10 100 1000 10000
10: 23
100: 2318
1000: 233168
10000: 23331668

SAS: macro send email if exist

I try build little system which will verify that attachment exist (from 4 sources) and if exists - send this attachments with email.
If exist one - then send it.
I found interesting code but I have problem with little verify four files and gluing variable with path to them.
%LET ATTACH1=%STR(C:\XXX\YYYY\ZZZZZ\XYZ_1_&calosc..csv);
%LET ATTACH2=%STR(C:\XXX\YYYY\ZZZZZ\XYZ_2_&calosc..csv);
%LET ATTACH3=%STR(C:\XXX\YYYY\ZZZZZ\XYZ_3_&calosc..csv);
%LET ATTACH4=%STR(C:\XXX\YYYY\ZZZZZ\XYZ_4_&calosc..csv);
%MACRO FINDMYFILE;
%LET ZALACZNIKI = ;
%IF %SYSFUNC(FILEEXIST(&ATTACH1)) %THEN &ZALACZNIKI = &ZALACZNIKI || &ATTACH1 || ' ';
%IF %SYSFUNC(FILEEXIST(&ATTACH2)) %THEN &ZALACZNIKI = &ZALACZNIKI || &ATTACH2 || ' ';
%IF %SYSFUNC(FILEEXIST(&ATTACH3)) %THEN &ZALACZNIKI = &ZALACZNIKI || &ATTACH3 || ' ';
%IF %SYSFUNC(FILEEXIST(&ATTACH4)) %THEN &ZALACZNIKI = &ZALACZNIKI || &ATTACH4 || ' ';
%PUT &ZALACZNIKI;
%IF %SYSFUNC(FILEEXIST(&ATTACH1)) OR %SYSFUNC(FILEEXIST(&ATTACH2)) OR %SYSFUNC(FILEEXIST(&ATTACH3)) OR %SYSFUNC(FILEEXIST(&ATTACH4)) %THEN
%DO;
FILENAME OUTBOX EMAIL
FROM = ("XX SYSTEM REPORT <noreply#systemaccount>")
TO = ("xzy.yzx#email.com")
CC = ("xzy.yzx#email.com")
REPLYTO = ("xzy.yzx#email.com")
SUBJECT = (" testowy ")
ATTACH = ("&ZALACZNIKI.");
DATA _NULL_;
FILE OUTBOX;
PUT "Hello,";
PUT ;
PUT %SYSFUNC(COMPBL(
"This is an example email."));
PUT ;
PUT %SYSFUNC(COMPBL(
"By using COMPBL we remove extra blanks from our text."));
PUT %SYSFUNC(COMPBL(
"There is no separation with this email line."));
RUN;
FILENAME OUTBOX CLEAR;
%END;
%ELSE %PUT NOTE: FILE DOES NOT EXIST AND NO EMAIL WILL BE SENT.;
%MEND FINDMYFILE;
%FINDMYFILE;
Thank for help :)
You're missing a few things here:
%IF %SYSFUNC(FILEEXIST(&ATTACH4)) %THEN &ZALACZNIKI = &ZALACZNIKI || &ATTACH4 || ' ';
You need to use %LET to reassign a macro variable, if that's your intention, and the concatenation operators are not required, but you may need double-quotes around the filename:
%IF %SYSFUNC(FILEEXIST(&ATTACH4)) %THEN %LET ZALACZNIKI = &ZALACZNIKI. "&ATTACH4.";
When you come to use the macro variable, you probably won't need the quotes, otherwise you'll get:
""file1" "file2" "file3""
So just do this:
ATTACH = (&ZALACZNIKI.);
Experiment with this code to learn how it works:
%let file1 = C:\temp;
%let file2 = C:\temp2;
%let z = ;
%let z = &z. "&file1.";
%let z = &z. "&file2.";
%put &z;

Loop recursive through two files in perl

Hi guys I have an issue to solve,
I have 2 files.
File A
col1,col2, value_total_to_put
File A 201843,12345,30
File B
col1,col2,col3, value_inputted, missing_value, value_max
201843,12345,447,4,0,4
201843,12345,448,0,0,4
201843,12345,449,0,0,2
201843,12345,450,4,0,4
201843,12345,451,2,0,2
201843,12345,455,4,0,4
201843,12345,457,0,0,4
201843,12345,899,10,0,10
201843,12345,334,0,1,1
201843,12345,364,0,1,1
201843,12345,71,0,2,2
201843,12345,260,0,2,2
201843,12345,321,0,2,2
201843,12345,328,0,2,2
201843,12345,371,0,2,2
201843,12345,385,0,2,2
201843,12345,426,0,2,2
201843,12345,444,0,2,2
201843,12345,31,4,6,10
201843,12345,360,2,87,99
201843,12345,373,4,95,99
201843,12345,472,4,95,99
201843,12345,475,4,95,99
201843,12345,430,0,99,99
201843,12345,453,0,99,99
201843,12345,463,0,99,99
201843,12345,482,0,99,99
201843,12345,484,0,99,99
My keys are col1 and col2 from both files and I am doing this way below and my loop is wrong because when I reach the EOF from File B my loop is stopped.
What I want is match File A and B with $col1 and $col2 and while the value_total_to_put is > 0 withdraw 1 in each loop and in value_inputted from File B when value_inputted is less than value_max. For withdraw from File A missing_value might be > 0.
For the result I will print when value_inputted is equal to value_max in other words the last value until reach value_max or value_total_to_put is 0.
while ( <FA> ){
chomp;
my($col1,$col2, $value_total_to_put) = split ",";
push #A, [$col1,$col2, $value_total_to_put];
}
my #B;
while ( <FB> ){
chomp;
my($col1,$col2,$col3, $value_inputted, $missing_value, $value_max) = split ",";
push #B, [$col1,$col2,$col3, $value_inputted, $missing_value, $value_max];
}
foreach my $line (#A){
my $idxl = #$line[0].",".#$line[1];
my $value_total_to_put = #$line[2];
while ($value_total_to_put > 0){
foreach my $row ( #B ){
if ( $idxr eq $idxl ){
my $idxr = #$row[0].",".#$row[1];
my $value_inputted = #$row[3];
my $value_max = #$row[5];
my $missing_value = #$row[4];
if ( ($value_inputted eq 0) and ($missing_value eq 0)){
#do_nothing
} elsif($value_inputted == $value_max){
#do_nothing
print join(",", $idxr, #$row[2],"Value_inputted: ".$value_inputted, "Missing_value: ".$missing_value, "Value_max:".$value_max, "Total: ".$value_total_to_put)."\n";
}else{
$value_inputted++;
$missing_value--;
$value_total_to_put--;
}
}
}
last if $value_total_to_put > 0;
}
}
The third file will be this way:
201843,12345,447,4,0,4
201843,12345,450,4,0,4
201843,12345,451,2,0,2
201843,12345,455,4,0,4
201843,12345,899,10,0,10
201843,12345,334,1,0,1
201843,12345,364,1,0,1
201843,12345,71,2,0,2
201843,12345,260,2,0,2
201843,12345,321,2,0,2
201843,12345,328,2,0,2
201843,12345,371,2,0,2
201843,12345,385,2,0,2
201843,12345,426,2,0,2
201843,12345,444,2,0,2
201843,12345,31,10,0,10
201843,12345,360,3,86,99
201843,12345,373,5,94,99
201843,12345,472,5,94,99
201843,12345,475,5,94,99
201843,12345,430,1,98,99
201843,12345,453,1,98,99
As explained by #Dave Cross, your code is quite hard to read (and misses use strictand use warnings), and your explanation of what you are trying to achieve is quite unclear...
One thing that caught my eye however, is that you start a loop with this statement...
while ($value_total_to_put > 0){
... but at the very end of that same block you do :
last if $value_total_to_put > 0;
}
This will effectively cause Perl to exit the loop after the first iteration, no matter the value of the $value_total_to_put variable. This is probably not what you want. Hence, as far as I understand, you should start your investigation by removing that last statement.

Perl: perl regex for extracting values from complex lines

Input log file:
Nservdrx_cycle 4 servdrx4_cycle
HCS_cellinfo_st[10] (type = (LTE { 2}),cell_param_id = (28)
freq_info = (10560),band_ind = (rsrp_rsrq{ -1}),Qoffset1 = (0)
Pcompensation = (0),Qrxlevmin = (-20),cell_id = (7),
agcreserved{3} = ({ 0, 0, 0 }))
channelisation_code1 16/5 { 4} channelisation_code1
sync_ul_info_st_ (availiable_sync_ul_code = (15),uppch_desired_power =
(20),power_ramping_step = (3),max_sync_ul_trans = (8),uppch_position_info =
(0))
trch_type PCH { 7} trch_type8
last_report 0 zeroth bit
I was trying to extract only integer for my above inputs but I am facing some
issue with if the string contain integer at the beginning and at the end
For ( e.g agcreserved{3},HCS_cellinfo_st[10],Qoffset1)
here I don't want to ignore {3},[10] and 1 but in my code it does.
since I was extracting only integer.
Here I have written simple regex for extracting only integer.
MY SIMPLE CODE:
use strict;
use warnings;
my $Ipfile = 'data.txt';
open my $FILE, "<", $Ipfile or die "Couldn't open input file: $!";
my #array;
while(<$FILE>)
{
while ($_ =~ m/( [+-]?\d+ )/xg)
{
push #array, ($1);
}
}
print "#array \n";
output what I am getting for above inputs:
4 4 10 2 28 10560 -1 1 0 0 -20 7 3 0 0 0 1 16 5 4 1 15 20 3 8 0 7 8 0
expected output:
4 2 28 10560 -1 0 0 -20 7 0 0 0 4 15 20 3 8 0 7 0
If some body can help me with explanation ?
You are catching every integer because your regex has no restrictions on which characters can (or can not) come before/after the integer. Remember that the /x modifier only serves to allow whitespace/comments inside your pattern for readability.
Without knowing a bit more about the possible structure of your output data, this modification achieves the desired output:
while ( $_ =~ m! [^[{/\w] ( [+-]?\d+ ) [^/\w]!xg ) {
push #array, ($1);
}
I have added rules before and after the integer to exclude certain characters. So now, we will only capture if:
There is no [, {, /, or word character immediately before the number
There is no / or word character immediately after the number
If your data could have 2-digit numbers in the { N} blocks (e.g. PCH {12}) then this will not capture those and the pattern will need to become much more complex. This solution is therefore quite brittle, without knowing more of the rules about your target data.

Remove Line from File Based on Column Value in Perl

I wish to loop through multiple files, and their respective lines in the file. I have done is successfully already. Want I want to do now is remove lines in a file based on a numeric value in one of the columns.
If I have an input such as this:
XP.sta1 -41.5166 0.0513 0.6842 0.1794 0 CPHI.BHZ 300.2458 -42.2436
XP.sta2 3.5972 0.0500 0.7699 0.1213 0 E000.BHZ 300.5616 2.5545
XP.sta3 3.7112 0.0267 0.7813 0.1457 0 E002.BHZ 300.6140 2.6160
XP.sta4 4.2891 0.0214 0.6870 0.1308 0 E004.BHZ 301.2073 2.6006
where the ninth column is the column I wish to look at. I need to remove that value in column 9 (let's assign it a variable $time), in that if that $time is > 10 or less than -10, remove the entire line. Thus far I have tried this:
unless (($time < -10) || ($time > 10) {
print OUT2 ($stlat," ",$stlon," ",$eqlat," ",$eqlong," ",$eqdepth," ",$time,"\n");
}}
However I get the following output:
XP.sta1 -41.5166 0.0513 0.6842 0.1794 0 CPHI.BHZ 300.2458 2.5545
XP.sta2 3.5972 0.0500 0.7699 0.1213 0 E000.BHZ 300.5616 2.6160
XP.sta3 3.7112 0.0267 0.7813 0.1457 0 E002.BHZ 300.6140 2.6006
XP.sta4 4.2891 0.0214 0.6870 0.1308 0 E004.BHZ 301.2073
As you can see, the entire line isn't deleted -- just the value that meets the true 'unless' condition, and then the other values move up in the 9th column. How do I delete the entire line, rather than just the ninth column number?
Here's where I wish to edit my script:
open(TABLEC,$File);
#tablec = <TABLEC>;
for ($j = 2; $j < $stop; $j++) {
chomp ($tablec[$j]);
($netSta,$delayTime) = (split /\s+/,$tablec[$j])[1,9] ;
}
In this for loop, I'm looping through each file, reading in the lines from 2 to 'stop', and chopming the return character. I set the 9th column to the delay time variable. So I'm looping through each line, but I don't want to print anything yet (that comes later in my script). I would just like to remove that entire line, so that later on in my script when I have to print the lines, the line where the 9th column values is >abs(10) does not exist.
I'd just skip the line:
use warnings;
use strict;
while(<DATA>){
my #split = split;
next if $split[8] > 10 or $split[8] < -10;
print "$_\n";
}
XP.sta2 3.5972 0.0500 0.7699 0.1213 0 E000.BHZ 300.5616 2.5545
XP.sta3 3.7112 0.0267 0.7813 0.1457 0 E002.BHZ 300.6140 2.6160
XP.sta4 4.2891 0.0214 0.6870 0.1308 0 E004.BHZ 301.2073 2.6006
You haven't shown enough of your code to diagnose the problem, but what you ask is very simply done like this
use strict;
use warnings;
while ( <DATA> ) {
print unless abs((split)[8]) > 10;
}
__DATA__
XP.sta1 -41.5166 0.0513 0.6842 0.1794 0 CPHI.BHZ 300.2458 -42.2436
XP.sta2 3.5972 0.0500 0.7699 0.1213 0 E000.BHZ 300.5616 2.5545
XP.sta3 3.7112 0.0267 0.7813 0.1457 0 E002.BHZ 300.6140 2.6160
XP.sta4 4.2891 0.0214 0.6870 0.1308 0 E004.BHZ 301.2073 2.6006
output
XP.sta2 3.5972 0.0500 0.7699 0.1213 0 E000.BHZ 300.5616 2.5545
XP.sta3 3.7112 0.0267 0.7813 0.1457 0 E002.BHZ 300.6140 2.6160
XP.sta4 4.2891 0.0214 0.6870 0.1308 0 E004.BHZ 301.2073 2.6006
I thought your question had been answered, buit here's something that should help you with the contents of your edit
Some points on your code
Identifiers for lexical variables should contain only lower-case letters, decimal digits, and underscore. Capital letters are reserved for global variables such as constants and package names
You should use lexical file handles with the three-parameter form of open
You should always verify that an open succeeded. In the case of a failure your program should die and include the value of $! in the die string to reveal why the operation failed
Together, those points mean that
open(TABLEC, $File);
becomes
open my $tablec_fh, '<', $File or die qq{Unable to open "$File" for input: $!};
You can chomp an entire array at once with chomp #tablec
You should avoid the C-style for loop as it is rarely a good choice. Perl allows you to iterate over a range, and you should make use of that. So
for ($j = 2; $j < $stop; $j++) { ... }
becomes
for my $j ( 2 .. $stop-1 ) { ... }
split /\s+/ should almost always be split ' '. The latter is a special case for the operator, which prevents it from returning an initial empty field if the parameter string has leading spaces. If you call split without any parameters then it defaults to split ' ', $_
Here's a rewrite of your sample code that takes these points into account. I hope it's a better fit than my previous answer
open my $tablec_fh, '<', $File or die qq{Unable to open "$File" for input: $!};
my #tablec = <$tablec_fh>;
chomp #tablec;
close $tablec_fh;
for my $i ( 2 .. $stop-1 ) {
my $row = $tablec[$i];
my ($net_sta, $delay_time) = (split ' ', $row)[0,8];
next unless abs($delay_time) <= 10;
# Do stuff with $row
}