I am new to perl programming and I am trying to build a script using several subroutines on it. For a start I am trying to run a short mocke script to work out subroutines behaviour, but I don't get to understand the input.
Here is my code:
sub prueba{
my (#array1, #array2)=#_;
if (scalar(#array1)<scalar(#array2)) {
print #array1,"\n";
} elsif (scalar(#array1)>scalar(#array2)){
print #array2,"\n";
}
};
my #primero=(1,5,9);
my #segundo=(1,7,8,9,6,5,6,9);
prueba(#primero,#segundo);
I am passing two arrays and I want the subroutine to retrieve the answer according to those arrays, but when I run it I get no output, not even warning errors messages... I already tried using the refference to the array, but still not working:
sub prueba{
my (#array1, #array2)=#_;
if (scalar(#array1)<scalar(#array2)) {
print #array1,"\n";
} elsif (scalar(#array1)>scalar(#array2)){
print #array2,"\n";
}
};
my #primero=(1,5,9);
my #segundo=(1,7,8,9,6,5,6,9);
prueba(\#primero,\#segundo);
You can't pass arrays to subs (and they can't return them). You can only pass a number of scalars. What you are doing is equivalent to the following:
prueba(1,5,9,1,7,8,9,6,5,6,9);
All of the arguments end up in #array1. What we do is pass references to arrays.
prueba(\#primero,\#segundo);
But that also requires changing the sub. Without change, all of the arguments still end up in #array1. See perlreftut for a start on working with references. You can use
sub prueba{
my ($array1, $array2)=#_;
if (scalar(#$array1)<scalar(#$array2)) {
print "#$array1\n";
} elsif (scalar(#$array1)>scalar(#$array2)){
print "#$array2\n";
}
}
or just
sub prueba {
my ($array1, $array2) = #_;
if (#$array1 < #$array2) { say "#$array1"; }
elsif (#$array1 > #$array2) { say "#$array2"; }
}
< and > expect a number, so they already impose scalar context. And might as well use say, though that requires use feature qw( say ); (or something like use 5.014; which does the trick as well).
You can use prototypes to make it look like you're passing multiple arrays, and have perl turn them automatically into references:
sub prueba :prototype(\#\#) {
my ($array1, $array2) = #_;
if (#$array1 < #$array2) {
print #$array1,"\n";
} elsif (#$array1 > #$array2){
print #$array2,"\n";
}
}
my #primero=(1,5,9);
my #segundo=(1,7,8,9,6,5,6,9);
prueba(#primero, #segundo);
But read the documentation carefully to understand the cases where the subroutine can be called without enforcing the prototype.
Thanks all I just figured out what I wanted. I found that I can actually pass an array to perl, however maybe I am not explaning mysfel properly.The thing is to load the arrays as follow, inside the subroutine.
my #primero=#{$_[0]};
my #segundo=#{$_[1]};
This means we are using the reference. Ehen running the function, we must write the \ before each input:
prueba(\#primero,\#segundo);
Related
Contrived example:
use strict;
use warnings;
my $myval = 'a';
my #result = my_sub($myval);
if (#result) {
print "DEFINED\n";
}
my ($res1, $res2, $res3) = #result;
print "res1=$res1, res2=$res2, res3=$res3\n";
sub my_sub {
my $myval = shift;
if ($myval eq 'a') {
return undef;
}
return ("a","b","c");
}
How do I check if sub returned undef?
or
How do I check if sub did not return undef?
return undef in list context returns list of one element, which is undef.
#result = my_sub($myval);
if (#result == 1 && !defined($result[0])) {
warn "my_sub() returned undef";
} else {
print "my_sub() returned data\n";
}
That said, a list with one undef element is almost never what you want. See How do I return nothing from a subroutine? You will generally just want to return with no arguments. In scalar context, that returns undef and in list context it returns an empty list.
sub my_other_sub {
my $myval = shift;
if ($myval eq 'a') {
return;
}
return ("a","b","c");
}
...
#result = my_other_sub($arg1);
$result = my_other_sub($arg2);
if (#result == 0) { # or: if (!#result) ... or: unless (#result) ...
warn "my_other_sub(arg1) did not return any data";
} else {
print "my_other_sub(arg1) returned data\n";
}
if (!defined($result)) {
warn "my_other_sub(arg2) did not return any data";
} else {
print "my_other_sub(arg2) returned data\n";
}
I would recommend, as others have, to use return, or the more explicit return (). This returns an empty list. However, since the example is contrived, we can't be sure that an empty list isn't an otherwise valid return. If it is a valid return, or it might reasonably be so one day, then you have other options which, IMO, are less ideal but can be more flexible.
An obvious one is to use die, as zdim suggested, but that can be relatively heavy-handed. It may actually be really what you want - if this situation really isn't supposed to happen, die is perfect as it might cause your program to abort if you don't wrap the failure in an eval.
Another alternative is to have your sub return an array ref instead of a list. And then you can return undef directly, your caller would be able to check that easily enough: my $result = my_sub(...);. Other uses of that array would just need to go through a dereference, e.g., my ($res1, $res2, $res3) = #$result;. This is probably my preference when a simple return () cannot suffice. Bonus points in that only the reference is passed back, not the whole list. Consider doing this even when an empty list isn't valid but the list can be very large.
Other options also abound, although those are probably the simplest. You could, for example, return a hash (or array) with one entry indicating success/failure and another with the array. You could return success/failure as the first element (your contrived example would return (1, "a", "b", "c") and you'd shift that first element off to see if it was successful or not). You could embed your return in an object that encapsulated all that. Of these, only the object one would I give serious consideration to, but it would greatly depend on the rest of the architecture, and would be very rare.
A subroutine returns a list of scalars in list context which you use, then assigned to variables. So if you return an undef from it the $res variables are going to be undef -- first assigned undef and others unassigned (while the array #result will have one element, an undef)
perl -Mstrict -wE'
my $val = shift;
sub t { return undef if shift eq "bad"; return qw(a b) };
my ($v1, $v2) = t($val);
if (not defined $v1 and not defined $v2) { say "undef" }
else { say "$v1, $v2" }
' bad
The first shift takes the value off of #ARGV, so vary input of "bad" to see other cases. This can be written in more compact and clearer ways if we knew how it's used.
I appreciate that this is a test example, but it is still too convoluted, allowing for tricky edge cases; for example, your first case won't work since #result is not "false" (empty list), what the code tests for, as it does have one element, which is undef.
For such "special" returns either use return with no arguments or throw a die, in situations you consider exceptional. For context-aware returns see wantarray.
I know the newer version is better, but company does not allows me to. So the question is related to AutoHotKey, ver 1.0.47.06.
I am trying to refactor my 400 lines program, by separating them into functions.
CaseNumberArray := "" ; The array to store all the case numbers
CaseNumberArrayCount := 0
; Helper function to load the case number into the array
ReadInputFile() {
Loop, Read, U:\case.txt
{
global CaseNumberArrayCount
CaseNumberArrayCount += 1 ; Increment the ArrayCount
CaseNumberArray%CaseNumberArrayCount% := A_LoopReadLine
current := CaseNumberArray%CaseNumberArrayCount%
}
}
CreateOutputHeader()
ReadInputFile()
MsgBox, There are %CaseNumberArrayCount% case(s) in the file.
Loop, %CaseNumberArrayCount%
{
case_number := CaseNumberArray%A_Index%
MsgBox, %case_number%
}
The last part of the code is testing if I can retrieve the case numbers I loaded into the array named CaseNumberArray, but it is currently all blank.
I studied this question, the author user1944441 wrote:
Important: YourArray must not be global and the counter in
YourArray%counter% must not be global, the rest doesn't matter.
I experimented by placing the global variables in different location, but it still does not work. I know the CaseArrayCount is correctly stored, and the Read Loop is working as well (When it is outside of a function). Is it possible to separate the code into a function?
Usually, global/local declarations are placed right below the method header, not somewhere in some subsequent code block. After all, these declarations apply only to the entire function.
You have to distinguish between simple loop counter variables and variables holding the actual size of the array. In your code, CaseNumberArrayCount describes the size of CaseNumberArray whereas in the answer to which you're referring, it's a counter only used to iterate over the array, which might as well be local.
But you don't have to use two "variables" anyway. Your pseudo array (which can be accessed like CaseNumberArray1, CaseNumberArray2, CaseNumberArray2, ...) has an unused CaseNumberArray0, why not not store the size there?
A pseudo array is actually a collection of sequentially numbered variables. global CaseNumberArray (which by the way you didn't seem to try) will only allow access to the variable named CaseNumberArray, but not CaseNumberArray1 or CaseNumberArray2 and so on.
One solution would be to use Assume-global mode which makes every global variable accessible by default:
; Now, CaseNumberArray0 will hold the array length,
; rendering CaseNumberArrayCount unnecessary
CaseNumberArray0 := 0
; Helper function to load the case number into the array
ReadInputFile() {
; We want to access every global variable we have,
; beware of name conflicts within your function!
global
Loop, Read, test.txt
{
CaseNumberArray0 += 1
CaseNumberArray%CaseNumberArray0% := A_LoopReadLine
}
}
; Here's an alternative: Let AHK build the pseudo array!
ReadInputFileAlternative() {
global caseAlt0
FileRead, fileCont, test.txt
StringSplit, caseAlt, fileCont, `n, `r
}
ReadInputFile()
out := ""
Loop, %CaseNumberArray0%
{
out .= CaseNumberArray%A_Index% "`n"
}
MsgBox, There are %CaseNumberArray0% case(s) in the file:`n`n%out%
; Now, let's test the alternative!
ReadInputFileAlternative()
out := ""
Loop, %caseAlt0%
{
out .= caseAlt%A_Index% "`n"
}
MsgBox, There are %caseAlt0% case(s) in the alternative pseudo-array:`n`n%out%
Edit: "Real Arrays"
As suggested in the comments, here's what I would do instead: I would convince my boss to allow the use of an up-to-date version of AHK and then work with real arrays. This comes with several benefits:
Real arrays are fully managed by AHK, which means that things like inserting, removing, iterating and indexing can all automagically be done by AHK.
A real array resides in one real variable, meaning that you can pass it along functions and anywhere you want, without having to worry about the current scope and whether you can access it in the first place.
The array syntax is very similar to most other languages, making your code intuitive and easier to read. And maybe it helps you in the future when dealing with another language.
Primitive n-dimensional arrays (and primitive AHK objects in general) can be expressed using JSON. This provides you with an easy way to (de-)serialize AHK objects.
The following code snippet shows the two methods used above (reading loop and splitting), but with real arrays. You will notice that we don't need any global declarations anymore, since we now can declare the array inside our function, and simply pass it back to the caller. In my opinion, this is what functions should really look like: A "black box" that doesn't affect its surroundings.
; Method 1: Line by line
ReadLineByLine(file) {
out := []
Loop, Read, % file
{
out.Insert(A_LoopReadLine)
}
return out
}
; Method 2: StrSplit
ReadAndSplit(file) {
FileRead, fileCont, % file
return StrSplit(fileCont, "`n", "`r")
}
caseNumbers := ReadLineByLine("test.txt")
out := "ReadLineByLine() yields " caseNumbers.MaxIndex() " entries:`n`n"
; using the for loop
for idx, caseNumber in caseNumbers
{
out .= caseNumber "`n"
}
MsgBox % out
caseNumbers := ReadAndSplit("test.txt")
out := "ReadAndSplit() yields " caseNumbers.MaxIndex() " entries:`n`n"
; using the normal loop
Loop % caseNumbers.MaxIndex()
{
out .= caseNumbers[A_Index] "`n"
}
MsgBox % out
MsgBox % "The second item is " caseNumbers[2]
I want to get the array that I send in my function but it seem's to be empty.
I call send_file(); with an array in the param
send_file($addr, #curfile);
And this is the way I get back the param
sub send_file($$)
{
my $addr = $_[0];
my #elem = #_;
...
}
Why my #elem is empty ? How could I get back the array without losing everything ?
Don't use prototypes. Their purpose is to change parsing of the source which you don't need.
sub send_file
{
my $addr = shift;
my #elem = #_;
...
}
send_file($addr, #curfile);
You should pass your array by reference instead:
#!/usr/bin/perl
use strict;
use warnings;
my $test_scalar = 10;
my #test_array = qw(this is a test);
sub test($\#)
{
my ($scalar, $array) = #_;
print "SCALAR = $scalar\n";
print "ARRAY = #$array\n";
}
test($test_scalar, #test_array);
system 'pause';
Output:
SCALAR = 10
ARRAY = this is a test
Press any key to continue . . .
EDIT:
If you would like to do the same thing without passing by reference change your $$ to $# and use shift so the first argument doesn't end up included in your array. Passing arrays by reference is better coding practice though . . . This is just to show you how it can be done without passing by reference:
#!/usr/bin/perl
use strict;
use warnings;
my $test_scalar = 10;
my #test_array = qw(this is a test);
sub test($#)
{
my ($scalar, #array) = #_;
print "SCALAR = $scalar\n";
print "ARRAY = #array\n";
}
test($test_scalar, #test_array);
system 'pause';
This will get you the same output.
You can also get rid of the $# altogether if you would like it really isn't necessary.
Why my #elem is empty ?
Your #elem is not empty, it has exactly two elements. First is value of $addr and second is size/number of elements in #curfile array. This is due $$ prototype definition which requires two scalars, so scalar #curfile is passed as second parameter.
How could I get back the array without loosing everything ?
Since you're not using prototype advantages, just omit prototype part,
sub send_file {
my ($addr, #elem) = #_;
...
}
I have the following method:
sub CleanErrorLog {
my ($actnList, $cmplist) = #_;
print "\n" . ("-" x 100) . "\n";
print "\t\t---->> Begin Clean Error Output <<----";
for my $comp (#$cmplist)
{
for my $action (#$actnList)
{
Build($comp, $action);
}
}
}
This is called by:
CleanErrorLog(\#actionList, \#failedComponents) if #failedComponents;
However, the loop never ends - it continuously attempt to Build($comp, $action) over and over. This is the first time I've used \# for parameters, so I could be doing something wrong?
Your Build function probably modified the #actionList or #failedComponents arrays. As you passed your arrays by reference, these modifications could lead to infinite looping. As a guide line, never modify the array or hash you are iterating over. Always do a copy first. For example, you could pass copies to CleanErrorLog:
CleanErrorLog([#actionList], [#failedComponents]) if #failedComponents;
The better solutution would be to rework Build so that it doesn't modify these variables.
I've a question about perl that I used to not bother about in the past, but it's bugging me now.
I have a method call saveItems which takes in a value from a text log and parses the input.
so I have this few lines in the method.
$intime = $_[1];
$timeHr = substr($intime, 0,2);
$timeMin = substr($intime, 2,2);
$timeSec = substr($intime, 5,2);
$object[$_[0]]->hr($timeHr);
$object[$_[0]]->min($timeMin);
$object[$_[0]]->sec($timeSec);
$intime being the value of the time passed into this method.
Sample of $intime: 0431:12
My question is that why does the above not give me any error but when I try to shorten the lines like so :
$object[$_[0]]->hr(substr($intime, 0,2));
$object[$_[0]]->min(substr($intime, 2,2));
$object[$_[0]]->sec(substr($intime, 5,2));
Only the first one works while the rest gives me an out of string error.
I am relatively new to perl, as you can see, but can anyone give me an answer to this?
EDIT
Sample HR:
sub hr {
my $self = shift;
if (#_) { $self->{HR} = shift }
return $self->{HR};
}
EDIT
Case Closed.. Read my answer post
From the comments above, adding .'' after each substr solved your problem. The reason for this is that the ->hr, ->min, and ->sec methods are modifying their argument in some way. Without seeing it further I can't say for certain what is happening.
The substr function returns a value that is a valid lvalue. This means that it can be assigned to. So when something in those methods assigns to the slice from substr, it is interfering with the other methods.
Appending an empty string fixes the problem by breaking the alias between the slice and the original string (stored in $intime).
If you wrote the hr, min and sec methods, you should figure out why they are modifying their arguments. Adding print "[$intime]\n"; statements between each method call should be revealing.
Can you come up with self-contained runnable code that demonstrates the problem? The problem you describe doesn't quite match up with the code you show, though I don't understand #object's role in your code.
The following works just fine:
use strict;
use warnings;
package Class;
sub new { bless {} }
sub saveItems {
my $intime = $_[1];
$_[0]->hr(substr($intime, 0,2));
$_[0]->min(substr($intime, 2,2));
$_[0]->sec(substr($intime, 5,2));
}
sub hr {
my $self = shift;
if (#_) { $self->{HR} = shift }
return $self->{HR};
}
sub min {
my $self = shift;
if (#_) { $self->{MIN} = shift }
return $self->{MIN};
}
sub sec {
my $self = shift;
if (#_) { $self->{SEC} = shift }
return $self->{SEC};
}
package main;
my $object = Class->new();
$object->saveItems( '0431:12' );
print "hr: ", $object->hr(), " min: ", $object->min(), " sec: ", $object->sec(), "\n";
This matter has been resolved.
The way of using substr as follows, are able to perform normally, without errors.
$object[$_[0]]->hr(substr($intime, 0,2));
$object[$_[0]]->min(substr($intime, 2,2));
$object[$_[0]]->sec(substr($intime, 5,2));
However, it is the log file that has trailing blank lines that got this script to fail.
Thanks to #ysth for asking me to reproduce the problem, when I realized that the problem actually lies with the log file instead of the script.
Lesson learnt: Check the codes AND the source before raising an issue