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

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.

Related

Reading data from file into an array to manipulate within Perl script

New to Perl. I need to figure out how to read from a file, separated by (:), into an array. Then I can manipulate the data.
Here is a sample of the file 'serverFile.txt' (Just threw in random #'s)
The fields are Name : CPU Utilization: avgMemory Usage : disk free
Server1:8:6:2225410
Server2:75:68:64392
Server3:95:90:12806
Server4:14:7:1548700
I would like to figure out how to get each field into its appropriate array to then perform functions on. For instance, find the server with the least amount of free disk space.
The way I have it set up now, I do not think will work. So how do I put each element in each line into an array?
#!usr/bin/perl
use warnings;
use diagnostics;
use v5.26.1;
#Opens serverFile.txt or reports and error
open (my $fh, "<", "/root//Perl/serverFile.txt")
or die "System cannot find the file specified. $!";
#Prints out the details of the file format
sub header(){
print "Server ** CPU Util% ** Avg Mem Usage ** Free Disk\n";
print "-------------------------------------------------\n";
}
# Creates our variables
my ($name, $cpuUtil, $avgMemUsage, $diskFree);
my $count = 0;
my $totalMem = 0;
header();
# Loops through the program looking to see if CPU Utilization is greater than 90%
# If it is, it will print out the Server details
while(<$fh>) {
# Puts the file contents into the variables
($name, $cpuUtil, $avgMemUsage, $diskFree) = split(":", $_);
print "$name ** $cpuUtil% ** $avgMemUsage% ** $diskFree% ", "\n\n", if $cpuUtil > 90;
$totalMem = $avgMemUsage + $totalMem;
$count++;
}
print "The average memory usage for all servers is: ", $totalMem / $count. "%\n";
# Closes the file
close $fh;
For this use case, a hash is much better than an array.
#!/usr/bin/perl
use strict;
use feature qw{ say };
use warnings;
use List::Util qw{ min };
my %server;
while (<>) {
chomp;
my ($name, $cpu_utilization, $avg_memory, $disk_free)
= split /:/;
#{ $server{$name} }{qw{ cpu_utilization avg_memory disk_free }}
= ($cpu_utilization, $avg_memory, $disk_free);
}
my $least_disk = min(map $server{$_}{disk_free}, keys %server);
say for grep $server{$_}{disk_free} == $least_disk, keys %server;
choroba's answer
is ideal, but I think your own code could be improved
Don't use v5.26.1 unless you need a specific feature that is available only in the given version of Perl. Note that it also enables use strict, which should be at the top of every Perl program you write
die "System cannot find the file specified. $!" is wrong: there are multiple reasons why an open may fail, beyond that it "cannot be found". Your die string should include the path to the file you're trying to open; the reason for the failure is in $!
Don't use subroutine prototypes: they don't do what you think they do. sub header() { ... } should be just sub header { ... }
There's no point in declaring a subroutine only to call it a few lines later. Put your code for header in line
You have clearly come from another language. Declare your variables with my as late as possible. In this case only $count and $totalMem must be declared outside the while loop
perl will close all open file handles when the program exits. There is rarely a need for an explicit close call, which just makes your code more noisy
$totalMem = $avgMemUsage + $totalMem is commonly written $totalMem += $avgMemUsage
I hope that helps
To your original question about how to store the data in an array...
First, initialize an empty array outside the file read loop:
my #servers = ();
Then, within the loop, after you have your data pieces parsed out, you can store them in your array as sub-arrays (the resulting data structure is a two dimensional array):
$servers[$count] = [ $name, $cpuUtil, $avgMemUsage, $diskFree ];
Note, the square brackets on the right create the sub-array for the server's data pieces and return a reference to this new array. Also, on the left side we just use the current value of $count as an index within the #servers array and as the value increases, the size of the #servers array will grow automatically (this is called autovivification of new elements). Alternatively, you can push new elements onto the #servers array inside the loop, like this:
push #servers, [ $name, $cpuUtil, $avgMemUsage, $diskFree ];
This way, you explicitly ask for a new element to be added to the array and the square brackets still do the same creation of the sub-array.
In any case, the end result is that after you are finished with the file read loop, you now have a 2D array where you can access the first server and its disk free field (the 4-th field at index 3) like this:
my $df = $servers[0][3];
Or inspect all the servers in a loop to find the minimum disk free:
my $min_s = 0;
for ( my $s = 0; $s < #servers; $s++ ) {
$min_s = $s if ( $servers[$s][3] < $servers[$min_s][3] );
}
print "Server $min_s has least disk free: $servers[$min_s][3]\n";
Like #choroba suggested, you can store the server data pieces/fields in hashes, so that your code will be more readable. You can still store your list of servers in an array but the second dimension can be hash:
$servers[$count] = {
name => $name,
cpu_util => $cpuUtil,
avg_mem_usage => $avgMemUsage,
disk_free => $diskFree
};
So, your resulting structure will be an array of hashes. Here, the curly braces on the right create a new hash and return the reference to it. So, you can later refer to:
my $df = $servers[0]{disk_free};

perl - How to replace an empty value in an array without using a variable for comparison?

I have an array where each element comes from a line delimited by tab.
Initial code:
#!/usr/bin/perl -w
use strict;
The code below is a piece of the code.
sub parser_domains {
my #params = #_;
my $interpro_line = "";
my #interpro_vector = ( );
my $idr_sub_id = $params[0];
my $idr_sub_start = $params[1]+1;
my $idr_sub_end = $params[2]+1;
my $interpro_id = "";
my $interpro_start_location = 0;
my $interpro_end_location = 0;
my $interpro_db = "";
my $interpro_length = 0;
my $interpro_db_accession = "";
my $interpro_signature = "";
my $interpro_evalue = "";
my $interpro_vector_size = 0;
my $interpro_sub_file= "";
my $idr_sub_lenght = ($idr_sub_end-$idr_sub_start)+1;
$interpro_sub_file = "$result_directory_predictor/"."$idr_sub_id/"."$idr_sub_id".".fsa.tsv";
#open file; if it fails, print a error and exits.
unless( open(TSV_FILE_DATA, $interpro_sub_file) ) {
print "Cannot open file \"$interpro_sub_file\"\n\n";
return;
}
my #interpro_file_line = <TSV_FILE_DATA>;
close TSV_FILE_DATA;
foreach $interpro_line (#interpro_file_line) {
#interpro_vector = split('\t',$interpro_line);
$interpro_id = $interpro_vector[0];
$interpro_db = $interpro_vector[3];
$interpro_db_accession = $interpro_vector[4];
$interpro_start_location = $interpro_vector[6];
$interpro_end_location = $interpro_vector[7];
$interpro_signature = $interpro_vector[11];
$interpro_length = ($interpro_end_location-$interpro_start_location) + 1;
if ($interpro_signature eq ""){
$interpro_signature = "NOPIR";
printf IDP_REGION_FILE "\nFound a $interpro_db domain with no IPR: starts at $interpro_start_location and ends at $interpro_end_location\n";
printf IDP_REGION_FILE "The size of $interpro_db domain in the sequence is $interpro_length\n";
printf IDP_REGION_FILE "The IDR starts at $idr_sub_start and and ends at $idr_sub_end\n";
printf IDP_REGION_FILE "The size of IDR is $idr_sub_lenght\n";
domains_regions($idr_sub_start,$idr_sub_end,$interpro_start_location,$interpro_end_location,$interpro_signature,$interpro_length,$interpro_db,$idr_sub_id,$interpro_db_accession,$idr_sub_lenght);
}
else{
for $entry_line (#entry_file_line) {
#entry_vector = split('\t',$entry_line);
$entry_ac = $entry_vector[0];
$entry_type = $entry_vector[1];
$entry_name = $entry_vector[2];
chomp($entry_name);
if ($interpro_signature eq $entry_ac) {
printf IDP_REGION_FILE "\nFound a $interpro_db domain with Interpro Signature $entry_ac: starts at $interpro_start_location and ends at $interpro_end_location\n";
printf IDP_REGION_FILE "The size of $interpro_db domain in the sequence is $interpro_length\n";
printf IDP_REGION_FILE "The Interpro Signature $entry_ac belongs to type $entry_type\n";
printf IDP_REGION_FILE "The name of $entry_ac is $entry_name\n";
printf IDP_REGION_FILE "The IDR starts at $idr_sub_start and ends at $idr_sub_end\n";
printf IDP_REGION_FILE "The size of IDR is $idr_sub_lenght\n";
domains_regions($idr_sub_start,$idr_sub_end,$interpro_start_location,$interpro_end_location,$interpro_signature,$interpro_length,$interpro_db,$idr_sub_id,$interpro_db_accession,$idr_sub_lenght);
}
}
}
}
}
A example of tsv file (interproscan):
P51587 14086411a2cdf1c4cba63020e1622579 3418 Pfam PF09103 BRCA2, oligonucleotide/oligosaccharide-binding, domain 1 2670 2799 7.9E-43 T 15-03-2013
P51587 14086411a2cdf1c4cba63020e1622579 3418 ProSiteProfiles PS50138 BRCA2 repeat profile. 1002 1036 0.0 T 18-03-2013 IPR002093 BRCA2 repeat GO:0005515|GO:0006302
P51587 14086411a2cdf1c4cba63020e1622579 3418 Gene3D G3DSA:2.40.50.140 2966 3051 3.1E-52 T 15-03-2013
...
The scripts works perfectly, but the comparison $interpro_signature eq "" provides a warning.
Use of uninitialized value $interpro_signature in string eq at /home/duca/eclipse-workspace/idps/idp_parser_interpro.pl line 666.
So, I searched and tried manners to replace the empty value into the array before the comparison. I would like the empty value by "NOIPR".
I'm working with 9 completed genomes, and I have more than 324000 proteins to parse.
How can I replace the empty values in my array?
Thanks.
Your array may not have 12 elements (or the 12-th element may be undef)
my $interpro_signature = $interpro_vector[11] // 'some_default_value';
The // is the defined-or operator.
The error Use of uninitialized value means that the variable hasn't been initialized, or it's been set to undef.
See perldiag and use it regularly. Run code with perl -Mdiagnostics ... on errors, regularly.
The use warnings; is actually better than -w.
Update to a substantial edit of the question
From shown data it appears that yet other fields may not be given in the file; so proof all variables with defaults, much like for the array element at index 11 above. This is what you want to do in general anyway. For example, if there are all fields in the file but some may be empty (two tabs with nothing in between)
my #interpro_defaults = ('id_default', 'db_default', ...);
my ($interpro_id, $interpro_db, ...) =
map {
$interpro_vector[$_] // $interpro_defaults[$_]
} 0 .. $#interpro_defaults;
This relies on order (of variables) in the list, what can be error prone with variables; see below.
If some fields are simply not there there may be (far) more work to do.
There are too many separate variables, all related and named as $interpro_X (and then there are $idr_Y and $entry_Z, but fewer and perhaps manageable).
Can you not bundle them in a container-type variable or a data structure?
A hash %interpro seems suitable, with keys X (so, $interpro{id} etc). Then you can use them more easily and can perform some actions on the whole lot. You still have to watch for order when initializing since they are read sequentially, but it should be clearer this way. For example
my #interpro_vars = qw(id db db_accesssion ...);
my #interpro_vector = qw(id_default db_default ...);
my %interpro;
#interpro{#interpro_vars} = #interpro_vector;
# or simply
#interpro{qw(id db ...)} = qw(id_default db_default ...);
I've defined arrays with keys and values first and then used them, in case that you may want to later have those lists in arrays. If that's not the case you can initialize the hash with lists (the last line).
Here
my %h;
#h{LIST-keys} = LIST-values;
is a way to assign the list of LIST-values to the set of keys of the hash %h given in LIST-keys. They are assigned one for one, in the given order of both lists (which had better match in size). There is the # sigil in front of hash's keys since we are having a list (of keys) there, not a hash. Note that the hash must have been declared somewhere. See slices in perldata.
The problem is that your 3rd line contains only 9 elements. So
#interpro_vector = split('\t',$interpro_line);
for that line assigns only 9 elements to #interpro_vector but you then access $interpro_vector[11] (i.e. the 12th element) and that doesn't exist. You can now either check that #interpro_vector contains (at least) 12 elements:
if (#interpro_vector >= 12) {
...
}
Or you can use the defined-or operator as #zdim suggested to use a default value in case $interpro_vector[11] isn't defined:
$interpro_signature = $interpro_vector[11] // '';
The above line is equivalent to
if (defined $interpro_vector[11]) {
$interpro_signature = $interpro_vector[11];
} else {
$interpro_signature = '';
}
Now
if ($interpro_signature eq "") {
...
}
will work.

AutoHotKey Adding to Array within function call

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]

