Question 1:
I want to pass an array to a function. But the passed argument is changed in the function. Is it called by value?
Question 2:
#my ($name, $num, #array)= #_; <=1 )
my $name = shift; <=2 )
my $num = shift;
my #array = shift;
Case 1 and 2 has different output. Why did it occur?
#!/usr/bin/perl
use strict;
my #test1;
push #test1, ['a', 1];
push #test1, ['b', 1];
push #test1, ['c', 1];
push #test1, ['d', 1];
push #test1, ['e', 1];
for (my $i=0; $i< scalar(#test1); $i++) {
print "out1: $test1[$i][0] $test1[$i][1]\n";
}
test_func("test_func", 10, #test1);
sub test_func {
#my ($name, $num, #array)= #_; <=1)
my $name = shift; <=2)
my $num = shift;
my #array = shift;
print "$name\n";
print "$num\n";
for (my $i=0; $i< scalar(#test1); $i++) {
print "$array[$i][0] $array[$i][1]\n";
}
for (my $i=0; $i< scalar(#test1); $i++) {
if ($array[$i][0] eq 'a') {
$array[$i][0] = 'z';
}
}
for (my $i=0; $i< scalar(#test1); $i++) {
print "change: $array[$i][0] $array[$i][1]\n";
}
}
for (my $i=0; $i< scalar(#test1); $i++) {
print "out2: $test1[$i][0] $test1[$i][1]\n";
}
#
Below is the test output.
out1: a 1
out1: b 1
out1: c 1
out1: d 1
out1: e 1
test_func
10
a 1
b 1
c 1
d 1
e 1
change: z 1
change: b 1
change: c 1
change: d 1
change: e 1
out2: z 1 <= Why did it change?
out2: b 1
out2: c 1
out2: d 1
out2: e 1
I want to pass an array to a function [...] has different output. Why did it occur?
You cannot pass an array to a function sub. Subs can only take a list of scalars as arguments.
test_func("test_func", 10, #test1);
is the same as
test_func("test_func", 10, $test1[0], $test1[1], $test1[2], $test1[3], $test1[4]);
You are creating a new array in test_func when you do
my ($name, $num, #array) = #_;
shift returns the first element of #_, which is necessarily a scalar. #_ is an array, and elements of arrays are scalars. The equivalent would be
my $name = shift(#_);
my $num = shift(#_);
my #array = splice(#_);
To pass an array to a sub, one would normally pass a reference to it.
test_func("test_func", 10, \#test1);
my ($name, $num, $array) = #_;
my $name = shift;
my $num = shift;
my $array = shift;
say "#$array";
But the passed argument is changed in the function. Is it called by value?
Perl never passes by value. It always passes by reference. If you change any element of #_, it will change the corresponding argument in the caller.
$ perl -E'sub f { $_[0] = "def"; } my $x = "abc"; f($x); say $x;'
def
But that's not the issue. You don't change any elements of #_. What you are doing is changing the single array referenced by both $test[0] and $array[0].
This is what you are doing:
my $ref1 = [ 'a', 1 ]; # aka $test1[0]
my $ref2 = $ref1; # aka $array[0]
$ref2->[0] = 'z'; # Changes the single array (not $ref1 or $ref2).
It's short for
my #anon = ( 'a', 1 );
my $ref1 = \#anon; # aka $test1[0]
my $ref2 = $ref1; # aka $array[0]
$ref2->[0] = 'z'; # Changes #anon (not $ref1 or $ref2).
Storable's dclone can be used to make a "deep copy" of an array.
my $ref1 = [ 'a', 1 ];
my $ref2 = dclone($ref1); # clones the reference, the array, 'a' and 1.
$ref1->[0] = 'y'; # Changes the original array
$ref2->[0] = 'z'; # Changes the new array
Related
Lets say the array is (1,2,3,4,5,6,7,8,9),
Another subarray is (2,3,4)
Is there a function to check if the subarray pattern(full exact order) exists within array?
In this case, it would return any indicator(index) that shows it exists.
Also would need to work for duplicates if there are multiple subarrays existing in the array like (4,2,3,4,2,3,4).
If it happens to match multiple times for example:
Array = (2,3,2,3,2,2,3,2)
Sub Array = (2,3,2)
Would just return starting index of matches in order: 0,2,5
Or if it removes, would result in (3,2)
Edit: Elements don't have to be num
There's no built-in method, but it's easy to write:
#!/usr/bin/env perl
use warnings;
use strict;
use feature qw/say/;
# Takes two arrayrefs of numbers.
#
# Returns the first index in the first one where the second list appears, or
# -1 if not found.
sub find_sublist(++) {
my ($haystack, $needle) = #_;
my $nlen = #$needle;
my $hlen = #$haystack;
return -1 if $hlen == 0 || $nlen == 0;
HAYSTACK_POS:
for (my $n = 0; $n <= $hlen - $nlen; $n++) {
for (my $m = 0; $m < $nlen; $m++) {
if ($haystack->[$n + $m] != $needle->[$m]) {
next HAYSTACK_POS;
}
}
return $n;
}
return -1;
}
# Takes two arrayrefs of numbers.
#
# Returns a list of the starting indexes of the first list
# of every run of the second list. Returns an empty list if
# there are no matches.
sub find_sublists(++) {
my ($haystack, $needle) = #_;
my $nlen = #$needle;
my $hlen = #$haystack;
my #positions;
return #positions if $hlen == 0 || $nlen == 0;
HAYSTACK_POS:
for (my $n = 0; $n <= $hlen - $nlen; $n++) {
for (my $m = 0; $m < $nlen; $m++) {
if ($haystack->[$n + $m] != $needle->[$m]) {
next HAYSTACK_POS;
}
}
push #positions, $n;
}
return #positions;
}
# Takes two arrayrefs of numbers.
#
# Returns a new list that is the first one with every non-overlapping run of
# the second second list removed.
sub remove_sublists(++) {
my #haystack = #{$_[0]};
my $needle = $_[1];
while ((my $pos = find_sublist #haystack, $needle) != -1) {
splice #haystack, $pos, #$needle;
}
return #haystack;
}
my #list1 = (1,2,3,4,5,6,7,8,9);
my #list2 = (4,2,3,4,2,3,4);
my #list3 = (2,3,2,3,2,2,3,2);
say find_sublist(#list1, [2, 3, 4]); # Returns 1
say find_sublist([2,9,3,4], [2,3,4]); # Returns -1
my #positions = find_sublists(#list2, [2,3,4]); # 1,4
say join(",", #positions);
#positions = find_sublists(#list3, [2,3,2]); # 0,2,5
say join(",", #positions);
say join(",", remove_sublists(#list1, [2,3,4])); # 1,5,6,7,8,9
say join(",", remove_sublists(#list3, [2,3,2])); # 3,2
If the inputs are numbers representable by your perl's integers (as shown), you can use
# Indexes
my $pattern = pack "W*", #pattern;
my $array = pack "W*", #array;
my #indexes;
push #indexes, $-[0] while $array =~ /\Q$pattern/g;
# Removal
my $pattern = pack "W*", #pattern;
my $array = pack "W*", #array;
$array =~ s/\Q$pattern//g;
#array = unpack "W*", $array;
How it handles overlaps:
/---\ /---\ Removed
2,3,2 from 2,3,2,3,2,2,3,2
\---/ Not removed
Note that this also works if you can map the inputs to numbers.
my ( %map_f, #map_r );
for ( #array, #pattern ) {
if ( !exists{ $map{ $_ } } ) {
$map_f{ $_ } = #map_r;
push #map_r, $_;
}
}
my $pattern = pack "W*", #map_f{ #pattern };
my $array = pack "W*", #map_f{ #array };
$array =~ s/\Q$pattern//g;
#array = #map_r[ unpack "W*", $array ];
It's not the best algorithm, but it should be very fast by moving the work from Perl to the regex engine.
I have the following code which reads in a 6x6 array from STDIN and saves it as an array of anonymous arrays. I am trying to print out each element with $arr[i][j], but the code below isn't working. It just prints out the first element over and over. How am I not accessing the element correctly?
#!/user/bin/perl
my $arr_i = 0;
my #arr = ();
while ($arr_i < 6){
my $arr_temp = <STDIN>;
my #arr_t = split / /, $arr_temp;
chomp #arr_t;
push #arr,\#arr_t;
$arr_i++;
}
foreach my $i (0..5){
foreach my $j (0..5){
print $arr[i][j] . "\n";
}
}
i and j are not the same as the variables you declared in the foreach lines. Change:
print $arr[i][j] . "\n";
to:
print $arr[$i][$j] . "\n";
warnings alerted me to this issue. You should add these lines to all your Perl code:
use warnings;
use strict;
To demonstrate the Perlish mantra that there's "more than one way to do it":
use 5.10.0; # so can use "say"
use strict;
use warnings qw(all);
sub get_data {
my ($cols, $rows) = #_;
my ($line, #rows);
my $i;
for ($i = 1; $i <= $rows and $line = <DATA>; $i++) {
chomp $line;
my $cells = [ split ' ', $line ];
die "Row $i had ", scalar(#$cells), " instead of $cols" if #$cells != $cols;
push #rows, $cells;
}
die "Not enough rows, got ", $i - 1, "\n" if $i != $rows + 1;
\#rows;
}
sub print_data {
my ($cols, $rows, $data) = #_;
for (my $i = 0; $i < $rows; $i++) {
for (my $j = 0; $j < $cols; $j++) {
say $data->[$i][$j];
}
}
}
my $data = get_data(6, 6);
print_data(6, 6, $data);
__DATA__
1 2 3 4 5 6
a b c d e f
6 5 4 3 2 1
f e d c b a
A B C D E F
7 8 9 10 11 12
Explanation:
if we use say, that avoids unsightly print ..., "\n"
get_data is a function that can be called and/or reused, instead of just being part of the main script
get_data knows what data-shape it expects and throws an error if it doesn't get it
[ ... ] creates an anonymous array and returns a reference to it
get_data returns an array-reference so data isn't copied
print_data is a function too
both functions use a conventional for loop instead of making lists of numbers, which in Perl 5 needs to allocate memory
There is also a two-line version of the program (with surrounding bits, and test data):
use 5.10.0; # so can use "say"
my #lines = map { [ split ' ', <DATA> ] } (1..6);
map { say join ' ', map qq{"$_"}, #$_ } #lines;
__DATA__
1 2 3 4 5 6
a b c d e f
6 5 4 3 2 1
f e d c b a
A B C D E F
7 8 9 10 11 12
Explanation:
using map is the premier way to iterate over lists of things where you don't need to know how many you've seen (otherwise, a for loop is needed)
the adding of " around the cell contents is only to prove they've been processed. Otherwise the second line could just be: map { say join ' ', #$_ } #lines;
I have 2D arrays (#AoA) that contains references to other arrays of strings. Size of this #AoA is different each time. I would like to compare each of these arrays of strings to each other.
To compare first array of strings to each other I can use something like this:
for (my $i=0; $i < $#AoA; $i++) {
my $lcm = List::Compare->new( $aAoA[$i], $AoA[$i+1] );
my #intersection = $lcm->get_intersection;
if (#intersection) {
#some code here
}
But what the best way to compare Each array with Each other?
I would like the results like this:
Arr1 Arr2 …. ArrN
Arr1 x 1 match 3 matches 0 matches
Arr2 x N matches 3 matches
…. x 1 match
ArrN x
If you feel comfortable with List::Compare, then you could use it instead of my function intersect_count.
#!/usr/bin/perl
use strict;
use warnings;
use List::MoreUtils 'uniq';
use List::Util 'any';
my #AoA = ([1,2,3,4,], [3,4,5,6], [4,7,8,9], [11,22,33]);
my #hdrs = map "Array_$_", 1 .. #AoA;
my $fmt = "%-10s" . "%-10s" x #hdrs . "\n";
printf $fmt, ' ', #hdrs;
for (my $i=0; $i < $#AoA; $i++) {
my #matches;
for (my $j = $i+1; $j < #AoA; $j++) {
$matches[$j] = intersect_count( $AoA[$i], $AoA[$j]);
}
printf $fmt, $hdrs[$i], map $_ // ' ', #matches;
}
sub intersect_count {
my ($a1, $a2) = #_;
my $cnt;
for my $item (uniq #$a1) {
$cnt += any {$item eq $_} uniq #$a2;
}
return $cnt;
}
This prints
. Array_1 Array_2 Array_3 Array_4
Array_1 2 1 0
Array_2 1 0
Array_3 0
I am a beginner in Perl. I have two string arrays array1 and array2. I want to check the each and every element in 2nd array. if there is i want to give a relative value one to that particular element in the 2nd array. the relative values are store in an array.I try it out but it wont work and git gives a warning like" Use of uninitialized value in string eq at pjt.pl line 52, line 3".
while($i <= (scalar #resultarray-1))
{
while ($j <= (scalar #inputsymbl-1))
{
if ($resultarray[$i] eq $inputsymbl[$j])
{
$rel[$j]=1;
$i=$i+1;
$j=0;
}
else
{
$j=$j+1;
}
}
if($j==(scalar #inputsymbl))
{
$i=$i+1;
$j=0;
}
}
try this:
my $i = 0;
my $j = 0;
## walk each array element
foreach(#resultarray) {
my $result = $_;
foreach(#inputsymbl) {
my $symbl = $_;
if ($result eq $symbl) {
$rel[$j] = 1;
$i++;
} else {
$j++;
}
}
if ($j == (scalar #inputsymbl - 1)) {
$i++;
$j = 0;
}
}
provide more informations if you need detailed help.
From your question and code, it appears that you want to flag the indexes, by using a third array, of the two array's elements that are equal. By doing this, however, you're creating a sparse array. Also, if the two arrays don't have the same number of elements, a "Use of uninitialized value in string eq..." warning will eventually occur. Given these issues, consider using the smaller index of the two arrays (done using the ternary operator below) and pushing the indexes of the equal elements onto the third array:
use strict;
use warnings;
use Data::Dumper;
my #results;
my #arr1 = qw/A B C D E F G H I J/;
my #arr2 = qw/A D C H E K L H N J P Q R S T/;
# Equal: ^ ^ ^ ^ ^
# Index: 0 2 4 7 9
for my $i ( 0 .. ( $#arr1 <= $#arr2 ? $#arr1 : $#arr2 ) ) {
push #results, $i if $arr1[$i] eq $arr2[$i];
}
print Dumper \#results;
Output:
$VAR1 = [
0,
2,
4,
7,
9
];
Hope this helps!
I have an element in an array that I'd like to move accordingly.
#array = ("a","b","d","e","f","c");
Basically I'd like to find the index of "c" and then place it before "d" again based on "d"'s index. I'm using these characters as an example. It has nothing to do with sorting alphabetically.
Try doing this using array slice and List::MoreUtils to find array elements indexes :
use strict; use warnings;
use feature qw/say/;
# help to find an array index by value
use List::MoreUtils qw(firstidx);
my #array = qw/a b d e f c/;
# finding "c" index
my $c_index = firstidx { $_ eq "c" } #array;
# finding "d" index
my $d_index = firstidx { $_ eq "d" } #array;
# thanks ysth for this
--$d_index if $c_index < $d_index;
# thanks to Perleone for splice()
splice( #array, $d_index, 0, splice( #array, $c_index, 1 ) );
say join ", ", #array;
See splice()
OUTPUT
a, b, c, d, e, f
my #array = qw/a b d e f c/;
my $c_index = 5;
my $d_index = 2;
# change d_index to what it will be after c is removed
--$d_index if $c_index < $d_index;
splice(#array, $d_index, 0, splice(#array, $c_index, 1));
Well, here's my shot at it :-)
#!/usr/bin/perl
use strict;
use warnings;
use List::Util qw/ first /;
my #array = ("a","b","d","e","f","c");
my $find_c = 'c';
my $find_d = 'd';
my $idx_c = first {$array[$_] eq $find_c} 0 .. $#array;
splice #array, $idx_c, 1;
my $idx_d = first {$array[$_] eq $find_d} 0 .. $#array;
splice #array, $idx_d, 0, $find_c;
print "#array";
This prints
C:\Old_Data\perlp>perl t33.pl
a b c d e f
Another solution using array slices. This assumes you know the desired of the elements in the array.
use strict;
use warnings;
my #array = qw(a b d e f c);
print #array;
my #new_order = (0, 1, 5, 2, 3, 4);
my #new_list = #array[#new_order];
print "\n";
print #new_list;
See this link to PerlMonks for details.
You can use splice to insert an element at a specific index in an array. And a simple for loop to find the indexes you seek:
my #a = qw(a b d e f c);
my $index;
for my $i (keys #a) {
if ($a[$i] eq 'c') {
$index = $i;
last;
}
}
if (defined $index) {
for my $i (keys #a) {
if ($a[$i] eq 'd') {
splice #a, $i, 1, $a[$index];
}
}
}
use Data::Dumper;
print Dumper \#a;
Output:
$VAR1 = [
'a',
'b',
'c',
'e',
'f',
'c'
];
Note that this code does not remove the c element. To do that you need to keep track of whether you insert the c before or after d, since you are changing the indexes of the array.
U can try this
my $search = "element";
my %index;
#index{#array} = (0..$#array);
my $index = $index{$search};
print $index, "\n";