I have a Perl-Script, which executes a recursive function. Within it compares two elements of a 2dimensional Array:
I call the routine with a 2D-Array "#data" and "0" as a starting value. First I load the parameters into a separate 2D-Array "#test"
Then I want to see, if the array contains only one Element --> Compare if the last Element == the first. And this is where the Error occurs: Modification of non creatable array value attempted, subscript -1.
You tried to make an array value spring into existence, and the subscript was probably negative, even counting from end of the array backwards.
This didn't help me much...I'm pretty sure it has to do with the if-clause "$counter-1". But I don't know what, hope you guys can help me!
routine(#data,0);
sub routine {
my #test #(2d-Array)
my $counter = $_[-1]
for(my $c=0; $_[$c] ne $_[-1]; $c++){
for (my $j=0; $j<13;$j++){ #Each element has 13 other elements
$test[$c][$j] = $_[$c][$j];
}
}
if ($test[$counter-1][1] eq $test[-1][1]{
$puffertime = $test[$counter][4];
}
else{
for (my $l=0; $l<=$counter;$l++){
$puffertime+= $test[$l][4]
}
}
}
#
#
#
if ($puffertime <90){
if($test[$counter][8]==0){
$counter++;
routine(#test,$counter);
}
else{ return (print"false");}
}
else{return (print "true");}
Weird thing is that I tried it out this morning, and it worked. After a short time of running he again came up with this error message. Might be that I didn't catch up a error constellation, which could happen by the dynamic database-entries.
Your routine() function would be easier to read if it starts off like this:
sub routine {
my #data = #_;
my $counter = pop(#data);
my #test;
for(my $c=0; $c <= $#data; $c++){
for (my $j=0; $j<13;$j++){ #Each element has 13 other elements
$test[$c][$j] = $data[$c][$j];
}
}
You can check to see if #data only has one element by doing scalar(#data) == 1 or $#data == 0. From your code snippet, I do not see why you need to copy the data to passed to routine() to #test. Seems superfluous. You can just as well skip all this copying if you are not going to modify any of the data passed to your routine.
Your next code might look like this:
if ($#test == 0) {
$puffertime = $test[0][4];
} else {
for (my $l=0; $l <= $counter; $l++) {
$puffertime += $test[$l][4];
}
}
But if your global variable $puffertime was initialized to zero then you can replace this code with:
for (my $l=0; $l <= $counter; $l++) {
$puffertime += $test[$l][4];
}
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 am having an error saying that prototype not terminated at filename.txt line number 113 where as line number 113 belongs to a different program which is running successfully.
sub howmany(
my #H = #_;
my $m = 0;
foreach $x (#H) {
if ( $x > 5 ) {
$m +=1;
}
else {
$m +=0;
}
}
print "Number of elements greater than 5 is equal to: $m \n";
}
howmany(1,6,9);
The sub keyword should be followed by { } not ( ) (if you define a simple function), that's why the error
prototype not terminated
After this, always start with : use strict; use warnings;
Put this and debug your script, there's more errors.
Last but not least, indent your code properly, using an editor with syntax highlighting, you will save many time debugging
The error is due to parenthesis.
Never do $m += 0; As you actually load processor for nothing. Of course it's not gonna be visible on such a small function, but...
sub howmany {
my $m = 0;
foreach (#_) {
$m++ if ($_ > 5);
}
print "Number of elements greater than 5 is equal to: $m \n";
}
howmany(1,6,9);
I need to test a file with each line having the same number of columns and each entry is some value and I want to only select those lines with every values greater than, say 0.5.
I know I can loop through the array in each line by doing something like this:
open (IN, shift #ARGV);
while (<IN>){
chomp;
my $count = 0;
my #array = split/\t/;
foreach (#array){
if ($_ > 0.5) {
$count ++;
}
}
if ($count == scalar #array){
print $_,"\n";
}
}
close IN;
This is kinda long and I'm wondering if there is a better way to do it?
Thanks.
Use all from List::Util - it checks if passed code block (often with condition) returns true for all elements of list.
use List::Util qw(all);
if (all { $_ > 0.5 } #array) {
print "Pass!"
}
It will even short-circuit for you, terminating as soon as it finds first false value, producing most speed-effective result.
my #array = 10 .. 20;
# compare size of array with list size of grep
if (#array == grep { $_ > 0.5 } #array) {
print "All are greater than 0.5\n";
}
Is there any way that number of elements iterated in an for loop can be traced in perl:
Like using special variables:
#arrayElements = (2,3,4,5,6,7,67);
foreach (#arrayElements) {
# Do something
# Want to know how may elements been iterated after
# some n number of elements processed without using another variable.
}
Either just count as you go:
my $processed = 0;
foreach my $element (#array_elements) {
...
++$processed;
}
or iterate over the indexes instead:
foreach my $index (0..$#array_elements) {
my $element = $array_elements[$index];
...
}
In perl5 v12 and later, you can use the each iterator:
while(my($index, $element) = each #array_elements) {
...;
}
However, the more portable solution is to iterate over the indices, and manually access the element, as shown by ysth.
In any case, the number of elements that were visited (including the current element) is $index + 1.
You may get the number of elements in an array as
my $num = #arrayElements;
print $num;
OR
my $num = scalar (#arrayElements);
print $num;
OR
my $num = $#arrayElements + 1;
print $num;
And for finding number of elements iterated, we can use the below code :
my $count = 0; #initially set the count to 0
foreach my $index(#arrayElements)
{
$count++;
}
print $count;
I started learning Perl last week.
I have an associative array from a file containing 'tokens' - Just a bunch of numbers.
I have another associative array from an SQL Database containing 'tokens'.
I'm wanting to see if any tokens in the file are NOT in the database. However anything I do doesn't seem to work and I've come to the conclusion that I'm just confusing myself.
I'm not sure I fully understand associative arrays yet but this is a snippet of my code for the file hash:
while($row = <FILE>){
if($row =~ /^000\E/){
#tmp=split(/\s+/,$row);
if($tmp[1] ne "Unassigned"){
$tokenfile{$tmp[0]} = $tmp[1] . " " . $tmp[2];
}
}
}
$tmp[1] + $tmp[2] are the first and second names. I compare names later on to see if they equal each other. However I want to compare $tmp[0] - The token. This is the SQL hash:
while(#rows = $sth->fetchrow_array){
($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwnam("\L$rows[1]\E");
$gcos =~ s/,.*//;
if(!defined($gcos)){
$missing++;
$tokendb{$rows[0]} = $rows[1];
}
else{
$tokendb{$rows[0]} = $gcos;
}
}
$rows[0] is the token.
I assumed I would use two foreach loops such as this:
foreach $token (keys(%tokendb)) {
foreach $token2(keys(%tokenfile)){
if($token ne $token2){
print "$token2 NOT IN DATABASE\n";
}
}
}
But that gives me the result of a lot of values that are still in the database.
I'd love some hints as to why this isn't working. Very frustrating as I know it's something so simple but my brain isn't working so well today (Even though it's my 21st Birthday :|).
foreach $token (keys(%tokenfile)) {
if (! exists $tokendb{$token}) {
print "$token NOT IN DATABASE\n";
}
}
Your nested loop failed because even if a key exists, it doesn't match all the other keys. To do it with a nested loop, it should be:
foreach $token (keys(%tokenfile)) {
$found = 0;
foreach $token2 (keys(%tokendb)) {
if ($token eq $token2) {
$found = 1;
last;
}
}
if (!found) {
print "$token NOT IN DATABASE\n";
}
}
Of course, there's no reason to write it this way, this is just to help you understand how your logic failed.
If you're iterating over a hash and testing every key individually to see whether one of them is a target value, then you're not taking advantage of the power of hashes: Lookups. Try something like
foreach $token (keys(%tokenfile)) {
unless (exists $tokendb{$token}) {
print "$token NOT IN DATABASE\n";
}
}
instead.