How can I print a multidimensional array in Perl? - arrays

I am trying to print a multidimensional array (matrix) in Perl passing reference to array to subroutine.
Here is my code:
sub print_matrix(\#) {
my $array = shift;
for my $i ( 0 .. $#{ $array } ) {
my $row = $array[$i];
for my $j ( 0 .. $#($row) ) {
print $array[$i][$j];
}
}
}

Borodin tells you what was wrong with your code.
Now consider this module: Data::Dumper (available on CPAN). You can use this module to print any data structure: arrayref of arrayrefs (what you called a matrix), hashref of hashrefs, arrayref of hashrefs, hashref of hashrefs, or any other combination of these structures for as many dimensions as you want. Of course, if you have too many dimensions, it could lead to a confusing output.
My point is, some time ago, I was asked in an interview how I would implement this module. I thought it was a very clever question. I had to think a little because I use the module often but never bothered to figure how it works. It is in fact very simple. Imagine in your subroutine you receive a reference but you don't actually know what kind of reference it is (scalarref, arrayref, hashref, etc.), how would you determine what it is? If you have multiple possibilities, what would you do to cover all of them? Have you thought of creating a recursive function?
So, to solve your problem quickly, if you just want to print your matrix for debugging purpose, use Data::Dumper. Otherwise, if you want to do something more complex and wish to cover multiple cases, try to create a recursive function.
Here's a Data::Dumper example:
my $arrayref = [
[ qw/ a b c d / ],
[ qw/ e f g h / ],
[ qw/ i j k l / ],
];
use Data::Dumper;
print Dumper $arrayref;
And here's the result you will get:
$VAR1 = [
[
'a',
'b',
'c',
'd'
],
[
'e',
'f',
'g',
'h'
],
[
'i',
'j',
'k',
'l'
]
];
Each "row" of your matrix is printed as a list of elements, separated by a comma (and a new line), inside a pair of brackets. Be careful, if you pass it an array, it will print each elements one by one, and you will lose the "dimensions". If you only have an array, you have to pass it as a reference like this:
print Dumper \#array;
I hope this helps.

