Search code examples
arraysperlscalar-context

Two-dimensional array access in 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 subroutine. But I cannot access the elements within my while loop. I get the error message:

    ... Use of uninitialized value in print at line 44, line 997 (#1) Use of uninitialized value in print at line 44, line 998 (#1) ...

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

@cities;

# 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;
    }
}
  1. The Perl syntax has me super confused. I do not understand what is going on with @{$_[1]}[0]. I am 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?


Solution

  • 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'] );
    example(\@array);
    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: https://stackoverflow.com/a/21950936/2534803 You can also check out this question: Best way to iterate through a Perl array