How to modify array elements using subroutine in Perl - arrays

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 ...").

Related

Extract number from array in Perl

I have a array which have certain elements. Each element have two char "BC" followed by a number
e.g - "BC6"
I want to extract the number which is present and store in a different array.
use strict;
use warnings;
use Scalar::Util qw(looks_like_number);
my #band = ("BC1", "BC3");
foreach my $elem(#band)
{
my #chars = split("", $elem);
foreach my $ele (#chars) {
looks_like_number($ele) ? 'push #band_array, $ele' : '';
}
}
After execution #band_array should contain (1,3)
Can someone please tell what I'm doing wrong? I am new to perl and still learning
To do this with a regular expression, you need a very simple pattern. /BC(\d)/ should be enough. The BC is literal. The () are a capture group. They save the match inside into a variable. The first group relates to $1 in Perl. The \d is a character group for digits. That's 0-9 (and others, but that's not relevant here).
In your program, it would look like this.
use strict;
use warnings;
use Data::Dumper;
my #band = ('BC1', 'BC2');
my #numbers;
foreach my $elem (#band) {
if ($elem =~ m/BC(\d)/) {
push #numbers, $1;
}
}
print Dumper #numbers;
This program prints:
$VAR1 = '1';
$VAR2 = '2';
Note that your code had several syntax errors. The main one is that you were using #band = [ ... ], which gives you an array that contains one array reference. But your program assumed there were strings in that array.
Just incase your naming contains characters other than BC this will exctract all numeric values from your list.
use strict;
use warnings;
my #band = ("AB1", "BC2", "CD3");
foreach my $str(#band) {
$str =~ s/[^0-9]//g;
print $str;
}
First, your array is an anonymous array reference; use () for a regular array.
Then, i would use grep to filter out the values into a new array
use strict;
use warnings;
my #band = ("BC1", "BC3");
my #band_array = grep {s/BC(\d+)/$1/} #band;
$"=" , "; # make printing of array nicer
print "#band_array\n"; # print array
grep works by passing each element of an array in the code in { } , just like a sub routine. $_ for each value in the array is passed. If the code returns true then the value of $_ after the passing placed in the new array.
In this case the s/// regex returns true if a substitution is made e.g., the regex must match. Here is link for more info on grep

Why does my Perl sub receive the parameters of its parent?

Here is my test code :
#!/bin/perl
use strict;
use Array::Utils qw[array_minus];
sub sub1 {
my #array1 = qw(1 2 3);
my #array2 = qw(1 3 5);
my #arrayMinus = array_minus(#array1, #array2);
my #sortedArrayMinus = sort #arrayMinus;
print "Result from array_minus + sort : " . join(",", #sortedArrayMinus) . "\n";
my #sortedArrayMinus2 = sort array_minus(#array1, #array2);
print "Result from sort array_minus : " . join(",", #sortedArrayMinus2) . "\n";
}
sub1("a","b");
When I run it, it gives the following result :
Result from array_minus + sort : 2
Can't use string ("b") as an ARRAY ref while "strict refs" in use at Array/Utils.pm line 123.
So, the second call to array_minus fails because of wrong parameters.
I'm using version 0.5 of the Array::Utils library (I've manually copied it from http://cpansearch.perl.org/src/ZMIJ/Array-Utils-0.5/Utils.pm)
The relevant lines of this file are :
sub array_minus(\#\#) {
my %e = map{ $_ => undef } #{$_[1]};
return grep( ! exists( $e{$_} ), #{$_[0]} );
}
If I debug the value of #_ in array_minus, its value is OK for the first call, but it's [ 'a', 'b' ] for the second call.
So it behaves as if array_minus sub receives the parameters of sub1, instead of the ones I passed, but only when I also ask to sort the result on the same line. What's wrong in my code?
I'm using Perl 5.22.1.
This expression:
sort array_minus(#array1, #array2)
really means "sort the list resulting from concatenating #array1 and #array2, using array_minus as the comparison function".
As explained in perldoc -f sort:
Warning: syntactical care is required when sorting the list returned from a function. If you want to sort the list returned by the function call find_records(#key), you can use:
my #contact = sort { $a cmp $b } find_records #key;
my #contact = sort +find_records(#key);
my #contact = sort &find_records(#key);
my #contact = sort(find_records(#key));
... because otherwise you're hitting the sort SUBNAME LIST syntax (which exists for historical reasons: perl had sort long before it supported subroutine references).
Read the warning in sort and fix the syntax:
my #sortedArrayMinus2 = sort(array_minus(#array1, #array2));
The original syntax was telling sort to use array_minus as the comparison function.

Perl "scalar" function returning "1" on an empty array

The following Perl code will print a "1", however the function test2 really has no legitimate input value. Why does Perl act this way?
test();
sub test {
my ($var) = #_;
test2($var);
}
sub test2 {
my (#array) = #_;
print scalar #array;
}
test2($var) passes one scalar to test2 ($var), so one scalar is assigned to #array by my (#array) = #_;.
The value of the scalar in question ($var) is undef, since you assigned "nothing" to $var in my ($var) = #_;.
Maybe you want test2(#_) (passes the zero scalars in #_) instead of test2($var) (passes the one scalar $var)?
Inside test, $var is set to undef by the assignment. So your call to test2 passes a list with one element, undef.

Emptying array inside loop while using push in PERL

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

How to retrieve an array from a hash that has been passed to a subroutine in perl

I am trying to write a subroutine that takes in a hash of arrays as an argument. However, when I try to retrieve one of the arrays, I seem to get the size of the array instead of the array itself.
my(%hash) = ( );
$hash{"aaa"} = ["blue", 1];
_subfoo("test", %hash);
sub _subfoo {
my($test ,%aa) = #_;
foreach my $name (keys %aa) {
my #array = #{$aa{$name}};
print $name. " is ". #array ."\n";
}
}
This returns 2 instead of (blue, 1) as I expected. Is there some other way to handle arrays in hashes when in a subroutine?
Apologies if this is too simple for stack overflow, first time poster, and new to programming.
You're putting your #array array into a scalar context right here:
print $name. " is ". #array ."\n";
An array in scalar context gives you the number of elements in the array and #array happens to have 2 elements. Try one of these instead:
print $name . " is " . join(', ', #array) . "\n";
print $name, " is ", #array, "\n";
print "$name is #array\n";
and you'll see the elements of your #array. Using join lets you paste the elements together as you please; the second one evaluates #array in list context and will mash the values together without separating them; the third interpolates #array by joining its elements together with $" (which is a single space by default).
As mu is too short has mentioned, you used the array in scalar context, and therefore it returned its length instead of its elements. I had some other pointers about your code.
Passing arguments by reference is sometimes a good idea when some of those arguments are arrays or hashes. The reason for this is that arrays and hashes are expanded into lists before being passed to the subroutine, which makes something like this impossible:
foo(#bar, #baz);
sub foo { # This will not work
my (#array1, #array2) = #_; # All the arguments will end up in #array1
...
}
This will work, however:
foo(\#bar, \#baz);
sub foo {
my ($aref1, $aref2) = #_;
...
}
You may find that in your case, each is a nice function for your purposes, as it will make dereferencing the array a bit neater.
foo("test", \%hash); # note the backslash to pass by reference
sub foo {
my ($test, $aa) = #_; # note use of scalar $aa to store the reference
while (my ($key, $value) = each %$aa)) { # note dereferencing of $aa
print "$key is #$value\n"; # ...and $value
}
}

Resources