Leudwinus has asked for the wisdom of the Perl Monks concerning the following question:
Fellow Monks,
I am hoping you can help me better understand array references and the use of multi-dimensional arrays in Perl. I need to be able to iterate through the rows and columns of a two-dimensional array as well as add columns to existing rows.
My first attempt was to use an array reference when constructing the array:
my $table = [[1, 2, 3], [4, 5, 6]]; # using reference
With this, I can iterate through all the elements:
for my $row (@{$table}) {
for my $column (@{$row}) {
print "$column\n";
}
}
My two issues with this approach are that (1) I’m struggling with how to access an individual element of the table, such as the number 4
print "@{ $table }[1][0]\n"; # syntax error
and (2), how do I add additional elements so that $table ends up like this:
$table = [[1, 2, 3, 8], [4, 5, 6, 9]]
I also tried the following:
my @table2 = [[7, 8, 9], [10, 11, 12]]; # using array
which allows me to do this to iterate through the values:
for my $row (@table2) {
for my $column (@{$row}) {
print "@{$column}\n";
for my $element (@{$column}) {
print "$element\n";
}
}
}
As well as this to print individual elements:
print "@{$table2[0][1]}[2]\n"; # prints 12
And add elements to the array:
push (@{$table2[0][1]}, 13);
print "@{$table2[0][1]}\n"; # prints 10 11 12 13
But this method seems odd that I have to use 3 indices to access an element on a two-dimensional array.
Sorry for the long post but would appreciate some guidance on the best way to structure this array for idiomatic iteration and the ability to add elements after the array has been defined.
Gratias tibi ago
Leudwinus
Re: Multi-Dimensional Arrays and Array References
by choroba (Cardinal) on Nov 16, 2020 at 16:01 UTC
|
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
use Data::Dumper;
my $ref = [[1, 2, 3], [4, 5, 6]];
# Print an element.
say $ref->[1][1];
# Add a row.
push @$ref, [7, 8, 9];
# Add a column.
my @column = (3.5, 6.5, 9.5);
push @{ $ref->[$_] }, $column[$_] for 0 .. $#column;
# The same with an array.
my @array = ([1, 2, 3], [4, 5, 6]);
# Print an element.
say $array[1][1];
# Add a row.
push @array, [7, 8, 9];
# Add a column.
my $column = \@column;
push @{ $array[$_] }, $column->[$_] for 0 .. $#$column;
Dumper($ref) eq Dumper(\@array) or die "Different";
map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
| [reply] [d/l] [select] |
|
Hi choroba,
Thank you very much for your reply. If I may trouble you a bit more, I have a few more questions:
1. When should I store my data in a reference such as
my $ref = [[1, 2, 3], [4, 5, 6]];
versus an array
my @array = ([1, 2, 3], [4, 5, 6]);
What is the difference?
2. In both your first example using the reference, how come $ref->[$_] can’t be written as $ref[$_]?
Gratias tibi ago
Leudwinus
| [reply] [d/l] [select] |
|
| [reply] |
|
Regarding my second question above:
Is it because $ref points to a reference of an array and therefore $ref->[2] would be the third element of that referenced array? Whereas $ref[2] would be the third element of the array @ref which perhaps doesn’t exist in this example?
| [reply] [d/l] [select] |
|
|
Re: Multi-Dimensional Arrays and Array References
by Fletch (Bishop) on Nov 16, 2020 at 15:28 UTC
|
You should (re-)read perlreftut and perllol while possibly having copies of Data::Dumper output of your samples' contents handy. That being said when you're referencing a single element of an array having a leading @ sigil is a good sign you're doing something wrong.
You're also probably confusing yourself trying to directly compare an array reference in the scalar $table against an actual array @table2. When you initialize my @table2 = [ [7, 8, 9], ... ] what you're actually doing is putting a reference to an anonymous array into the 0th (first) element (i.e. you're setting $table2[0] to point to an arrayref) so that's where your extra level of indexen comes in.
Edit: perldsc might also be useful for more examples. Also tweaked wording.
The cake is a lie.
The cake is a lie.
The cake is a lie.
| [reply] [d/l] [select] |
|
Hi Fletch,
You are correct that I am very confused about
- the difference between
- how to work with, and
- how to compare
arrays and array references but the links to the Perl documentation pages you provided look quite useful. Thank you for directing me that way.
I really love how perldoc allows you to browse those pages directly on the command line too!
Gratias tibi ago
Leudwinus
| [reply] [d/l] |
Re: Multi-Dimensional Arrays and Array References
by johngg (Canon) on Nov 16, 2020 at 15:34 UTC
|
my @table2 = [[7, 8, 9], [10, 11, 12]]; # using array
To assign to an array you need parentheses, not square brackets.
my @table2 = ([7, 8, 9], [10, 11, 12]);
The following code might help you with accessing array references.
johngg@abouriou:~/perl/Monks$ perl -Mstrict -Mwarnings -MData::Dumper
+-E '
my $raTable = [
[ 1, 2, 3 ],
[ 4, 5, 6 ],
];
say qq{\nNumber 4 is found at \$raTable->[ 1 ]->[ 0 ] - $raTable->[ 1
+]->[ 0 ]};
push @{ $raTable->[ 0 ] }, 8;
push @{ $raTable->[ 1 ] }, 9;
print Data::Dumper->Dumpxs( [ $raTable ], [ qw{ raTable } ] );'
Number 4 is found at $raTable->[ 1 ]->[ 0 ] - 4
$raTable = [
[
1,
2,
3,
8
],
[
4,
5,
6,
9
]
];
I hope this is helpful.
| [reply] [d/l] [select] |
Re: Multi-Dimensional Arrays and Array References
by GrandFather (Saint) on Nov 16, 2020 at 20:16 UTC
|
In Perl $, @ and % tell you what you get out of an expression, not what you are putting in. When you declare an array: my @table; the @ tells you this particular variable ("table") is an array. When you write my $value = $table[1]; the $ on each variable tells you you are dealing with scalar values. The table[1] expression accesses the second element of the array "table" and returns a scalar value - arrays only ever store scalars.
Perl allows you to build complex structures by providing references to things. So an array can be an array of references to arrays - your case. The usual way to do that would be:
my @table = ([1, 2, 3], [4, 5, 6]);
An array of arrays. You can then iterate over all the elements by:
for my $row (@table) {
for my $cell (@$row) {
printf "%4d", $cell;
}
print "\n";
}
or, this being Perl you could:
print "@$_\n" for @table;
Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
| [reply] [d/l] [select] |
Re: Multi-Dimensional Arrays and Array References
by bliako (Monsignor) on Nov 16, 2020 at 19:39 UTC
|
Start with an array my @arr = (1,2,3); Get a reference to it my $ref = \@arr; my $ref1 = \@arr; Note that ref and ref1 point to the same array, they are the same memory location: print $ref." and ".$ref1; Access elements: print $arr[1]; print $ref->[1];
Or start with a (array) reference my $ref = [1,2,3]; "Dereference" it: my @arr = @{$ref}; print $arr[1];
An array can hold scalars as well as references to other arrays or any other data structure or object. So in my @arr = (1,2,3, [7,8,9]); print $arr[1]; print $arr[3]->[1]; , the $arr[3] is a reference and its elements must be accessed using '->[]' as opposed to '[]'. Note that "->" can be used to access the internals (i.e. inside the memory space where the reference points to) of other types of references, e.g. the value from a hashref given the key or execute a subroutine inside an object. A "proper" 2D array, the way you want it, is just a 1D array whose 2 elements are references to other arrays: my @arr = ([1,2,3],[4,5,6]);
And multi-dimensional arrays need not be rectangular or homogeneous as this shows: my @arr = (1,2,3, [7,8,9], File::Find::Node->new("/home/bliako") );
If you have a C background these things are common and inbred. But if you don't, then you may wonder why things are not as simple as: @arr = ( (1,2,3), (4,5,6) ); This in actuality concatenates the two sub-arrays into a 1D array (as I understand it). I guess it would be too much dumping down for Perl to allow you to specify a 2D array this way and allow you (let alone "lead you") to abstract completely the hardware underneath and forget that RAM is 1D.
This is also useful in order to demonstrate the above point: $ref = [1,2,3]; @arr = ($ref, $ref); $arr[0]->[1] = 66; print $ref->[1]; print $arr[1]->[1]; $ref->[1] = 33; print $arr[0]->[1];
Or this: $ref = [1,2,3]; @arr = ($ref); $ref = [4,5,6]; push @arr, $ref; print $arr->[0]->[1]; print $arr->[1]->[1];
References are memory addresses pointing to the real data structure or object. They are useful in that you can pass them around to subroutines without risking making copies to the whole data structure (re: https://www.effectiveperlprogramming.com/2010/05/use-scalar-references-to-pass-large-data-without-copying). Most importantly, you can have the subroutine modifying items inside the data structure. That's why I learned to love them. Strange love indeed! A program can be written without using a @arr = (...); at all, but only $ref = [...];
Edit: "memory addresses pointing to the real data structure" maybe I should rephrase this to "they are the starting address of the memory where the real data structure resides". Also fixed ref1 and ref2.
bw, bliako
| [reply] [d/l] [select] |
Re: Multi-Dimensional Arrays and Array References
by Marshall (Canon) on Nov 16, 2020 at 23:32 UTC
|
Here are some more examples for you...
#!/usr/bin/perl
use strict;
use warnings;
# simple 1D arrays:
my @rowx = (1,2,3,4);
my @rowy = qw (a b c);
# A 2D array is an array of references to 1D arrays:
# Note that Perl 2D arrays do not need to have the same
# number of elements on each row!
my @twoD = (\@rowx, \@rowy);
foreach my $rowref (@twoD)
{
print "@$rowref\n";
}
# prints
# 1 2 3 4
# a b c
# this can be a bit confusing...
# In the first print, Perl knows that $twoD[0][1] means
# an array element of @twoD, not be confused with
# perhaps a simple scalar of the same name!
#
# In the second print, we get the value of $twoD,
# a simple scalar
my $twoD = "asdf";
print "$twoD[0][1]\n"; # prints 2
print "$twoD\n"; # prints asdf
# In general, DO NOT use the same variable name for
# two different sigils! Many things are "legal" in Perl
# that you should not do!
Update: You will notice that in my foreach loop, I used the name "rowref" instead of just "row". Perl wouldn't care. But naming the iteration variable like this helps me to remind myself to dereference that thing. YMMV.
| [reply] [d/l] |
|
use strict;
use warnings;
package PrintMe;
use overload '""' => sub {return $_[0]{str};};
sub new {
return bless {str => $_[1]}, $_[0];
}
package main;
my @mixedTable = (
[1 .. 5],
{a => 1, b => 2, wibble => 'plop'},
PrintMe->new("The quick brown fox")
);
for my $row (@mixedTable) {
if ('ARRAY' eq ref $row) {
print "@$row\n";
} elsif ('HASH' eq ref $row) {
print join ', ', map{"$_ => $row->{$_}"} sort keys %$row;
print "\n";
} else {
print "$row\n";
}
}
Prints:
1 2 3 4 5
a => 1, b => 2, wibble => plop
The quick brown fox
Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
| [reply] [d/l] [select] |
Re: Multi-Dimensional Arrays and Array References
by perlfan (Vicar) on Nov 18, 2020 at 20:01 UTC
|
One thing to note is that even if your first level variable is a hash or array (not a reference) creating any "nested" structure (for doing actual or interesting things) based on these will necessarily require references. Keep plugging away at it, the learning curve is steep here but persistence pays off wildly over time. | [reply] |
|
|