Two dimensional array access in Perl

2019-07-13 16:30发布

问题:

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...):

@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;
    }
}

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?

回答1:

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



回答2:

To make references a bit easier to understand, I prefer the -> syntax instead of the munge-it-all-together syntax.

Instead of:

@{$_[1]}[0].

Try

$_[1]->[0];

It means the same thing. It's easier, and it's cleaner to see. You can see that $_[1] is an array reference, and that you're referencing the first element in that array reference.

However, an even better way is to simply set variables for your various element in @_. You have to type a few more letters, but your code is much easier to understand, and is a lot easier to debug.

sub print_elements {
    my $file_handle      = shift;   # This isn't a "reference", but an actual file handle
    my $cities_array_ref = shift;   # This is a reference to your array

    my @cities = @{ $cities_array_ref };  # Dereferencing makes it easier to do your program

Now, your subroutine is dealing with variables which have names, and your array reference is an array which makes things cleaner. Also, you cannot accidentally affect the values in your main program. When you use @_, it's a direct link to the values you pass to it. Modifying @_ modifies the value in your main program which is probably not what you want to do.

So, going through your subroutine:

sub printElements {
    my file_handle        = shift;
     my $cities_array_ref  = shift;

    my @cities = @{ $cities_array_ref };
    my $counter;
    my $array_size = @cities;     # No need for scalar. This is automatic
    while  ( my $line = $file_handle->getline and $counter < 1000 ) {
        chomp $line;
        my $city_number = $counter % $array_size;
        print $cities[$city_number]. "\n";
        $counter += 1;
    }
}

Note, how much easier it is to see what's going on by simply assigning a few variables instead of trying to cram everything together. I can easily see what your parameters to your subroutine are suppose to be. If you called the subroutine with the incorrect parameter order, you could easily spot it. Also notice I broke out $counter % $array_size and assigned that to a variable too. Suddenly, it's obvious what I'm trying to get out of it.

However, I can't see where you're using the $line you're getting with getline. Did I miss something?

By the way, I could have done this without referencing the array in the while loop too:

sub printElements {
    my file_handle        = shift;
    my $cities            = shift;   # This is an array reference!

    my $counter;
    my $array_size = @{ $cities };   # I need to deref to get an array
    while  ( my $line = $file_handle->getline and $counter < 1000 ) {
        chomp $line;
        my $city_number = $counter % $array_size;
        print $cities->[$city_number]. "\n";   # That's it!
        $counter += 1;
    }
}

See how that -> syntax makes it easy to see that $cities is a reference that points to an array? A lot cleaner and easier to understand than ${$cities}[$city_number].



回答3:

This code wouldn't actually compile.

print @{$_[1][($counter)%$arraySize];

probably wants to be:

print $_[1]->[($counter)%$arraySize];

after you fix arraySize.

If the result is somehow a pointer to an array then

print "@{$_[1]->[($counter)%$arraySize]}";


回答4:

I figured how to solve my #1 problem (still looking for help on my #2 if anyone can).

I changed

my $arraySize = scalar $_[1];

to

my $arraySize = @{$_[1]};

And my second print statement is printing the way I want.

It seems that scalar $_[1] was taking the memory address of the array and I was moding against this allowing my $counter to go way beyond the number of elements in the array.



回答5:

References confuse me too! I always like to dereference them as soon as possible. This works for me:

sub printElements{

    my $counter = 0;
    my $fh = $_[0];
    my @array = @{$_[1]};
    my $arraySize = scalar @array;

    # Prints fine!!!
    print @array[($counter)%$arraySize];

    while ((my $line = $fh->getline()) && $counter < 1000){

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

        $counter += 1;
    }
}

I'm sure someone else could explain in the comments why they think working with the reference is a better way (please do), but under the mantra of "keep it simple", I don't like working with them. Probably because I was never a C programmer...