donsiuch donsiuch - 1 year ago 75
Perl Question

Two dimensional array access in Perl

Trying to learn Perl. I have an array populated with cities. I want to pass the array by reference to a sub routine and print each city to output. However I have the following problems:

1) I can access each element before my while loop in the sub routine. But I cannot access the elements within my while loop. I get the error message:

Use of uninitialized value in print at <filename> line 44, <GEN2> line 997 (#1)
Use of uninitialized value in print at <filename> line 44, <GEN2> line 998 (#1)

Following is come code. I have commented what prints and what doesn't (I tried to cut out code that is not needed for my explanation...):


#Assume cities is loaded successfully
&loadCities(getFileHandle('cities.txt'), $NUM_CITIES, \@cities);
&printElements(getFileHandle('names.txt'), \@cities);

sub printElements{

my $counter = 0;
my $arraySize = scalar $_[1];

# Prints fine!!!
print @{$_[1][($counter)%$arraySize];

while ((my $line = $_[0]->getline()) && $counter < 1000){

#Doesn't print. Generates the above error
print @{$_[1][($counter)%$arraySize];

$counter += 1;

2) The Perl syntax has me super confused. I do not understand what is going on with @{$_[1]}[0]. Trying to work it out.

  1. $_[1], treat the value at this location as scalar value (memory
    address of the array)

  2. @{...}, interpret what is stored at this
    memory address as an array

  3. @{...} [x], access the element at index x

Am I on the right track?

Answer Source

My first tip is that you should put use strict; and use warnings; at the top of your script. This generally reveals quite a few things.

This line: print @{$_[1][($counter)%$arraySize]; doesn't have a closing }. You also don't need the parenthesis around $counter.

Like you mentioned, the best/most clear way to get the length of an array is my $arraySize = scalar @{$_[1]};.

You can check out the documentation here for working with references. I'll give you a quick overview.

You can declare an array as normal

my @array = (1, 2, 3);

Then you can reference it using a backslash.

my $array_ref = \@array;

If you want to use the reference, use @{...}. This is just like using a regular array.

print @{$array_ref};

You could also declare it as a reference to begin with using square braces.

my $array_ref = [1, 2, 3];
print @{$array_ref}; # prints 123

In Perl, a 2-dimensional array is actually an array of array references. Here is an example:

my @array = ( ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'] );
print @{$array[1]}; # prints def

Now let's try passing in an array reference to a subroutine.

my @array = ( ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'] );

example(\@array); # pass in an array reference

sub example {
    my @arr = @{$_[0]}; # use the array reference to assign a new array
    print @{$arr[1]};

    print @{$_[0][1]}; # using the array reference works too!

Now let's put it together and print the whole 2-d array.

my @array = ( ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'] );
sub example {
    my @arr = @{$_[0]};
    for my $ref (@arr) {
        print @{$ref};
} # prints abcdefghi

You could adapt this example pretty easily if you wanted to use it for your printElements subroutine.

Another note about printing the elements in an array. Let's take this line from the last example:

print @{$ref};

Since we are calling it every time through the loop, we may want to print a new line at the end of it.

print @{$ref} . "\n";

What does this print? Try it! Does it work?

This is where the built-in subroutine join comes in handy.

print join(" ", @{$ref}) . "\n";

For loops are generally the best way to iterate through an array. My answer here talks a little about doing it with a while loop: You can also check out this question: Best way to iterate through a Perl array