Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Ways to group elements of an AoH

by bradcathey (Prior)
on Jan 04, 2009 at 02:30 UTC ( [id://733972]=perlquestion: print w/replies, xml ) Need Help??

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

Fellow Monasterians,

As a web developer I spend lots of time with HTML::Template and it's requirement for AoH in it's <tmpl_loop ...> function, which I use frequently. Many of the lists I loop in the browser need to be grouped first. It's great when I can use GROUP BY when querying the database, but often I don't have that luxury. My question is, what's the best way to group a list?

My example has several entries for a few users that I want to group, including sums of hours worked and a monetary extension.

my $allusers = [ { 'user' => 'Sarah', 'duration' => 2, 'amount' => 200 }, { 'user' => 'William', 'duration' => 1, 'amount' => 100 }, { 'user' => 'Michael', 'duration' => 3, 'amount' => 300 }, { 'user' => 'Michael', 'duration' => 3, 'amount' => 300 }, { 'user' => 'William', 'duration' => 7, 'amount' => 700 }, { 'user' => 'Sarah', 'duration' => 5, 'amount' => 500 } ];

My first attempt sorts first and then iterates over the list—not especially efficient and a little goofy with the -1 counter:

$allusers = [ sort { $a->{'user'} cmp $b->{'user'} } @$allusers ]; my ($temp, $grouped); my $ctr = -1; for ( @{ $allusers } ) { if ( $temp && $temp eq $_->{'user'} ) { $grouped->[$ctr]{'amount'} += $_->{'amount'}; $grouped->[$ctr]{'duration'} += $_->{'duration'}; } else { $ctr++; $temp = $grouped->[$ctr]{'user'} = $_->{'user'}; $grouped->[$ctr]{'amount'} = $_->{'amount'}; $grouped->[$ctr]{'duration'} = $_->{'duration'}; } }

I reworked it to eliminate the sort and cut the processing time basically in half:

my $allkeys; for ( @{ $allusers } ) { my $user = $_->{'user'}; if ( !exists $allkeys->{$user} ) { my $attributes = { 'amount' => $_->{'amount'}, 'duration' => $_->{'duration'} }; $allkeys->{$user} = $attributes; } else { my $refAttributes = $allkeys->{$user}; $refAttributes->{'amount'} += $_->{'amount'}; $refAttributes->{'duration'} += $_->{'duration'}; } } my $finalArray; while ((my $key, my $value) = each %{$allkeys}) { my $hash = { 'user' => $key, 'amount' => $value->{'amount'}, 'duration' => $value->{'duration'} }; push @{$finalArray}, $hash; }

OUTPUT for both methods are the same:

print Dumper ($finalArray); $VAR1 = [ { 'amount' => 600, 'user' => 'Michael', 'duration' => 6 }, { 'amount' => 800, 'user' => 'William', 'duration' => 8 }, { 'amount' => 700, 'user' => 'Sarah', 'duration' => 7 } ];

But even the second way seems a bit tedious with that second while loop to build the final array. Is there a better way?

—Brad
"The important work of moving the world forward does not wait to be done by perfect men." George Eliot

Replies are listed 'Best First'.
Re: Ways to group elements of an AoH
by kyle (Abbot) on Jan 04, 2009 at 03:01 UTC

    Put all the data you'll need in $allkeys and then just pull it all out with values.

    my $allkeys; for ( @{ $allusers } ) { my $user = $_->{'user'}; if ( !exists $allkeys->{$user} ) { $allkeys->{$user} = { %{ $_ } }; } else { my $refAttributes = $allkeys->{$user}; $refAttributes->{'amount'} += $_->{'amount'}; $refAttributes->{'duration'} += $_->{'duration'}; } } my $finalArray = [ values %{ $allkeys } ];

      Sweet! So much cleaner. Thanks.

      —Brad
      "The important work of moving the world forward does not wait to be done by perfect men." George Eliot
Re: Ways to group elements of an AoH
by GrandFather (Saint) on Jan 04, 2009 at 03:05 UTC

    Using an explicit intermediate hash cleans up the code somewhat (for some definition of clean):

    use strict; use warnings; use Data::Dump::Streamer; my $allusers = [ {...} ]; # Data per sample my %users; for my $user (@$allusers) { my $name = $user->{user}; $users{$name}{$_} += $user->{$_} for qw(duration amount); } @$allusers = map { {user => $_, duration => $users{$_}{duration}, amount => $users{$_ +}{amount}} } sort keys %users; Dump $allusers;

    Prints:

    $ARRAY1 = [ { amount => 600, duration => 6, user => 'Michael' }, { amount => 700, duration => 7, user => 'Sarah' }, { amount => 800, duration => 8, user => 'William' } ];

    Perl's payment curve coincides with its learning curve.
Re: Ways to group elements of an AoH
by fmerges (Chaplain) on Jan 04, 2009 at 21:40 UTC

    Hi,

    I would recommend you to use also another template system, aside of HTML::Template module for certain situations, as the transformations you need to do most times outweight the benefits of using HTML::Template, creating a code which is not so maintainable, and more centered to overcome the limitations of the module.

    Update: seems to be that people are misunderstanding what I'm trying to say which is that for more complex structures than simple AoH, you end up transforming the data to make it work. I'm not saying in any means that the module is not nice and very useful, which is not the case. I also do use it a lot.

    Regards,

    fmerges at irc.freenode.net

      Hmmmm, strong words for one of the more revered CPAN modules, and you offer no examples or recommendations, just generalizations.

      —Brad
      "The important work of moving the world forward does not wait to be done by perfect men." George Eliot

        Hi,

        If you read carefully I say also. Thinking these are strong words is misinterpreting what I'm saying.

        Just read the way the loop works in HTML::Template, and then think about a structure which is not a AoH, tip what about iterating through a regular array?

        # given my @array = qw(apple orange peach); # now you have to change this to my @new_arr = map { { name => $_ } } @array; # and now you can say $template->param(fruits => \@new_arr);

        You want valid alternatives, check out HTML::Mason, Template::Toolkit. Yes one is not a pure templating and the other one is heavy, that's why I say also.

        Update: modified the example code to show more clearly the transformation required.

        Regards,

        fmerges at irc.freenode.net

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://733972]
Approved by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (3)
As of 2024-03-29 07:26 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found