rightfield has asked for the wisdom of the Perl Monks concerning the following question:

Hi,

I wrote some pseudo code and when Perl-izing it I discovered I can't seem to do what I had planned the way I had planned. I do not use Pearl often so I was wondering if you could tell me how you usually save values in a two-dimensional array as I have not found any examples.

Below is the partly Perl-ized portion of code where I had expected to loop through the two dimensions of an array called @summary, the first dimension would represent the week-of-the-month and the second the day-of-the-week. The value to be put in each location was the total number of orders placed on a particular day (the %total_opd hash) with each row across showing the day of the week and the columns were how many weeks the month spanned (a month can span 4, 5, or 6 weeks). Below is kind of what the output would look like: 0,0 means week 1 (partials count) and the second 0 is for Sunday. Next would be 0,1 for week 1, day 1=Monday.

There may or may not still be other bugs but the real issue is how would I stuff the array?

Thanks.

RF

wk1 wk2 wk3 wk4 wk5
Sunday0,01,02,03,0 4,0
Monday0,11,12,13,1 4,1
Tuesday0,21,22,2
Wednesday0,3
Thursday0,4
Friday0,5
Saturday0,6
Total0,7


# create data for small summary table of all retailers my $numWks = (($num_days - ( 7 - $firstDOW_curMonth )) / 7 ) + 1; if ( (($num_days - ( 7 - $firstDOW_curMonth )) % 7 ) ) { $numWks++; } my @summary[$numWks][7]; # !!! uh-oh this does not Perl-ize !!! # init the blank portion of $summary[][] if there is one my $temp = 0; while ( $temp < $firstDOW_curMonth ) { $summary[0][$temp++] = "--"; } my $wktotal; my $count=1; my $firstDOW = $firstDOW_curMonth; for ( my $wk = 0; $wk < $numwks; $wk++ ) { $wktotal = 0; for ( my $dw = $firstDOW; $dw < 7; $dw++ ) { $summary[$wk][$dw] += $total_opd{$count}; $wktotal += $total_opd{$count}; last if ($count++ == $num_days); } $firstDOW = 0; $summary[$wk][$7] = $wktotal; last if ($count > $num_days); }

Replies are listed 'Best First'.
Re: pre-size a two-dimensional array
by ikegami (Patriarch) on Jul 11, 2007 at 21:58 UTC

    Perl doesn't have two-dimensional arrays. What you are actually using is an array where each element happens to be a reference to another array.

    When dealing with a single array, Perl will expand it as needed, so there's rarely any need to pre-define its size. Because of autovivification, the same applies to "2d arrays".

    If you really did want to pre-allocate your "2d array", you could do it as follows:

    my @summary; $#summary = $numWks - 1; foreach (@summary) { my @days; $#days = 7 - 1; $_ = \@days; }

    However, I think a simple my @summary; will suffice for you.

      Hi ikegami,

      Thank you. After reading your post my decision is to not pre-allocate the array and I have decided on using an array of hashes.

      In the loop section where I am using the array and hash could you tell me if the hash 'dayHash' filled and pushed onto the @summary array for $wk=0 is different than the 'dayHash' pushed onto the @summary array for $wk=1? Said differently, am I saving a copy of the hash keys and values into the array or am I saving the same reference to the same hash in every element of the array and overwriting the hash values each time with the next week's order totals from %total_opd?

      Thanks.

      RF

      # create data for small summary table of all retailers my $numWks = (($num_days - ( 7 - $firstDOW_curMonth )) / 7 ) + 1; if ( (($num_days - ( 7 - $firstDOW_curMonth )) % 7 ) ) { $numWks++; } my %dayHash; # the hash my @summary; # the array of hashes # init the blank portion of $summary[][] if there is one #my $temp = 0; #while ( $temp < $firstDOW_curMonth ) { # $summary[0][$temp++] #} my $wktotal; my $count=1; # key into %total_opd, it is based on date (1-$numdays) my $firstDOW = $firstDOW_curMonth; # $wk is index for array for ( my $wk = 0; $wk < $numwks; $wk++ ) { $wktotal = 0; # the first time through $dw may be 0, 1, 2,...6 # $dw is the key of the hash for ( my $dw = $firstDOW; $dw < 7; $dw++ ) { $dayHash{$dw} = $total_opd{$count}; $wktotal += $total_opd{$count}; last if ($count++ == $num_days); } $firstDOW = 0; # next time through the $dw loop it will start a +t 0 $dayHash{7} = $wktotal; # save the accumulated total for the we +ek push @summary, $dayHash; # last if ($count > num_days); }

        I strongly recommend that you use strictures (use strict; use warnings;). That would pick up the use of $numwks and $numWks for example.

        It would also alert you to the use of the (presumably) undeclared $dayHash in push @summary, $dayHash; - although I suspect you really intended push @summary, \%dayHash;.

        Strictures would also alert you to the use of num_days in last if ( $count > num_days ); which seems pretty unlikely given you've used $num_days a few lines up

        In answer to your actual question: yes, you will be pushing copies of the reference. The easy fix is to make %dayHash (and $wktotal btw) local to the loop:

        for ( my $wk = 0 ; $wk < $numWks ; $wk++ ) { my $wktotal = 0; my %dayHash; ... }

        I notice that $wk is not used. You could just for (1 .. $numWks) { and if you do need it at some point you should for my $wk (1 .. $numWks) {.


        DWIM is Perl's answer to Gödel
        You should push an anonymous hashref to get a new copy:

        push @summary, { %dayhash };

        Cheers
        Chris

      perl -MData::Dumper -e"$a[1][2][3]=4;print Dumper\@a" $VAR1 = [ undef, [ undef, undef, [ undef, undef, undef, 4 ] ] ];
Re: pre-size a two-dimensional array
by wind (Priest) on Jul 11, 2007 at 22:07 UTC
Re: pre-size a two-dimensional array
by Cristoforo (Curate) on Jul 19, 2007 at 00:19 UTC
    Here is a solution using 2 helpful modules. Date::Simple takes care of all the date arithmetic and Text::Table formats the output (automatically sizing the columns to fit the data).
    Chris
    #!/usr/bin/perl use strict; use warnings; use Date::Simple 'ymd'; use Text::Table; #Set up an example hash of orders placed daily (for the month of July) my %total_opd; @total_opd{1..30} = (12..40, 1000); # Use July 2007 for this example my ($y, $m, $d) = (2007, 7, 1); my @summary = [qw/Sun Mon Tue Wed Thu Fri Sat Total/ ]; my ($row, $col) = 1; for (my $date = ymd($y, $m, $d); $date->month == $m; $date++) { $col = $date->day_of_week; # weekdays are 0 - 6 # if $total_opd{$date->day} not exists or is undefined this day, s +et to 0 $summary[$row][7] += $summary[$row][$col] = $total_opd{$date->day} + || 0; ++$row if $col==6; } my $num_weeks = @summary - 1; my @head = (" ", map "Wk" . $_, 1..$num_weeks); my @new = transpose(@summary); my $tb = Text::Table->new( map {title => $_, align_title => 'right'}, +@head)->load(@new); print $tb; sub transpose { my (@summary, @new) = @_; for my $r (0..$#summary) { for my $c (0..$#{ $summary[$r]}) { $new[$c][$r] = $summary[$r][$c]; } } return @new; } __END__ ***prints output Wk1 Wk2 Wk3 Wk4 Wk5 Sun 12 19 26 33 40 Mon 13 20 27 34 1000 Tue 14 21 28 35 0 Wed 15 22 29 36 Thu 16 23 30 37 Fri 17 24 31 38 Sat 18 25 32 39 Total 105 154 203 252 1040