Emptying array inside loop while using push in PERL - arrays

I am writing a subroutine that prints an array of non redundant elements from another array.
This code is inside my subroutine.
foreach (#old_table) { push(#new_table, $_) unless ($seen{$_}++); }
print "#new_table" . "\n";
Then i call my subroutine in a loop inside my main program, for the first iteration it is OK and my new table contains one occurrence of my old table.
But after that #new_table keeps elements from past iterations and the print result is false.
I tried emptying #new_table inside my subroutine like this
#new_table = ();
foreach (#old_table) { push(#new_table, $_) unless ($seen{$_}++); }
print "#new_table" . "\n";
But then my #new_table becomes empty in all iterations except for the first one.
What is the problem with this and how can i fix it ?

Due to incorrect scoping, you're reusing the #new_table and %seen of previous passes. Create these just before the loop.
my #new_table;
my %seen;
foreach (#old_table) { push(#new_table, $_) unless ($seen{$_}++); }
print "#new_table" . "\n";
This can be simplified to
my %seen;
my #new_table = grep { !$seen{$_}++ } #old_table;
print "#new_table\n";
You can also use
use List::MoreUtils qw( uniq );
my #new_table = uniq(#old_table);
print "#new_table\n";
You are using use strict; use warnings;, right? If not, you should be. Always.

You can try uniq from List::MoreUtils to remove redundant elements.
my #new_table = uniq(#old_table);
To quote from perldoc
uniq LIST
distinct LIST
Returns a new list by stripping duplicate values in LIST. The order of elements in the returned list is the same as in LIST. In
scalar context, returns
the number of unique elements in LIST.
my #x = uniq 1, 1, 2, 2, 3, 5, 3, 4; # returns 1 2 3 5 4
my $x = uniq 1, 1, 2, 2, 3, 5, 3, 4; # returns 5

Related

How to modify array elements using subroutine in Perl

I am trying to modify an array passed to a subroutine.
I am passing an array reference to the subroutine and assigning new values but it is not getting reflected in the caller side.
Below is my program.
sub receiveArray {
my $arrayref = #_;
#{$arrayref} = ( 4, 5, 6 );
}
#ar = ( 1, 2, 3 );
print "Values of the function before calling the function\n";
foreach my $var ( #ar ) {
print $var;
print "\n";
}
receiveArray(\#ar);
print "Values of the function after calling the function\n";
foreach my $var ( #ar ) {
print $var;
print "\n";
}
What is the problem in the above code?
You should start every Perl file you write with use strict; use warnings;. That will help you avoid errors like this.
The problem is in this line:
my $arrayref = #_;
You're assigning an array to a scalar, so the array is evaluated in scalar context, which yields the number of elements in the array.
What you should do instead is:
my ($arrayref) = #_;
Now it's using list assignment, putting the first function argument into $arrayref (and ignoring the rest, if any).
List assignment is documented in perldoc perldata (the part starting with "Lists may be assigned to ...").

Using an array to index into another array

I have two arrays, let's call them #a1 and #a2. What I'm trying to do is obtain elements from #a2 using the values in #a1 as indices. My current attempt doesn't work properly.
foreach (#a1) {
print $a2[$_] . "at" . $_;
}
This only prints $_ but not $a2[$_].
I sense there is a trivial solution to this, but I just can't find it.
There is nothing wrong with the code you have. I have tested a small script and it works as expected. Asi i suggested in my comment, try using something like Data::Dumper to see whats in the arrays before the loop.
use strict;
use warnings;
use Data::Dumper;
my #a1 = (0..4);
my #a2 = ("a".."e");
print Dumper \#a1, \#a2;
foreach (#a1){
print $a2[$_]." at ".$_."\n";
}
OUTPUT
$VAR1 = [
0,
1,
2,
3,
4
];
$VAR2 = [
'a',
'b',
'c',
'd',
'e'
];
a at 0
b at 1
c at 2
d at 3
e at 4
there's no reason your code shouldn't work as long as the values of the first array are valid addresses in the second array. but if all you really want to do is just get the values and address of the second array, you could just do:
for my $i (0..$#a2) {
print "$i: $a2[$i]","\n";
}
$#a2 is the last element address of the array.

"Use of uninitialized value" when indexing an array

I get the following error from Perl when trying to run the code below
Use of uninitialized value within #words in concatenation (.) or string...
It references the line where I try to create an array made up of three-word sequences (the line that starts with $trigrams). Can anyone help me figure out the problem?
my %hash;
my #words;
my $word;
my #trigrams;
my $i = 0;
while (<>) {
#words = split;
foreach $word (#words) {
$hash{$word}++;
# Now trying to create the distinct six-grams in the 10-K.
$trigrams[$i] = join " ", $words[$i], $words[$i + 1], $words[$i + 2];
print "$words[$i]\n";
$i++;
}
}
All that is happening is that you are falling off the end of the array #words. You are executing the loop for each element of #words, so the value of $i goes from 0 to $#words, or the index of the final element of the array. So the line
join " ", $words[$i], $words[$i + 1], $words[$i + 2];
accesses the last element of the array $words[$i] and two elements beyond that which don't exist.
In this case, as with any loop which uses the current index of an array, it is easiest to iterate over the array indices instead of the contents. For the join to be valid you need to start at zero and stop at two elements before the end, so 0 .. $#words-2.
It is also neater to use an array slice to select the three elements for the trigram, and use the fact that interpolating an array into a string, as in "#array", will do the same as join ' ', #array. (More precisely, it does join $", #array, and $" is set to a single space by default.)
I suggest this fix. It is essential to use strict and use warnings at the start of every Perl program, and you should declare all your variables using my as late as possible.
use strict;
use warnings;
my %hash;
while (<>) {
my #words = split;
my #trigrams;
for my $i (0 .. $#words - 2) {
my $word = $words[$i];
++$hash{$word};
$trigrams[$i] = "#words[$i,$i+1,$i+2]";
print "$word\n";
}
}
Update
You may prefer this if it isn't too terse for you
use strict;
use warnings;
my %hash;
while (<>) {
my #words = split;
my #trigrams = map "#words[$_,$_+1,$_+2]", 0 .. $#words-2;
}

deleting elements in array in a loop Perl

I want to make a loop where I go through all the elements in one array (#array1), and if the same element is found in another array (#array2), I want the value from a third array (#array3) with the same index to be added to the first array and deleted from the third array. I tried it this way, however the line with the if-statement runs on unitialized values, and it loops forever.
foreach my $elem1 (#array1){
my $i = 0;
while ($i < scalar #array2){
if($array2[$i]==$elem1){
push (#array1, $array3[$i]);
delete $array2[$i];
}
else{
$i++;
}
}
}
The problem is you do not increment $i if the element matches. Delete the else.
Well, here's one way to deal with the problem (to the extent that I understand what you want to do). Whenever you need to answer questions about membership, you probably want to use a hash.
use strict;
use warnings;
my #array1 = ( 11, 22, 33, 44);
my #array2 = ( 11, 2, 3, 44, 5, 44);
my #array3 = (-11, -2, -3, -44, -5, -444);
# Keep track of every value in #array1.
my %vals = map { $_ => 1 } #array1;
my #keep_indexes;
# Go through #array2, either adding the corresponding
# value in #array3 to #array1 or storing the array index.
for my $i (0 .. $#array2) {
if ($vals{$array2[$i]}){
push #array1, $array3[$i];
}
else {
push #keep_indexes, $i;
}
}
# Rebuild #array3 from the indexes that we stored.
# Change this to #array2 if you want to delete from that array instead.
#array3 = map $array3[$_], #keep_indexes;
print join(' ', #array1), "\n"; # 11 22 33 44 -11 -44 -444
print join(' ', #array2), "\n"; # 11 2 3 44 5 44
print join(' ', #array3), "\n"; # -2 -3 -5
I dislike that code, so here are a few caveats:
Any time you have numbered variable names (#array1, #array2, etc.), you're headed for confusion. You need better variable names or, more likely, a better data structure.
Any time you find yourself in the business of maintaining parallel arrays, you should consider whether a better data structure would help.
You are deleting an element from array2, not array3 as stated in the question. I think the delete operation sets the array element to undef. The next time around the loop it checks that same element that is now undef against $elem. Hence the error. Then it does he same again and again.

Perl: Dereferencing Array

Why does the following code not work in getting into an anonymous array?
my #d = [3,5,7];
print $(#{$d[0]}[0]);
# but print $d[0][0] works.
Script 1 (original)
Because it is invalid Perl code?
#!/usr/bin/env perl
use strict;
use warnings;
my #d = [3,5,7];
print $(#{$d[0]}[0]);
When compiled (perl -c) with Perl 5.14.1, it yields:
Array found where operator expected at xx.pl line 6, at end of line
(Missing operator before ?)
syntax error at xx.pl line 6, near "])"
xx.pl had compilation errors.
Frankly, I'm not sure why you expected it to work. I can't make head or tail of what you were trying to do.
The alternative:
print $d[0][0];
works fine because d is an array containing a single array ref. Thus $d[0] is the array (3, 5, 7) (note parentheses instead of square brackets), so $d[0][0] is the zeroth element of the array, which is the 3.
Script 2
This modification of your code prints 3 and 6:
#!/usr/bin/env perl
use strict;
use warnings;
my #d = ( [3,5,7], [4,6,8] );
print $d[0][0], "\n";
print $d[1][1], "\n";
Question
So the $ in $d[0] indicates that [3,5,7] is dereferenced to the array (3,5,7), or what does the $ do here? I thought the $ was to indicate that a scalar was getting printed out?
Roughly speaking, a reference is a scalar, but a special sort of scalar.
If you do print "$d[0]\n"; you get output something like ARRAY(0x100802eb8), indicating it is a reference to an array. The second subscript could also be written as $d[0]->[0] to indicate that there's another level of dereferencing going on. You could also write print #{$d[0]}, "\n"; to print out all the elements in the array.
Script 3
#!/usr/bin/env perl
use strict;
use warnings;
$, = ", ";
my #d = ( [3,5,7], [4,6,8] );
#print $(#{$d[0]}[0]);
print #d, "\n";
print $d[0], "\n";
print #{$d[0]}, "\n";
print #{$d[1]}, "\n";
print $d[0][0], "\n";
print $d[1][1], "\n";
print $d[0]->[0], "\n";
print $d[1]->[1], "\n";
Output
ARRAY(0x100802eb8), ARRAY(0x100826d18),
ARRAY(0x100802eb8),
3, 5, 7,
4, 6, 8,
3,
6,
3,
6,
I think you are trying for this:
${$d[0]}[0]
Though of course there's always the syntactic sugar way, too:
$d[0]->[0]
The square bracket constructor creates an anonymous array, but you are storing it in another array. This means that you are storing the three element array inside the first element of a one element array. This is why $d[0][0] will return the value 3. To do a single level array use the list constructor:
my #d = (3,5,7);
print $d[0];
If you really mean to create the array inside the outer array then you should dereference the single (scalar) value as
print ${$d[0]}[0].
For more read perldoc perlreftut.

Resources