Using plain print is OK when all you have are single letter entries in your matrix, but a module like Text::Table can make it much easier to produce tidy output. For example,
#!/usr/bin/env perl
use strict;
use warnings;
use Text::Table;
my #matrix = map {
[ map sprintf('%.2f', -500 + rand(1000)), 1 .. 5 ]
} 1 .. 5;
my $mat = Text::Table->new;
$mat->load(#matrix);
print $mat;
Output:
-7.73 -83.85 -351.18 21.06 320.40
174.83 238.29 91.16 361.43 213.04
446.43 -4.82 322.81 10.38 -436.62
-128.05 195.68 199.05 288.39 115.30
-251.19 -329.35 244.13 -428.25 454.64

You can print a two-dimensional Perl array very simply with something like this
use strict;
use warnings;
my #arr_2d = (
[ qw/ a b c d / ],
[ qw/ e f g h / ],
[ qw/ i j k l / ],
);
print_2d(\#arr_2d);
sub print_2d {
my ($matrix) = #_;
print "#$_\n" for #$matrix;
}
output
a b c d
e f g h
i j k l
Update
Here's a working version of your own code. You weren't using array references properly and had parentheses where there should have been braces. This version also prints a space after each element and a newline after each row.
sub print_matrix {
my $array = shift;
for my $i ( 0 .. $#{ $array } ) {
my $row = $array->[$i];
for my $j ( 0 .. $#{ $row } ) {
print $array->[$i][$j], ' ';
}
print "\n";
}
}

Related

Perl remove same value back to back with splice

I am trying to remove, the same values twice in an array, it is located back to back, this is my code
#{$tmp_h->{'a'}} = qw/A B B C/;
print Dumper ($tmp_h);
my $j = 0;
foreach my $cur (#{$tmp_h->{'a'}}) {
if ($cur eq 'B') {
splice(#{$tmp_h->{'a'}}, $j, 1);
}
$j++;
}
print Dumper $tmp_h;
However what got is,
$VAR1 = {
'a' => [
'A',
'B',
'B',
'C'
]
};
$VAR1 = {
'a' => [
'A',
'B',
'C'
]
};
I am expecting both 'B' to be removed in this case, what could possibly went wrong?
That code is removing from an array while iterating over it, pulling the carpet from underneath itself; is that necessary?
Instead, iterate and put elements on another array if the adjacent ones aren't equal. So iterate over the index, looking up an element and the next (or previous) one.†
I presume that B is just an example while in fact it can be any value, equal to its adjacent one.
It's interesting that regex can help too, with its simple way to find repeated patterns using backreferences
my #ary = qw(a b b c d d e f f f g);
my $str_ary = join '', #ary;
$str_ary =~ s/(.)\g{-1}//g;
my #new_ary = split //, $str_ary;
say "#new_ary"; #--> a c e f g
This removes pairs of adjacent values, so if there is an odd number of equal adjacent values it leaves the odd one (f above). As a curiosity note that it can be written in one statement
my #new_ary = split //, join('', #ary) =~ s/(.)\g{-1}//gr;
The join-ed array, forming a string, is bound to the substitution operator where /r modifier is crucial, for allowing this and returning the changed string which is then split back into a list.
To change an array in place have it assign to itself.‡
But single-letter elements are only an example, likely. With multiple characters in elements we can't join them by empty string because we wouldn't know how to split that back into an array; we have to join by something that can't be in any one element, clearly a tricky proposition. A reasonable take is a line-feed, as one can expect to know whether elements are/not multiline strings
my #ary = qw(aa no no way bah bah bah go);
my $str_ary = join "\n", #ary ;
$str_ary =~ s/([^\n]+)\n\g{-1}//g;
my #new = grep { $_ } split /\n/, $str_ary;
say "#new"; #--> aa way bah go
This would still have edge cases with interesting elements, like spaces and empty strings (but then any approach would).
† For example
use warnings;
use strict;
use feature 'say';
my #ary = qw(a b b c d d e f f f g);
my #new_ary;
my $i = 0;
while (++$i <= $#ary) {
if ($ary[$i] ne $ary[$i-1]) {
push #new_ary, $ary[$i-1]
}
else { ++$i }
}
push #new_ary, $ary[-1] if $ary[-1] ne $ary[-2];
say "#new_ary"; #--> a c e f g
‡ Done for the arrayref in the question
#{ $hr->{a} } = qw/A B B C/;
#{$hr->{a}} = split //, join('', #{$hr->{a}}) =~ s/(.)\g{-1}//gr;
say "#{$hr->{a}}"; #--> A C
The Perl documentation tells you in perlsyn under Foreach Loops:
If any part of LIST is an array, foreach will get very confused if you
add or remove elements within the loop body, for example with splice. So
don't do that.
You can iterate over the indices instead, but don't forget to not increment the index when removing a value:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my $tmp_h = {a => [qw[ A B B C ]]};
print Dumper($tmp_h);
my $j = 0;
while ($j <= $#{ $tmp_h->{a} }) {
my $cur = $tmp_h->{a}[$j];
if ($cur eq 'B') {
splice #{ $tmp_h->{a} }, $j, 1;
} else {
++$j;
}
}
print Dumper($tmp_h);
Or start from the right so you don't have to worry:
my $j = $#{ $tmp_h->{a} };
while ($j-- >= 0) {
my $cur = $tmp_h->{a}[$j];
splice #{ $tmp_h->{a} }, $j, 1 if $cur eq 'B';
}
But the most straight forward way is to use grep:
#{ $tmp_h->{a} } = grep $_ ne 'B', #{ $tmp_h->{a} };

Iterate over first at most n elements of an perl array

What is the most pleasant idiomatic way of writing
for (take(100,#array)) {...}
given that there is no take (which takes the first n elements of a list, but fewer if there are not n elements)?
Things I considered:
for (#array[0..99]) {...}
but that fails ungraceful if #array has fewer than 100 elements
for (#array[0..min(99,$#array)]) {...}
but min is not a standard function in Perl
for (splice #array,0,100) {...}
bu that changes the array.
for (#array[0..min(99,$#array)]) {...}
but min is not a standard function in Perl
min is a standard function in the module List::Util, which is part of core as of 5.7.3.
use List::Util qw(min);
for (#array[0..min(99,$#array)]) { # generator in 5.8.8+
...
}
Note that perl 5.8.8 onward, and perhaps earlier, is smart enough to understand that expression as a generator rather than a slice. That is, elements 0 through $terminus are fetched one at a time from #array instead of an anonymous slice being taken and copied.
You want the CPAN module List::Slice
use List::Slice 'head';
foreach my $elem ( head 100, #things ) { ... }
You've indicated you've found the following the cleanest:
take(100, #array)
So to answer your question as to what's the cleanest, that is! I don't see why you're trying to find an alternative.
How about using map:
my #array = qw ( 1 2 3 4 );
print join "\n", map { $_ // () } #array[0..10];
This takes 10 elements from a list, but applies the 'defined' test to it - and if it's not defined, returns an empty list.
So you could:
for ( map { $_ // () } #array[0..100] ) {
#do something
}
Note - // is a defined-or operator, only available from perl 5.10+. You could use instead a defined ternary:
print join "\n", map { defined ? $_ : () } #array[0..10];
You could add an additional check inside of your loop to break once the end is reached.
my #arr = (1 .. 90);
for ( #arr[0..99]) {
last unless defined $_;
say;
}
But that would not work on arrays that have undef values in between, like these:
my #foo = (1, 2, undef, 4);
my #bar;
$bar[2] = 'foo'; # (undef, undef, 'foo')
The other responses have this covered but, just for thoroughness, there are a couple of "pumpkin perl" gather/take implementations on CPAN :-)
List::Gather
Syntax::Keyword::Gather
There is also Damian Conway's Perl6::Gather which is pretty much the same but requires Perl6::Export.
They let you work with lists in the way you want. e.g. to "take" half the alphabet:
perl -E 'use List::Gather; #lpha = ("a" .. "z");
#half = gather { for (#lpha){ take $_ if gathered < 13 } } ; say #half'
abcdefghijklm
or less if we aren't halfway there yet :
perl -E 'use List::Gather; #lpha = ("a" .. "c");
#half = gather { for (#lpha) { take $_ if gathered < 13 } } ; say #half'
abc
With List::Gather the gather block can take a loop (because of lexical scoping inside gather{} ??) and the topic $_ is required inside the block:
perl -E 'use List::Gather; #lpha = ("a" .. "g");
#half = gather for (#lpha) { take $_ if gathered < 13 }; say #half'
With Syntax::Keyword::Gather you do that inside the gather{} block (which is also possible with List::Gather):
perl -E 'use Syntax::Keyword::Gather; #lpha = ("a".."g");
#half = gather { for (#lpha){ take if gathered < 13 } }; say #half'
I find gather/take to be a nice alternative way to work with lists. Whether it is nice enough to ship with perl one day - say in List::Util - is an interesting implicit part of your question ;-) but they are on CPAN.
Postscript
To address some of the concerns about defined-ness raised by #simbabque, #zaid and #Joachim Breitner more checks can be added to the take() routine.
Here I use Ingy's boolean:
perl -E 'use boolean; use List::Gather;
#lpha = ("a" .. "g", "", undef, undef, "x", "0", "z");
#half = gather { for (#lpha){ take $_ if boolean($_) && gathered < 13 }};
use DDP; p #half;'
Output:
[
[0] "a",
[1] "b",
[2] "c",
[3] "d",
[4] "e",
[5] "f",
[6] "g",
[7] "x",
[8] "z"
]
I think you should use iterator pattern, i.e.
my $iterator = create_iterator(100);
while (my $element = $iterator->()) {
...;
}
There limit might be either embedded into iterator creation, i.e.
sub create_iterator {
my $limit = shift;
my #data = (0 x 1000);
my $i = 0;
return sub {
return $data[$i++] if ($i < #data);
}
}
PS. There is a limitation, that undef cannot be part of #data

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.

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.

How to properly create and loop in a 3D array in perl?

I've got an algorithm that selects a cell in a 3d array and then reads or writes the data wich is a reference to another 3d array. Think of it as a "minecraft" algorithm.
The problem is i have no idea how to make a data structure in Perl that works like this : #3darray(x,y,z) = value
Can you help me?
If I understand correctly:
use Data::Dumper;
my ($x, $y, $z) = (1, 2, 3);
my #array = map [map [map 0, 1..$z], 1..$y], 1..$x;
print Dumper \#array;
Output:
$VAR1 = [
[
[
0,
0,
0
],
[
0,
0,
0
]
]
];
However, there's no need to make this structure beforehand, since Perl creates it for you through autovivification (see reference further down) when you access an element in the nested structure:
use Data::Dumper;
my #array;
$array[0][0][2] = 3;
print Dumper \#array;
Output:
$VAR1 = [
[
[
undef,
undef,
3
]
]
];
From perlglossary:
autovivification
A Greco-Roman word meaning "to bring oneself to life". In Perl, storage locations
(lvalues) spontaneously generate themselves as needed, including the creation of
any hard reference values to point to the next level of storage. The assignment
$a[5][5][5][5][5] = "quintet" potentially creates five scalar storage locations,
plus four references (in the first four scalar locations) pointing to four new
anonymous arrays (to hold the last four scalar locations). But the point of
autovivification is that you don't have to worry about it.
As for looping, if you need indexes:
for my $i (0 .. $#array) {
for my $j (0 .. $#{$array[$i]}) {
for my $k (0 .. $#{$array[$i][$j]}) {
print "$i,$j,$k => $array[$i][$j][$k]\n";
}
}
}
Otherwise:
for (#array) {
for (#$_) {
for (#$_) {
print "$_\n";
}
}
}

Resources