Perl: Iterating through this funky array? - arrays

I'm trying to iterate over a 2D array that is structured in this specific way. Whether or not this is a good way to structure the array is another question - I still need to be able to iterate over it (if it is possible).
#row1 = ( "Current Scan", "Last Month");
#row2 = ( "240", "0");
#row3 = ( "226", "209");
#row4 = ( "215", "207");
#array = (\#row1, \#row2, \#row3, \#row4);
print Dumper(#array);
printarray(#array);
Dumper gives me the following output:
$VAR1 = [
'Current Scan',
'Last Month'
];
$VAR2 = [
'240',
'0'
];
$VAR3 = [
'226',
'209'
];
$VAR4 = [
'215',
'207'
];
I've tried several for loops with no success. Each only prints the first row ($VAR1) and quits. Here is my most recent attempt:
sub printarray {
#array = shift;
$rowi = 0;
foreach my $row (#array) {
for (my $coli = 0; $coli <= #$row; $coli++) {
print "$array[$rowi][$coli]\n";
}
$rowi++;
}
}
I'm obviously overlooking something simple. What am I doing wrong? Thanks in advance!

If you want just print the array, try following code:
foreach my $row (#array) {
foreach my $elem (#$row) {
print $elem; ## print elements without separator
}
print "\n"; ## new line after row
}
If you need indexes for some purpose, here we go:
for(my $row_i = 0; $row_i < #array; $row_i++) {
for(my $column_i = 0; $column_i < #{ $array[$row_i] }; $column_i++) {
print $array[$row_i][$column_i];
}
}
The idea is that #array in scalar context returns number of elements in array. And #{ $array[$row_i] } is a little more tricky. It dereference array stored in $array[$row_i].
Update for subroutine:
In perl you can pass array by reference:
printarray(\#array); ## pass reference
sub printarray {
my $array_ref = shift; ## no copy created
foreach my $row (#$array_ref) { ## now we need to dereference
...
}
}
You can also pass a copy of array:
printarray(#array);
sub printarray {
my #array_copy = #_; ## store local copy of array
...
}
For more details take a look at How can I pass/return a {Function, FileHandle, Array, Hash, Method, Regex}? manual page.
And please add use strict; at the begining of programm. It'll force you to declare all variables, but will save bunch of time if you type something incorrectly.

When you pass the array into the subroutine, you're essentially passing in eight scalars. Then, when you do
sub printarray {
#array = shift;
... you're popping off only the first element in the list. Try:
sub printarray {
#array = #_;

#!/usr/bin/perl
use warnings;
use strict;
my #row1 = ( "Current Scan", "Last Month");
my #row2 = ( "240", "0");
my #row3 = ( "226", "209");
my #row4 = ( "215", "207");
my #array = (\#row1, \#row2, \#row3, \#row4);
foreach my $row (#array) {
foreach my $value (#$row) {
print "$value\n";
}
}
This will print
Current Scan
Last Month
240
0
226
209
215
207
Not sure if that's what you wanted.

Yes, the problem is in the way you're passing the array to the subroutine. Perl flattens arrays in parameter lists. Basically, printarray(#array) is (in this example) equivalent to printarray($array[0], $array[1], $array[2], $array[3]). The shift at the beginning of printarray takes the first parameter and assigns it to #array. So no matter how big the array is, printarray only sees the first element.

Are you looking for something like this:
#!/usr/bin/perl
use warnings;
use strict;
use Algorithm::Loops 'MapCar';
my #row1 = ( "Current Scan", "Last Month");
my #row2 = ( "240", "0");
my #row3 = ( "226", "209");
my #row4 = ( "215", "207");
my #array = (\#row1, \#row2, \#row3, \#row4);
MapCar { print "Scan: $_[0]: $_[1], $_[2], $_[3]\n" } #array;

Related

Perl: Go to further processing only if there are no elements to be processed by foreach loop of array (last iteration of foreach loop is on)

How can I check if no elements exist further in the array to be processed by the foreach loop?
Example:
my #array = ("abc","def","ghi");
foreach my $i (#array) {
print "I am inside array\n";
#####'Now, I want it further to go if there are no elements after
#####(or it can be said if it is the last element of array. Otherwise, go to next iteration'
print "i did this because there is no elements afterwards in array\n";
}
I could think of ways to do this, but wondering if I can get it in a short way, either using a specific keyword or function. One way I thought:
my $index = 0;
while ($index < scalar #array) {
##Do my functionality here
}
if ($index == scalar #array) {
print "Proceed\n";
}
There are multiple ways to achieve desired result, some based on usage of $index of array, and other based on use $#array-1 which can be utilized to obtain the array slice, the last element of an array accessible with $array[-1].
use strict;
use warnings;
use feature 'say';
my #array = ("abc","def","ghi");
say "
Variation #1
-------------------";
my $index = 0;
for (#array) {
say $index < $#array
? "\$array[$index] = $array[$index]"
: "Last one: \$array[$index] = $array[$index]";
$index++;
}
say "
Variation #2
-------------------";
$index = 0;
for (#array) {
unless ( $index == $#array ) {
say "\$array[$index] = $_";
} else {
say "Last one: \$array[$index] = $_";
}
$index++;
}
say "
Variation #3
-------------------";
$index = 0;
for( 0..$#array-1 ) {
say "\$array[$index] = $_";
$index++;
}
say "Last one: \$array[$index] = $array[$index]";
say "
Variation #4
-------------------";
for( 0..$#array-1 ) {
say $array[$_];
}
say 'Last one: ' . $array[-1];
say "
Variation #5
-------------------";
my $e;
while( ($e,#array) = #array ) {
say #array ? "element: $e" : "Last element: $e";
}
One way to detect when processing is at the last element
my #ary = qw(abc def ghi);
foreach my $i (0..$#ary) {
my $elem = $ary[$i];
# work with $elem ...
say "Last element, $elem" if $i == $#ary;
}
The syntax $#array-name is for the index of the last element in the array.
Note also that each works on arrays, useful if there are uses of indices
while (my ($i, $elem) = each #ary) {
# ...
say "Last element, $elem" if $i == $#ary;
}
Then make sure to read docs to be aware of subtleties of each.
Depending on how you want to handle empty arrays:
for my $ele ( #array ) {
say $ele;
}
say "Proceed";
or
for my $ele ( #array ) {
say $ele;
}
if ( #array ) {
say "Proceeding beyond $array[-1]";
}

For Loop Issues in creating nested array

Creating a matrix of products for three element arrays. I understand Perl does not have multi-dimensional arrays and are flattened. I have been using refs but I can't seem to get past the for loop issue in getting three products into a single array and pushing that array into a different single array. And I could be way off too. Be nice, but I've spent too many hours on this.
I have moved values inside and out of various places i.e. { }, printed out variables until I'm blue and used $last all over for debugging. I'm likely fried at this point.
use strict;
use warnings;
my #array1 = (1, 2, 3);
my #array2 = (2, 4, 6);
my #matrixArray = ();
my $matrixArray;
my #row;
my #finalArray = maths(\#array1, \#array2);
print #finalArray;
sub maths{
my $array1ref = shift;
my $array2ref = shift;
my $value1;
my $value2;
my $maths;
my #row = ();
my #array1 = #{$array1ref};
my #array2 = #{$array2ref};
my $len1 = #array1;
my $len2 = #array2;
for my $x (0 ..($len1 -1)){
#iterate through first array at each value
$value1 = $array1[$x];
#print $value1, " value1 \n";
for my $y (0 .. ($len2 -1)){
#iterate through second array at each value
$value2 = $array2[$y];
#print $value2, " value2 \n";
#calculate new values
$maths = $value1 * $value2;
#exactly right here
#print $maths, " maths \n" ;
push #row, $maths;
}
}
#and exactly right here but not set of arrays
#print #row, "\n";
return #row;
}
Currently I'm able to get this: 246481261218. Which is the correct dumb math but...
it should appear as a matrix:
2 4 6
4 8 12
6 12 18
I am not passing three arrays so it seems my issue is up in the sub routine before I can get on with anything else. This seems to be a theme that I often miss. So sorry if I sound inept.
EDIT***
This was working but I couldn't unpack it
use strict;
use warnings;
my #array1 = (1, 2, 3);
my #array2 = (2, 4, 6);
my #matrixArray = ();
maths(\#array1, \#array2);
foreach my $x (#matrixArray){
print "$x \n";
}
sub maths{
my $array1ref = shift;
my $array2ref = shift;
my $value1;
my $value2;
my $maths;
my #row = ();
my $row;
my #array1 = #{$array1ref};
my #array2 = #{$array2ref};
my $len1 = #array1;
my $len2 = #array2;
for my $x (0 ..($len1 -1)){
#iterate through first array at each value
$value1 = $array1[$x];
for my $y (0 .. ($len2 -1)){
#iterate through second array at each value
$value2 = $array2[$y];
#calculate new values
$maths = $value1 * $value2;
push #row, $maths;
$row = \#row;
}
push #matrixArray, $row;
}
return #matrixArray;
}
The output right after the function call is this:
ARRAY(0x55bbe2c667b0)
ARRAY(0x55bbe2c667b0)
ARRAY(0x55bbe2c667b0)
which would be the (line 10) print of $x.
****EDIT
This Works (almost):
print join(" ", #{$_}), "\n" for #matrixArray;
Output is a bit wrong...
2 4 6 4 8 12 6 12 18
2 4 6 4 8 12 6 12 18
2 4 6 4 8 12 6 12 18
And of note: I knew $x was an array but I seemed to run into trouble trying to unpack it correctly. And I'm no longer a fan of Perl. I'm pining for the fjords of Python.
And *****EDIT
This is working great and I get three arrays out of it:
sub maths{
my ($array1, $array2) = #_;
my #res;
for my $x (#$array1) {
my #row;
for my $y (#$array2) {
push #row, $x * $y;
}
push #res, \#row;
}
#This is the correct structure on print #res!
return #res;
}
But, though it's putting it together correctly, I have no output after the call
maths(\#array1, \#array2);
NOTHING HERE...
print #res;
print join(" ", #{$_}), "\n" for #res;
foreach my $x (#res){
print join(" ", #{$x}), "\n";
}
And of course a million thanks! I regret taking this stupid course and fear my grade will eventually do me in. Still pining for Python!
It appears that you need a matrix with rows obtained by multiplying an array by elements of another.
One way
use warnings;
use strict;
use Data::Dump qw(dd);
my #ary = (2, 4, 6);
my #factors = (1, 2, 3);
my #matrix = map {
my $factor = $_;
[ map { $_ * $factor } #ary ]
} #factors;
dd #matrix;
The array #matrix, formed by the outer map, has array references for each element and is thus (at least) a two-dimensional structure (a "matrix"). Those arrayrefs are built with [ ], which creates an anonymous array out of a list inside. That list is generated by map over the #ary.
I use Data::Dump to nicely print complex data. In the core there is Data::Dumper.
With a lot of work like this, and with large data, efficiency may matter. The common wisdom would have it that direct iteration should be a bit faster than map, but here is a benchmark. This also serves to show more basic ways as well.
use warnings;
use strict;
use feature 'say';
use Benchmark qw(cmpthese);
my $runfor = shift // 5; # run each case for these many seconds
sub outer_map {
my ($ary, $fact) = #_;
my #matrix = map {
my $factor = $_;
[ map { $_ * $factor } #$ary ]
} #$fact;
return \#matrix;
}
sub outer {
my ($ary, $fact) = #_;
my #matrix;
foreach my $factor (#$fact) {
push #matrix, [];
foreach my $elem (#$ary) {
push #{$matrix[-1]}, $elem * $factor;
}
}
return \#matrix;
}
sub outer_tmp {
my ($ary, $fact) = #_;
my #matrix;
foreach my $factor (#$fact) {
my #tmp;
foreach my $elem (#$ary) {
push #tmp, $elem * $factor;
}
push #matrix, \#tmp;
}
return \#matrix;
}
my #a1 = map { 2*$_ } 1..1_000; # worth comparing only for large data
my #f1 = 1..1_000;
cmpthese( -$runfor, {
direct => sub { my $r1 = outer(\#a1, \#f1) },
w_tmp => sub { my $r2 = outer_tmp(\#a1, \#f1) },
w_map => sub { my $r3 = outer_map(\#a1, \#f1) },
});
On a nice machine with v5.16 this prints
Rate direct w_map w_tmp
direct 11.0/s -- -3% -20%
w_map 11.4/s 3% -- -17%
w_tmp 13.8/s 25% 21% --
The results are rather similar on v5.29.2, and on an oldish laptop.
So map is a touch faster than building a matrix directly, and 15-20% slower than the method using a temporary array for rows, which I'd also consider clearest. The explicit loops can be improved a little by avoiding scopes and scalars, and the "direct" method can perhaps be sped up some by using indices. But these are dreaded micro-optimizations, and for fringe benefits at best.
Note that timings such as these make sense only with truly large amounts of data, what the above isn't. (I did test with both dimensions ten times as large, with very similar results.)
The second program is mostly correct.
The problem is that you didn't unpack the second level of the array.
foreach my $x (#matrixArray){
print "$x \n";
}
should be something like:
foreach my $x (#matrixArray) {
print join(" ", #{$x}), "\n";
}
# or just:
print join(" ", #{$_}), "\n" for #matrixArray;
Your maths function can be made shorter without losing legibility (it may actually make it more legible) by cutting out unnecessary temporary variables and indexing. For example:
sub maths {
my #array1 = #{ $_[0] };
my #array2 = #{ $_[1] }; # or: ... = #{ (shift) };
my #res = ();
for my $x (#array1) {
my #row = (); # <-- bugfix of original code
for my $y (#array2) {
my $maths = $x * $y;
push #row, $maths;
}
push #res, \#row;
}
return #res;
}

Create Multi Dimensional Hash Using Array Elements

I need to create multidimensional hashes with varying depth using array elements as keys. Pseudocode attempt:
Example line:
Statement Coverage for instance Hierarchical/path/with/block/and/module ...
if (/Statement Coverage for instance (.&?) /)
{
#array = split /\//, $1;
}
for (my $eye = 1; $eye <= $#array; $eye++)
{
A miracle happens to %hash!
}
$hash{"path"}{"with"}{"block"}{"and"} now has a value of "module". Remember, the number of keys can vary. Any ideas?
That's what Data::Diver does for you:
my #array = split /\//, $1;
DiveVal(\ my %hash, #array[ 0 .. $#array - 1 ]) = $array[-1];
print Dumper(\%hash);
See my code below. It builds the desired hash recursively.
But I think that you are taking a wrong approach. I obviously don't know what exactly you are trying to achieve, but seems to me, that you should use tree data structure instead of the multidimensional hash.
use strict;
use warnings;
use v5.10;
use Data::Dumper;
my #data = (
'some/path/test',
'some/path/deeper/test',
'another/deeper/path/test',
);
my $resultHr = {};
foreach my $path (#data) {
my #elems = split /\//, $path;
buildHash($resultHr, #elems);
}
say Dumper($resultHr);
sub buildValue {
my $n = shift;
if (#_) {
return {$n => buildValue(#_)};
}
else {
return $n;
}
}
sub buildHash {
my $hr = shift;
my $k = shift;
return unless $k;
if (exists $hr->{$k} && ref $hr->{$k}) {
buildHash($hr->{$k}, #_);
}
else {
$hr->{$k} = buildValue(#_);
}
}

What is the 'best' way to delete multiple non-sequential elements in a Perl array?

While executing a script, I need to delete multiple elements (these elements are not sequential) of an array. I will get my array and indexes while executing the script.
For example:
I may get an array and list of indexes like below:
my #array = qw(one two three four five six seven eight nine);
my #indexes = ( 2, 5, 7 );
I have below subroutine to do this:
sub splicen {
my $count = 0;
my $array_ref = shift #_;
croak "Not an ARRAY ref $array_ref in $0 \n"
if ref $array_ref ne 'ARRAY';
for (#_) {
my $index = $_ - $count;
splice #{$array_ref}, $index, 1;
$count++;
}
return $array_ref;
}
If I call my subroutine like below:
splicen(\#array , #indexes);
That works for me but:
Is there any better way to do this?
If instead you splice from the end of the array, you won't have to maintain the offset $count:
sub delete_elements {
my ( $array_ref, #indices ) = #_;
# Remove indexes from end of the array first
for ( sort { $b <=> $a } #indices ) {
splice #$array_ref, $_, 1;
}
}
Another way think about it is to build a new array rather than modifying the original:
my #array = qw(one two three four five size seven eight nine);
my #indexes = (2, 5, 7);
my %indexes = map { $_ => 1 } #indexes;
my #kept = map { $array[$_] } grep { ! exists $indexes{$_} } 0 .. $#array;

Finding common elements in arrays

I have a hash whose values are arrays. I need to find the common elements of those arrays,
ie. the elements that are present in all the arrays. So I extracted the values of the hash into
a multidimensional array whose each row corresponds to an array in the hash. Then I took the first row
of this matrix into another array (#arr1) and iterated through it to find if there was any element
in arr1 that was also in the rest of the rows of the matrix. If such an element is found, it is
pushed onto another array that contains the final list of all the elements. The code is as follows
(I hope it is clear enough):
sub construct_arr(my %records) {
my $len = keys %records;
my #matrix;
my $i = 0;
# Extract the values of the hash into a matrix
foreach my $key (keys %records) {
$matrix[$i] = $records{$key};
$i++;
}
my #arr1 = $matrix[0];
my #final;
# Iterate through each element of arr1
for my $j (0..$#{$arr1[0]}) {
my $count = 1;
# Iterate through each row of the matrix, starting from the second
for ( my $i = 1; $i < $len ; $i++ ) {
my $flag = 0;
# Iterate through each element of the row
for my $k (0..$#{$matrix[$i]}) {
if ($arr1[0][$j] eq $matrix[$i][$k]) {
$flag = 1;
$count++;
}
}
# On finding the first instance of the element in a row, go to the next row
if (!$flag == 1) {
last;
}
}
# If element is in all the rows, push it on to the final array
if ($count == $len) {
push(#final, $arr1[0][$j]);
}
}
return #final;
}
I know that the above works, but I would like to know if there is any other (perlish) way to do this.
I am starting to learn perl and I am very interested in knowing things that could make my work easier
in perl as compared to other languages. If my code is the best that can be done, please let me know that
too. Any guidance would be appreciated. Thanks!
Take a look at Chris Charley's link for calculating the intersection of arrays.
Hashes are the clear way to go for problems like this. Together with map and grep a solution can be reduced to just a few lines.
This program uses sundar's data for want of anything better, and seems to do what you need.
use strict;
use warnings;
my %records = (
a => [ qw/ A B C / ],
b => [ qw/ C D E A / ],
c => [ qw/ A C E / ],
);
print "$_\n" for construct_arr(\%records);
sub construct_arr {
my $records = shift;
my %seen;
$seen{$_}++ for map #$_, values %$records;
grep $seen{$_} == keys %$records, keys %seen;
}
output
A
C
Edit
I thought it may help to see a more Perlish, tidied version of your own solution.
use strict;
use warnings;
my %records = (
a => [ qw/ A B C / ],
b => [ qw/ C D E A / ],
c => [ qw/ A C E / ],
);
print "$_\n" for construct_arr(\%records);
sub construct_arr {
my $records = shift;
my #matrix = values %$records;
my #final;
# iterate through each element the first row
for my $i ( 0 .. $#{$matrix[0]} ) {
my $count = 1;
# look for this value in all the rest of the rows, dropping
# out to the next row as soon as a match is found
ROW:
for my $j ( 1 .. $#matrix ) {
for my $k (0 .. $#{$matrix[$j]}) {
next unless $matrix[0][$i] eq $matrix[$j][$k];
$count++;
next ROW;
}
}
# If element is in all the rows, push it on to the final array
push #final, $matrix[0][$i] if $count == #matrix;
}
return #final;
}
The output is the same as for my own program, but the functionality is slightly different as mine assumes the values in each row are unique. If the sama value appears more than once my solution will break (the same applies to sundar's). Please let me know if that is acceptable.
Although the poster explained there aren't duplicates within a single array, here is my attempt which handles that case too (notice the slightly modified test data - "5" should not be printed):
#!/usr/bin/env perl
use warnings;
use strict;
my %records = (
a => [1, 2, 3],
b => [3, 4, 5, 1],
c => [1, 3, 5, 5]
);
my %seen;
while (my ($key, $vals) = each %records) {
$seen{$_}{$key} = 1 for #$vals;
}
print "$_\n" for grep { keys %{$seen{$_}} == keys %records } keys %seen;
You can find the size of the hash easily using scalar(keys %hash);
Here's an example code that does what you need:
#!/usr/bin/perl
use strict;
use warnings;
my %records = ( a => [1, 2, 3],
b => [3, 4, 5, 1],
c => [1, 3, 5]
);
my %count;
foreach my $arr_ref (values %records) {
foreach my $elem (#$arr_ref) {
$count{$elem}++;
}
}
my #intersection;
my $num_arrays = scalar(keys %records);
foreach my $elem (keys %count) {
#If all the arrays contained this element,
#allowing for multiple entries per array
if ($count{$elem} >= $num_arrays) {
push #intersection, $elem;
}
}
Feel free to comment if you need any clarification in this code. And the second foreach that constructs the #intersection array is written this way only for clarity - if you're learning Perl, I'd suggest you study and rewrite it using the map construct, since that's arguably more idiomatic Perl.

Resources