Let's consider simple Perl code:
my #x = ( 1, 5, 9);
for my $i ( 0 .. $#x ) {
splice( #x, $i, 1 ) if ( $x[$i] >= 5 );
}
print "#x";
Output is not correct, 1 9 but there must be 1
If we run code with -w flag it prints warning
Use of uninitialized value within #x in numeric ge (>=) at splice.pl line 5.
So, it's not good practise to use conditional splice and better to push result in new variable?
The problem isn't your use of conditional splice per se, it's your loop. The most obvious problem, and the one that causes your warning, is that you're running off of the end of the array. for my $i ( 0 .. $#x ) sets the iteration endpoint to $#x before the loop starts, but after you splice one or more elements out, the last index of the array will be smaller. You could fix that using a C-style for loop, instead of the range-style loop, but I don't recommend it — keep reading.
The next problem is that after you splice an element out of the array, you continue the loop with $i one higher... but because you spliced an element out of the array, the next element that you haven't seen yet is in $x[$i], not $x[$i+1]. You say "Output is correct, 1 9", but shouldn't 9 have been removed, since it's more than 5? You could fix this using redo after splice to go through the loop again without incrementing $i, but I don't recommend that either.
So it is possible to fix your loop which uses splice in place so that it will work correctly, but the result would be pretty complicated. Unless there's a compelling reason to do it differently, I would recommend using simply
#x = grep { $_ < 5 } #x;
There's no problem with assigning the result to the same array as the source, and there is no loop management or other housekeeping for you to do.
Related
Small debug question I can't solve for some reason. Consider the following code:
use warnings;
my $flag = 0;
foreach my $i (0..scalar(#ARGV)) {
$data{$OPTION} .= $ARGV[$i]." " if($flag);
$flag = 1 if($ARGV[$i] =~ /$OPTION/);
undef $ARGV[$i] if($flag);
}
I get the following two warnings:
Use of uninitialized value within #ARGV in concatenation (.) or string at line 4
Use of uninitialized value in pattern match (m//) at line 5
I get the reason is that I undefine some value of #ARGV and then it tries to check it.
The way I do it like this is because I would like to 'cut' some of the data of #ARGV before using GetOpt module (which uses this array).
How to solve it?
Let's expand on those comments a bit.
Imagine #ARGV contains four elements. They will have the indexes 0, 1, 2 and 3 (as arrays in Perl are zero-based).
And your loop looks like this:
foreach my $i (0..scalar(#ARGV)) {
You want to visit each element in #ARGV, so you use the range operator (..) to generate a list of all those indexes. But scalar #ARGV returns the number of elements in #ARGV and that's 4. So your range is 0 .. 4. And there's no value at $ARGV[4] - so you get an "undefined value" warning (as you're trying to read past the end of an array).
A better way to do this is to use $#ARGV instead of scalar #ARGV. For every array variable in Perl (say #foo) you also get a variable (called $#foo) which contains the last index number in the array. In our case, that's 3 and your range (0 .. $#ARGV) now contains the integers 0 .. 3 and you no longer try to read past the end of the array and you don't get the "undefined value" warnings.
There's one other improvement I would suggest. Inside your loop, you only ever use $i to access an element from #ARGV. It's only used in expressions like $ARGV[$i]. In this case, it's probably better to skip the middle man and to iterate across the elements in the array, not the indexes.
I mean you can write your code like this:
foreach my $arg (#ARGV) {
$data{$OPTION} .= $arg . " " if($flag);
$flag = 1 if($arg =~ /$OPTION/);
undef $arg if($flag);
}
I think that's a little easier to follow.
I have a certain array from which I want to remove the last three element (constant number, because I want a list of subdirectory which are in a directory where there is 3 files).
Is there a better and nicer solution than this one ?
my #list = (`ls --group-directories-first`);
pop #list;
pop #list;
pop #list;
You can use splice:
splice #list, -3;
Where -3 denotes an offset of 3 from the end. This will remove elements from that offset and onward.
It's worth noting that parsing output from ls is a horrible idea. You can do the exact same thing with Perl code:
my #list = grep -d, glob "*";
The -d is a file check, which checks if the current argument is a directory.
splice is also the solution I'd advise.
However, since you're working with the end of an array and just wanting to destroy those trailing elements instead of saving them to another structure, you could also just edit the last array index:
$#list -= 3;
The above is reduces the size of the #list array by 3 elements. Here's another example:
my #a = (1..10);
$#a -= 4;
print "#a\n";
# Prints: 1 2 3 4 5 6
Consider the following code:
my #candidates = get_candidates($marker);
CANDIDATE:
for my $i (0..$#candidates) {
next CANDIDATE if open_region($i);
$candidates[$i] = $incumbent{ $candidates[$i]{region} };
}
What is meaning $# in line 3?
It is a value of last index on array (in your case it is last index on candidates).
Since candidates is an array, $#candidates is the largest index (number of elements - 1)
For example:
my #x = (4,5,6);
print $#x;
will print 2 since that is the largest index.
Note that if the array is empty, $#candidates will be -1
EDIT: from perldoc perlvar:
$# is also used as sigil, which, when prepended on the name of
an array, gives the index of the last element in that array.
my #array = ("a", "b", "c");
my $last_index = $#array; # $last_index is 2
for my $i (0 .. $#array) {
print "The value of index $i is $array[$i]\n";
}
This means array_size - 1. It is the same as (scalar #array) - 1.
In perl ,we have several ways to get an array size ,such as print #arr,print scalar (#arr) ,print $#arr+1 and so on.No reason ,just use it.You will get familiar with some default usage in perl during your further contact with perl .Unlike C++/java ,perl use a lot of
special expression to simplify our coding , but sometimes it always make us more confused.
I wanted to chop off all but the first five elements of an array, so I stupidly did:
#foo = #foo[ 0 .. 4 ];
and heartily praised my own cleverness. But that broke once #foo ended up with only three elements, because then I ended up with two undefs on the end, instead of a three-element array. So I changed it to:
#foo = #foo > 5 ? #foo[ 0 .. 4 ] : #foo;
This works but is kinda ugly. Is there a better idiom for saying "give me everything up to the first five elements of the array?"
You can set the last index of an array to shorten or lengthen it. Like your code you'll need to check to make sure your not creating undef elements.
$#foo = 4 if $#foo > 4;
If you don't care about mutations (implied by the self-referential lhs #foo = something referencing #foo) use the two-argument splice(), see perldoc -f splice for more info.
splice ARRAY,OFFSET
Removes the elements designated by OFFSET and LENGTH from an array, and replaces them with the elements of LIST, if any. In list context, returns the elements removed from the array. In scalar context, returns the last element removed, or "undef" if no elements are removed. The array grows or shrinks as necessary. If OFFSET is negative then it starts that far from the end of the array. If LENGTH is omitted, removes everything from OFFSET onward. If LENGTH is negative, removes the elements from OFFSET onward except for -LENGTH elements at the end of the array. If both OFFSET and LENGTH are omitted, removes everything. If OFFSET is past the end of the array, perl issues a warning, and splices at the end of the array.
Then watch the effect:
#_ = 1..10;
splice #_, 5;
say for #_;
#_ = 1..3;
splice #_, 5;
say for #_;
If you're using warnings, and I hope you'll have to check for the length (as in Axeman's suggestion) or disable the noisy warning (splice() offset past end of array):
{
no warnings 'misc';
splice #_, 5;
}
Yet another way:
#foo = splice(#foo, 0, 5);
Unlike the other suggestion for splice, this doesn't trigger a warning; the 5 explicitly means "up to 5".
This isn't that graceful, but you can express it like this:
#foo[ 0..( $#foo > 4 ? 4 : $#foo ) ];
A generalized min function might look better.
use List::Util qw<min>;
#foo[ 0..min( $#foo, 4 ) ];
But if you just want to get rid of everything else then you just need to splice off the rest:
splice( #foo, 5 ) if 5 < #foo;
I've come across the following line of code. It has issues:
it is intended to do the same as push
it ought to have used push
it's hard to read, understand
I've since changed it to use push
it does something I thought was illegal, but clearly isn't
here it is:
$array [++$#array] = 'data';
My question is: what does it mean to pre-increment $#array? I always considered $#array to be an attribute of an array, and not writable.
perldata says:
"The length of an array is a scalar value. You may find the length of array #days by evaluating $#days , as in csh. However, this isn't the length of the array; it's the subscript of the last element, which is a different value since there is ordinarily a 0th element. Assigning to $#days actually changes the length of the array. Shortening an array this way destroys intervening values. Lengthening an array that was previously shortened does not recover values that were in those elements."
Modifying $#array is useful in some cases, but in this case, clearly push is better.
A post-increment will return the variable first and then increment it.
If you used post-increment you would be modifing the last element, since its returned first, and then pushing an empty element onto the end. On the second loop you would be modifing that empty value and pushing a new empty one for later. So it wouldn't work like a push at all.
The pre-increment will increment the variable and then return it. That way your example will always being writing to a new, last element of the array and work like push. Example below:
my (#pre, #post);
$pre[$#pre++] = '1';
$pre[$#pre++] = '2';
$pre[$#pre++] = '3';
$post[++$#post] = '1';
$post[++$#post] = '2';
$post[++$#post] = '3';
print "pre keys: ".#pre."\n";
print "pre: #pre\n";
print "post keys: ".#post."\n";
print "post: #post\n";
outputs:
pre keys: 3
pre: 2 3
post keys: 3
post: 1 2 3
Assigning a value larger than the current array length to $#array extends the array.
This code works too:
$ perl -le 'my #a; $a[#a]="Hello"; $a[#a]=" world!"; print #a'
Hello world!
Perl array is dynamic and grows when assign beyond limits.
First of all, that's foul.
That said, I'm also surprised that it works. I would have guessed that ++$#array would have gotten the "Can't modify constant" error you get when trying to increment a number. (Not that I ever accidentally do that, of course.) But, I guess that's exactly where we were wrong: $#array isn't a constant (a number); it's a variable expression. As such you can mess with it. Consider the following:
my #array = qw/1 2 3/;
++$#array;
$array[$#array] = qw/4/;
print "#array\n"
And even, for extra fun, this:
my #array = qw/1 2 3/;
$#array += 5;
foreach my $wtf (#array) {
if (defined $wtf) {
print "$wtf\n";
}
else {
print "undef\n";
}
}
And, yeah, the Perl Cookbook is happy to mess with $#array to grow or truncate arrays (Chapter 4, recipe 3). I still find it ugly, but maybe that's just a lingering "but it's a number" prejudice.