Perl: Use of reference "HASH(0x13bd718)" as array index at

I'm writing a script to implement game 2048 to practice perl, and an error pops up occationally as something like this:
Use of reference "HASH(0x13bd718)" as array index at 2048.pl line 181, line 11.
The code involved is for a subroutine like this:
sub gen1{
my #free_locs = {};
my $length;
my $rand_loc;
my $insert_loc;
for ($i=0; $i<16; $i++){
if($all_lines[$i] == 0){push #free_locs, $i;}
}
$length = #free_locs;
$rand_loc = int rand $length;
if ($rand_loc == $length) {$rand_loc--;}
$insert_loc = $free_locs[$rand_loc];
$all_lines[$insert_loc] = &generate();
&row_update;
&col_update;
}
At first I was writing that like as
$all_lines[$free_locs[$rand_loc]] = &generate();
The error seems to pop up more often. Then I switched to the code in the subroutine shown above, which seems to reduce the chance for it to happen, but it still happens...
Is there anything wrong with my way of coding here? What's the cleanest way of writing such piece of code?
Thanks and regards,
Terry
my #free_locs = {}; declares #free_locs to be a one-element array whose first element is a reference to an empty hash ({}). Thus, whenever $rand_loc is 0, $insert_loc will be a hash, and trying to use that hash as an index in $all_lines[$insert_loc] produces the error. To declare #free_locs as an empty array, write either my #free_locs = (); or just my #free_locs;.

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

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

Resources