How does foreach(#array, $var) work in Perl? - arrays

I am trying to figure out some Perl code someone else wrote, and I'm confused with the following syntax of foreach loop
foreach (#array, $var){
....
}
This code runs, but nobody else uses it based on my google search online. And it doesn't work the same way as the more common way of foreach with arrays, which is:
foreach $var (#array){
....
}
Could someone explain this syntax?

foreach (#array, $var){ .... }
is short for
foreach $_ (#array, $var){ .... }
which is short for
foreach $_ ($array[0], $array[1], ... $array[N], $var){ .... }
So on each iteration of the loop, $_ is set to be each element of the array and then finally to be $var. The $_ variable is just a normal perl variable, but one which is used in many places within the Perl syntax as a default variable to use if one is not explicitly mentioned.

Perl's for() doesn't operate on an array, it operates on a list.
for (#array, $var) {...}
...expands #array and $var into a single list of individual scalar elements, and then iterates over that list one element at a time.
It's no different than if you had of done:
my #array = (1, 2, 3);
my $var = 4;
push #array, $var;
for (#array) {...}
Also, for my $var (#array, $var) {...} will work just fine, because with my, you're localizing a new $var scalar, and the external, pre-defined one is left untouched. perl knows the difference.
You should always use strict;, and declare your variables with my.

Related

Array got flushed after while loop within a filehandle

I got a problem with a Perl script.
Here is the code:
use strict;
use Data::Dumper;
my #data = ('a','b');
if(#data){
for(#data){
&debug_check($_)
}
}
print "#data";#Nothing is printed, the array gets empty after the while loop.
sub debug_check{
my $ip = shift;
open my $fh, "<", "debug.txt";
while(<$fh>){
print "$_ $ip\n";
}
}
Array data, in this example, has two elements. I need to check if the array has elements. If it has, then for each element I call a subroutine, in this case called "debug_check". Inside the subroutine I need to open a file to read some data. After I read the file using a while loop, the data array gets empty.
Why the array is being flushed and how do I avoid this strange behavior?
Thanks.
The problem here I think, will be down to $_. This is a bit of a special case, in that it's an alias to a value. If you modify $_ within a loop, it'll update the array. So when you hand it into the subroutine, and then shift it, it also updates #data.
Try:
my ( $ip ) = #_;
Or instead:
for my $ip ( #array ) {
debug_check($ip);
}
Note - you should also avoid using an & prefix to a sub. It has a special meaning. Usually it'll work, but it's generally redundant at best, and might cause some strange glitches.
while (<$fh>)
is short for
while (defined($_ = <$fh>))
$_ is currently aliased to the element of #data, so your sub is replacing each element of #data with undef. Fix:
while (local $_ = <$fh>)
which is short for
while (defined(local $_ = <$fh>))
or
while (my $line = <$fh>) # And use $line instead of $_ afterwards
which is short for
while (defined(my $line = <$fh>))
Be careful when using global variables. You want to localize them if you modify them.

while and foreach interaction

I am having an undesired interaction with a foreach loop and a while that I don't quite understand. I have a normal for loop, and then a foreach loop going through an array. I create a string (representing a file name) with both and then open the file and read it.
The code I use is here:
#array=(1,2);
for($y=0;$y<2;$y++)
{
foreach(#array)
{
print "#array\n";
$name="/Users/jorge/$_\_vs_$y\.txt";
print "$name\n";
open(INFILE,"$name") or die "Can't open files!\n";
while(<INFILE>)
{
$line=$_;
}
}
}
and the output is:
array: 1 2
/Users/jorge/1_vs_0.txt
array: 2
/Users/jorge/2_vs_0.txt
array:
/Users/jorge/_vs_1.txt
Seems that somehow the while loop is shortening my array, if I remove the:
while(<INFILE>)
it works as intented, also if I change the foreach too:
foreach $tmp (#array)
and use $tmp instead of $_, it also works as intended, the output looks like this:
array: 1 2
/Users/jorge/1_vs_0.txt
array: 1 2
/Users/jorge/2_vs_0.txt
array: 1 2
/Users/jorge/1_vs_1.txt
array: 1 2
/Users/jorge/2_vs_1.txt
array: 1 2
/Users/jorge/1_vs_2.txt
You're using $_ as the loop control variable on two nested loops. Instead, you should give each loop its own variable.
Not only is the inner loop changing the value of the control variable for the outer loop, but it's also changing the actual array being looped over. That's because the loop control variable in Perl's for/foreach aliases the elements of the array, so when the <INFILE> construct reads a line into $_, it overwrites the current element of #array with that line. When the while loop finishes reading the file, $_ comes out of the last <INFILE> as undefined, which means the most-recently processed element of #array will always be undefined when you get back to the top of the foreach loop.
You should also be declaring your variables with my, using a lexical scalar instead of a bareword as a file handle, and using the the three-argument version of open, so I've made those changes below as well. But the solution to your shrinking-array problem is just the use of an explicit variable instead of the default $_ for at least one of the two loops.
my #array = (1, 2);
for (my $y = 0; $y < 2; $y++)
{
foreach my $x (#array) # using $x instead of $_
{
print "#array\n";
my $name = "/Users/jorge/${x}_vs_$y.txt";
print "$name\n";
open my $infile, '<', $name or die "$0: can't open file '$name': $!\n";
while (my $line = <$infile>) # using $line instead of $_
{
# do something with $line here
}
}
}
Your while loop overwrites content of array as it uses $_ which is aliased to #array elements.
It is best to use lexical (my) variable when reading file using while,
while (my $line = <INFILE>) {
# ..
}

Beginner problems with perl

I have been tasked with correcting bugs in a chunk of Perl code and had a few questions about it (as I am new to Perl).
My first issue is what does the exclamation mark do?
if (!$superceded{returned->{$field}}) {
$found = 0;
foreach (blahblahblah)
My second problem is what does it mean if you have a variable $supercede and a hash %superceded and you write
(keys %$superceded)
Finally I have read up on these next two but I am still unsure on how they work. Setting a variable equal to shift and how to use "last;".
Thanks for any help and advice.
! is logical negation, which means if $cond is true, !$cond will be false. You could learn more about Truth and Falsehood from perlsyn.
If $hashref is a hash reference, then %$hashref is the hash that that reference referred to. For example,
my %hash = ( key1 => "val1", key2 => "val2" );
my $hashref = \%hash; # create a hash reference
while (my ($key, $val) = each %$hashref) {
# do something
}
Oh, you also could write something like this
my $hash = \%hash;
A little confusion to human readers, but Perl will accept it without any problem. To Perl, $hash and %hash are two completely different variables, and could be totally unrelated.
You could learn more about reference from perlref. And #$arrayref is similar, except in this case $arrayref is an array reference, it could be created by $arrayref = \#array;.
Setting a variable equal to shift
Do you mean something like this:
my $val = shift;
In this case, this means my $val = shift #_;, which will remove the first element of #_ and assign it to $val. See perldoc -f shift for further details.
how to use "last;"
If you want to finish a loop early, you could use last. For example,
foreach my $i (1..100) {
print "$i\n";
}
will print 1 to 100, and this
foreach my $i (1..100) {
print "$i\n";
last if $i == 5;
}
will only print 1 to 5. See perlsyn for further details.

Perl splitting array based on condition using grep

I have some perl code that looks something like this:
my #array = map { rand } ( 1..100 );
my #matching = grep { $_ == $condition } #array;
#array = grep { $_ != $condition } #array;
This works ok, but what I would like to do is split the original array into two based on a single operation...I think I'm carrying out twice as many operations as strictly necessary.
Help appreciated!! Thanks.
This is where part from List::MoreUtils comes in handy.
use List::MoreUtils qw'part';
my($even,$odd) = part { $_ % 2 } #array;
This works great if you want each element of input in exactly one array of the output.
If you want to possibly put them in more than one of the arrays, you have to loop over them yourself.
The best way to do that is with a foreach loop.
my(#div2,#div3);
for my $elem (#array){
push #div2, $elem unless $elem % 2;
push #div3, $elem unless $elem % 3;
}
If there are a lot of similar checks you have to do, perhaps you should loop on what your testing against as-well.
my %div;
for my $elem (#array){
for my $div (2,3,5,7,11,13){
push #{ $out{$div} }, $elem unless $elem % $div;
}
}
By far the easiest method is to iterate your array and push values to either of the two arrays depending on the condition, as in the below example.
for (#array) {
if ($_ % 2) {push #odd, $_}
else {push #even, $_}
}
If you'd like to modify the source array:
for (my $i =0; $i < #array; ++$i) {
if ($array[$i] % 2) {
push #odd, splice (#array, $i--, 1);
}
}
Why didn't you recommend List::MoreUtils::part?
The module in question might not exists on the target system, which is always an annoying thing.
Also on the system I ran tests on I found that List::MoreUtils::part was twice as slow as first snippet in this post, though with different implementations of part it might be the opposite actually.
I love the simplicity of List::MoreUtils' part function:
sub part (&#) {
my ($code, #list) = #_;
my #parts;
push #{ $parts[ $code->($_) ] }, $_ foreach #list;
return #parts;
}
The resulting #parts array is an array of arrayrefs. #$parts[0] is the array of elements that returned false. #$parts[1] returned true.

How can I extract an array from a two-dimensional array in Perl?

I have once again forgotten how to get $_ to represent an array when it is in a loop of a two dimensional array.
foreach(#TWO_DIM_ARRAY){
my #ARRAY = $_;
}
That's the intention, but that doesn't work. What's the correct way to do this?
The line my #ARRAY = #$_; (instead of = $_;) is what you're looking for, but unless you explicitly want to make a copy of the referenced array, I would use #$_ directly.
Well, actually I wouldn't use $_ at all, especially since you're likely to want to iterate through #$_, and then you use implicit $_ in the inner loop too, and then you could have a mess figuring out which $_ is which, or if that's even legal. Which may have been why you were copying into #ARRAY in the first place.
Anyway, here's what I would do:
for my $array_ref (#TWO_DIM_ARRAY) {
# You can iterate through the array:
for my $element (#$array_ref) {
# do whatever to $element
}
# Or you can access the array directly using arrow notation:
$array_ref->[0] = 1;
}
for (#TWO_DIM_ARRAY) {
my #arr = #$_;
}
The $_ will be array references (not arrays), so you need to dereference it as:
my #ARRAY = #$_;

Resources