Why does substr work differently when passed directly into a method? - arrays

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

Related

Laravel Checking if Array or Collection is not Empty to run

I have an object of $person as below:
$person = Person::where('id', $id)->first();
According to which $person exists or not I load other data:
if($person) {
$person->family_members = FamilyMemberController::FamilyMemberOf($person->id);
} else {
$person->family_members = [];
}
In the view file, I check the $person->family_members if not empty and exists to add a generated value :
if(!empty(array_filter($person->family_members))) {
// my code
}
But it throws an error:
array_filter(): Argument #1 ($array) must be of type array, Illuminate\Database\Eloquent\Collection given
I need to check this $person->family_members to make sure whether it's an array or a collection is not empty.
Writing code for if array do something if collection do something is the wrong way of implementation.
You can do two things.
use both returns as collection()
or either use both returns as an array[]
If collection
else {
$person->family_members = collect();
}
If array
use ->toArray() at the end of Eloquent. Check this answer
As well, I think you are confused with array_filter(). Maybe you are searching for in_array() or contains()
Use count method
if(count($person->family_members)>0){
//your code
}
We don't know your code, but given the error, it's safe to assume your method returns a Collection. You can see available methods in the docs. However, if you need it as array, all you need to do is call ->toArray() on the result Collection. Then you can use array_filter.
What about just doing
if(!$person->family_members){
// your code here
}
or
if($person->family_members){
// your code here
} else {
// code of "if it's empty" goes here
}
You can use the count() function to return a count of an index. ex
if(count($person->family_members)){
//do your true algo
}
Why you are using the empty method ?! Try this:
$person->family_members = $person ? FamilyMemberController::FamilyMemberOf($person->id) : null;
if($person->family_members){ // your code }
Try simple ways in Programming ;)

Passing array arguments to Perl subroutine

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);

check if sub returned undef

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.

Perl: array goes empty after passing it to a function?

I'm working on a project that's scalating a lot, lately, and I'm re-writing code to make it more OOP and passing all redundant code into sub-routines.
The script checks whether a gene exists in the database (through various means) or not. It may also report possible duplicates. Before reporting a duplicate, the script makes sure it's not a "biological duplicate" (essentially the same biological data but a with different position in the genome and, hence, not an actual duplicate). In order to do so...
my #gene_ids;
my #gene_names;
while(my $gene = $geners_bychecksum->next){
my $gene_name = $gene->gene_name;
my $gene_id = $gene->gene_id;
push #gene_ids, $gene_id;
push #gene_names, $gene_name;
}
print STDERR "$id\tJ\tALERT CHECKSUM MULTI-HIT\t(".join(",",#gene_names).")\n";
my $solve_multihit = solve_multihit($id, \#gene_names, \#gene_ids, $spc, $species_directory, $dataset);
print STDERR "$id\tJ\tALERT CHECKSUM MULTI-HIT\t(".join(",",#gene_names).")\n";
if($solve_multihit){
print STDERR "$id\tM\tUPDATE \n";
print $report "$id\tM\tUPDATE \n";
$countM++;
} else {
print STDERR "$id\tJ\tALERT CHECKSUM MULTI-HIT\t(".join(",",#gene_names).")\n";
}
Here, $geners_bychecksum is a DBIC resulset with database hits from a prior search and, for this case-scenario, it always has more than 1 gene. The $id,$spc,$species_directory and $dataset are all strings that come from the config and are defined above this chunk.
The solve_multihit subroutine is a rather complicated function that tries to resolve whether the multi-hits are actual duplicates or biological duplicates. Notice that I'm passing the #gene_names and #gene_ids arrays to this function. This function will return the gene_id of the proper gene, if it was able to solve the discrepancy; or 0 if not. Simplified code for the sub can be found in the following link
https://codeshare.io/2EM8qN
THE ACTUAL QUESTION
You may have noticed that the
print STDERR "$id\tJ\tALERT CHECKSUM MULTI HIT\t(".join(",",#gene_names).")\n";
is both before and after the solve_multihit subroutine call... and the array seems to go empty after running the function, according to the STDERR:
BBOV_I005030 J ALERT CHECKSUM MULTI-HIT (XP_001609152.1,XP_001609157.1)
BBOV_I005030 J ALERT CHECKSUM MULTI-HIT ()
BBOV_I005040 J ALERT CHECKSUM MULTI-HIT (XP_001609156.1,XP_001609153.1)
BBOV_I005040 J ALERT CHECKSUM MULTI-HIT ()
BBOV_I005050 J ALERT CHECKSUM MULTI-HIT (XP_001609154.1,XP_001609155.1)
BBOV_I005050 J ALERT CHECKSUM MULTI-HIT ()
BBOV_I005060 J ALERT CHECKSUM MULTI-HIT (XP_001609154.1,XP_001609155.1)
BBOV_I005060 J ALERT CHECKSUM MULTI-HIT ()
BBOV_I005070 J ALERT CHECKSUM MULTI-HIT (XP_001609156.1,XP_001609153.1)
BBOV_I005070 J ALERT CHECKSUM MULTI-HIT ()
BBOV_I005080 J ALERT CHECKSUM MULTI-HIT (XP_001609152.1,XP_001609157.1)
BBOV_I005080 J ALERT CHECKSUM MULTI-HIT ()
Why would that happen? I'm pretty sure I could solve it by returning the arrays along with the results of the solve_multihit{} sub, but I wonder why would it go empty.
PS: The J in the report is just a case-scenario key code.
my #gene_names = splice(shift);
my #gene_ids = splice(shift);
is short for
my #gene_names = splice(#{ shift(#_) });
my #gene_ids = splice(#{ shift(#_) });
splice(#a) empties the array and returns its contents. There's no reason to do that! The above should be
my #gene_names = #{ shift(#_) };
my #gene_ids = #{ shift(#_) };
Honestly, there's no need to make a copy of the array. Just use the provided reference.
my $gene_names = shift;
my $gene_ids = shift;
I'd provide a fixed-up version of solve_multihit, but it has numerous major problems I can't fix with the information I have.
I can see two ways for your code to accomplish the data removal that it seems to be doing.
The function arguments available in #_ are aliased to data passed to it. So if you change #_ itself (or its elements) you change the data outside of the function.
More likely, as you are passing by reference, your sub probably works directly with it
sub ff {
my ($rary) = #_;
#$rary = ();
}
my #data = 1..4;
ff(\#data);
say for #data; # empty
If your processing needs to change the array it works with then make a local copy first
sub ff {
my ($rary) = #_;
my #local_ary = #$ary;
# now changes to #local_ary do not affect #data in the caller
}
This is generally safer, while it does introduce a data copy which doesn't happen when working with the reference.
The edit together with ikegami's answer clears this up: splice is destructive to the array it works with and here by curious syntax it's fed an anonymous array formed out of a dereferenced #_ argument, whereby it changes the data in the caller.
There is no reason for splice in what you do. Its purpose is to change the array.
Instead, use arrayrefs that are passed to the sub
sub solve_multihit {
my ($id, $gene_names, $gene_ids, ...) = #_;
foreach my $name (#$gene_names) {
...
}
...
}
or make a local copy if you wish
sub solve_multihit {
my $id = shift;
my #gene_names = #{ shift #_ };
...
}
where my #gene_names is a lexical variable in this scope (the sub in your case ) and changes to it do not affect the one with the same name in the calling scope.

get param function array in perl

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) = #_;
...
}

Resources