views:

276

answers:

3

I am struggling through objects in perl, and am trying to create a 2d array and store it in a hash field of my object. I understand that to create a 2d array I need an array of references to arrays, but when I try to do it I get this error: Type of arg 1 to push must be array (not hash element) The constructor works fine, and set_seqs works fine, but my create_matrix sub is throwing these errors.

Here is what I am doing:

sub new {
    my ($class) = @_;
    my $self = {};
    $self->{seq1} = undef;
    $self->{seq2} = undef;
    $self->{matrix} = ();
    bless($self, $class);
    return $self;
}
sub set_seqs {
    my $self = shift;
    $self->{seq1} = shift;
    $self->{seq2} = shift;
    print $self->{seq1};
}

sub create_matrix {
    my $self = shift;
    $self->set_seqs(shift, shift);
    #create the 2d array of scores
    #to create a matrix:
    #create a 2d array of length [lengthofseq1][lengthofseq2]
    for (my $i = 0; $i < length($self->{seq1}) - 1; $i++) {
        #push a new array reference onto the matrix
        #this line generates the error
        push(@$self->{matrix}, []);
    }
}

Any idea of what I am doing wrong?

+4  A: 

You're missing an extra set of braces when you dereference $self. Try push @{$self->{matrix}}, [].

When in doubt (if you're not sure if you're referring to the correct value in a complicated data structure), add more braces. :) See perldoc perlreftut.

Ether
+1 good future suggestion.
Axeman
So I need the extra set of braces because $self->{matrix} returns a reference?
Jergason
Right. Without them, you'd be dereferencing $self as an array and then trying to get a field on that (and since arrays can be treated as hashes, that's legal), which yields the "must be array (not hash element)" error that you are seeing. So you need the braces to get the arrayref `$self->{matrix}` first, then dereference that to an array for `push`.
Ether
PS. `perl -MO=Deparse,-p` would probably be useful here in seeing how your code is being parsed (i.e. the order of operations in an object reference), but that's beyond the scope of this post :) search for `[perl] deparse` on this site for more info.
Ether
+2  A: 
sub create_matrix {
    my($self,$seq1,$seq2) = @_;
    $self->set_seqs($seq2, $seq2);

    #create the 2d array of scores
    #to create a matrix:
    #create a 2d array of length [$seq1][$seq2]
    for( 1..$seq1 ){
        push @{$self->{matrix}}, [ (undef) x $seq2 ];
    }
}
Brad Gilbert
I'm confused about some of the magic in your code. What is the for(1..$seq1) doing? What is the [ (undef) X $seq2]?
Jergason
+3  A: 

Perl is a very expressive, language. You can do that all with the statement below.

$self->{matrix} = [ map { [ (0) x $seq2 ] } 1..$seq1 ];

Is this golf? Maybe, but it also avoids mucking with the finicky push prototype. I explode the statement below:

$self->{matrix} = [     # we want an array reference
    map {               # create a derivative list from the list you will pass it
        [ (0) x $seq2 ] # another array reference, using the *repeat* operator 
                        # in it's list form, thus creating a list of 0's as 
                        # long as the value given by $seq2, to fill out the  
                        # reference's values.
   } 
   1..$seq1             # we're not using the indexes as anything more than  
                        # control, so, use them base-1.
];                       # a completed array of arrays.

I have a standard subroutine to make tables:

sub make_matrix { 
    my ( $dim1, $dim2 ) = @_;
    my @table = map { [ ( 0 ) x $dim2 ] } 1..$dim1;
    return wantarray? @table : \@table;
}

And here's a more generalized array-of-arrays function:

sub multidimensional_array { 
    my $dim = shift;
    return [ ( 0 ) x $dim ] unless @_; # edge case

    my @table = map { scalar multidimensional_array( @_ ) } 1..$dim;
    return wantarray ? @table : \@table;
}
Axeman