Multidimensional Arrays in Perl

My last post dealt with the technique of using a hash to pass named parameters to a subroutine.  Since I covered the hash data structure last time, it seemed appropriate to spend some time on arrays.  Using a one dimensional array in Perl is straight forward and routine practice -not very exciting.  On the other hand, multidimensional arrays are quite interesting, because Perl doesn’t really support them.  They have to be simulated using pointers.

In Perl a reference to an array, hash or scalar can be created using a backslash (\).  For example:

   my $scalar=1.50;
   my @array=qw(one two three);
   my %hash=(a=>'1', b=>'2', c=>'3');

   # Create references...
   my $ref1=\$scalar;
   my $ref2=\@array;
   my $ref3=\%hash;

We can de-reference, or access the data the reference points to, by prefixing the reference variable with the data type symbol.  So for example, to de-reference an array, use @$ref2.  Or for a hash, use %$ref3.  In the case of arrays, use $ref2->[n] to access a specific element’s value, where n is an index value.  And for a hash, use $ref3->{key}.

Continuing from the script started above, we can print the original values using these de-referencing techniques:

   print "Scalar: " . $$ref1 . "\n";
   print "Array:  " . join(',', @$ref2) . "\n";
   print "Hash:   ";
   for (sort keys %$ref3) { print $_ . '=>' . $ref3->{$_} . " " };
   print "\n";

This produces the following:

   Scalar: 1.5
   Array:  one,two,three
   Hash:   a=>1 b=>2 c=>3

“Now we know how to create and use references, but what does this have to do with multidimensional arrays?”, you ask.  Well, imagine what we could do with an array of pointers, or references, to additional arrays.  Think an array of arrays.

Let’s say we we want to create a matrix of 2 rows and 10 columns. The cells of the matrix will be filled with the alphabet, so that row 0, column 5 returns the letter ‘F’.

      0 1 2 3 4 5 6 7 8 9
   0: A B C D E F G H I J
   1: K L M N O P Q R S T

Here’s the code to create and print a two dimensional matrix like the one above:

#!/usr/bin/perl -w

   use strict;

   my @chars=qw(A B C D E F G H I J K L M N O P Q R S T);
   my @array1=@chars[0..9];
   my @array2=@chars[10..19];
   my @array3;
   my @innerArray;
   my $i;
   my $j;

   push(@array3, \@array1, \@array2);

   # Print our column header row
   print "  ";
   for $i (0..9) { print ' ' . $i }
   print "\n";

   # Print the conents of array3 using a technique not reliant on
   # knowing the size of any of our arrays.
   for $i (0..$#array3) {
       print $i . ": " . join(' ', @{$array3[$i]}) . "\n";
   }

   # And the value of row 0 column 5 is...
   print "\n(0,5)= " . $array3[0]->[5] . "\n";

A few notes… A range can be used to split an array. Doing it properly requires referencing the array with @ rather than $. References to array1 and array2 are pushed onto array3, making array3 an array of arrays. The ‘for’ and ‘foreach’ statements are interchangeable when looping through a range. When looping through an array, never hard code or assume the number of elements is known. Use $#arrayname to get the last index number, or use @arrayname to operate on the entire array. Notice how braces are used in @{$array3[$i]} to de-reference the inner array.

Running the script produces the following:

      0 1 2 3 4 5 6 7 8 9
   0: A B C D E F G H I J
   1: K L M N O P Q R S T

   (0,5)= F

Who says you can’t do multidimensional arrays in Perl? It might not be the most modern language out there, but look at how much work we were able to do with only a few lines of code. Take away the formatting and title we added to the grid, and there’s not much left. Perl makes doing the complex simple.

Passing Named Parameters in Perl

I had the privilege of working with a veteran Perl warrior today.  Such encounters are always welcomed learning opportunities, and this was no exception.  We were looking at some code, and he asked me if I knew how to pass named parameters to a subroutine in such a way that some parameters could be optional, while others might be required.  I thought for a moment.  I came up blank.  All I could think of was looping through the @_ array.  That would be messy.  I knew right away that using the @_ array was not what he had in mind.

He gave me a hint, “Use a hash.”  OK.  That makes sense.  A hash consists of key/value pairs.  The $myhash{‘key’} syntax can be used to check for the value of a particular parameter.  If no value is returned, the parameter was not passed.  I was warming up to the idea as I started to think about it, but I still didn’t have the whole picture.  As he scribbled out an example on a white board, it quickly came into focus.

  #!/usr/bin/perl -w

  use strict;

  &displayParms (a=>"3.00", c=>"foo", d=>"bar");

  sub displayParms {
      my $parm;
      my %params = (a => "a",
                    b => "b",
                    c => "c",
                    @_
                   );

      foreach $parm (sort(keys %params)) {
         print $parm . "=>" . $params{$parm} . "\n";
      }

  }

The displayParms subroutine makes only one assumption- parameters will be passed as a hash. Other than that, it does not care how many hash keys are passed, nor does it pay regard to the order.

Running the above script produces the following:

   a=>3.00
   b=>b
   c=>foo
   d=>bar

The %params hash provides default values for keys ‘a’, ‘b’ and ‘c’.  The @_ array then gets added to the end of %params.  “But wait, we’re appending an array to a hash. That can’t work!”  Well, it does, actually.  Remember the traditional way to define a hash:

     %myhash=( "a", "3.00", "b", "foo", "c", "bar" )

A hash is really an array.  If we throw an array that follows the same pattern as above into our hash definition, it works.  So in our case, the %params hash ends up looking like this:

     %parms=("a","a","b","b","c","c","a","3.00","c","foo","d","bar")

Here is again, but this time with pointer notation:

     %params=( a=>"a",
               b=>"a",
               c=>"c",
               a=>"3.00",
               c=>"foo",
               d=>"bar" );

Notice that it is important to add the @_ array to the end of the %params hash.  For keys that repeat, the last value assigned to the key is the value returned later when the hash is accessed.  So for example, in the case of key ‘a’, the default value of ‘a’ is replaced with ’3.00′ -the value passed in from the command line.

From here it’s simple to reference keys in the %params hash and process the command line arguments.  Simple.  Brilliant.

Thanks for a valuable insight.  Why didn’t I think of that?

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: