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

I've been delving into referencing recently (inspired by HTML::Template), and would very much like some advice.

From Sam Tregars' excellent perldoc HTML::Template documentation describing how to create a <TMPL_LOOP> referenced list:

my @words = qw(I Am Cool); my @numbers = qw(1 2 3); my @loop_data = (); # initialize an array to hold your loop while (@words and @numbers) { my %row_data; # get a fresh hash for the row data # fill in this row $row_data{WORD} = shift @words; $row_data{NUMBER} = shift @numbers; # the crucial step - push a reference to this row into the loop! push(@loop_data, \%row_data); } # finally, assign the loop data to the loop param, again with a ref $template->param(THIS_LOOP => \@loop_data);

All well and good, but is there a better or shorter, way to do it? Now, if you're extracting the data from a MySQL table everything can be simplified (via fetchrow_hashref) and by creating a sub (not necessary but useful if extracting data for several loops).

sub getLoopData { my $table = shift; my $order = shift; my @fields = @_; my $sql = "SELECT " . join (', ', @fields) . " from $table ORDER B +Y $order"; my $sth = $dbh->prepare_cached($sql); $sth->execute(); my @rows; while (my $row = $sth->fetchrow_hashref) { push (@rows, $row); } return \@rows; }

Which can be called by something like:

my $template->param(foo_loop => &getLoopData('foo', 'bar', 'baz', 'm +oo'));

Nice. MySQL's fetchrow_hashref function takes care of the referencing for you. I was wondering if theres a nice Perl (golfish) way of doing the same. Sams sample used two arrays, but, what if there was only one, heres a working example:

my @years = (); my @years_loop = (); my $curr_year = (localtime)[5]; $curr_year += 1900; for ($curr_year - 100 .. $curr_year - 5) { push (@years, $_); } while (@years) { my %year_data; $year_data{year} = shift @years; push (@years_loop, \%year_data); } $template->param(years_loop => \@years_loop);

Are all the steps within the while loop to get a referenced @years_loop array really necessary? ... well, they are necceasry in this instance otherwise it wouldn't work, but, as asked before, can anyone suggest a better, shorter (or even 'different', in the spirit of learning) way to do it?

This question is asked in the hope that I will get to understand Perl a little better. As can probably be extrapolated from this node I don't really know what I'm talking about so if anyone could throw me some pointers I'd be very grateful. (/me cowers in corner awaiting a thrashing ;)

Replies are listed 'Best First'.
(jeffa) Re: A quesion about referencing lists
by jeffa (Bishop) on Jul 19, 2003 at 15:29 UTC
    Here is a rewrite of your first snippet that uses map:
    # assumption: @words == @numbers use Data::Dumper; my @words = qw(I Am Cool); my @numbers = qw(1 2 3); my @loop_data = map { { WORD => $words[$_], NUMBER => $numbers[$_], } } 0..$#words; print Dumper \@loop_data;
    A while back i broke down and wrote a sub that transforms database results into an HTML::Template-friendly data structure:
    sub db2tmpl { my ($sth,@bind) = @_; $sth->execute(@bind); my $fields = $sth->{NAME}; my $rows = $sth->fetchall_arrayref; my @loh; for my $row (@$rows) { my %hash; @hash{@$fields} = @$row; push @loh, {%hash}; } return \@loh; }
    I probably should have fetched a hash, but i didn't. Anyway, you can call it like so:
    my $sth = $dbh->prepare('select * from student'); my $students = db2tmpl($sth);
    I am sure that i saw a CPAN module that did something very similar. I can't seem to find it now (slipped away), so if anyone knows what i am talking about ... please tell me. :) Hope this helps.

    UPDATE:
    gmax reminded me that this is overkill ... just call $sth->fetchall_arrayref({}) (notice the anonymous hash ref as the arg).

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
Re: A quesion about referencing lists
by Zaxo (Archbishop) on Jul 19, 2003 at 15:30 UTC

    Constructions like that can often be simplified by slices or by map. In the case of @years and @years_loop in the last example, the for(){push} construction is unnecessary. You already have a list of that data in the parens - just assign it directly. @years_loop is then an easy map,

    my $curr_year = (localtime)[5] + 1900; my @years = $cur_year - 100 .. $cur_year - 5; my @years_loop = map { { year => $_ } } @years; $template->param(years_loop => \@years_loop);
    The inner pair of curlies in the map produce a reference to an anonymous hash without needing to give it any temporary name.

    After Compline,
    Zaxo

Re: A quesion about referencing lists
by Mr. Muskrat (Canon) on Jul 19, 2003 at 15:25 UTC

    I would think that the following would do the same as your last snippet. Not to mention that it should use less memory.

    my $curr_year = (localtime)[5]; $curr_year += 1900; for ($curr_year - 100 .. $curr_year - 5) { push @years_loop, { year => $_ }; } $template->param(years_loop => \@years_loop);

Re: A quesion about referencing lists
by barrd (Canon) on Jul 19, 2003 at 15:51 UTC
    Thank you very much to Mr Muskrat, jeffa & Zaxo, very very helpful indeed. T'was exactly what I was looking for.

    barrd scuttles off to "play" with some new ideas for his poor code.

      You cannot reference a list, you're talking about arrays.