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

I am trying to take an array of hashes and based on certain values within the hash add elements of the array together and then shrink the array. Here is what I have so far:
sub fold { my $self = shift; return if ! @{$self->{data}}; my $array = $self->{data}; my $date_grouped = 0; my $campaign_grouped = 0; foreach(@{$self->{groups}}) { $date_grouped = 1 if $_ eq 'date'; $campaign_grouped = 1 if $_ eq 'campaign'; } my $merge_index = 0; my $points_needed = $campaign_grouped + $date_grouped; @$array = sort {fold_sort($campaign_grouped,$date_grouped)} @$arra +y; for(my $current_index=0; $current_index<@$array; $current_index++) + { my $el = $array->[$current_index]; if($merge_index == $current_index) { next; } #compare current element to merge element my $match = 0; my $merge = $array->[$merge_index]; $match++ if $date_grouped && $el->{date} eq $merge->{date}; $match++ if $campaign_grouped && $el->{campaign_id} == $merge->{campaign_id}; #if we can fold the array elements then do so and remove the #current element if($match == $points_needed) { $merge->{views} += $el->{views}; $merge->{clicks} += $el->{clicks}; my $del = splice(@$array,$current_index,1); $current_index--; } else { $merge_index++; } } }
Anyone have a better way of doing this? The fold_sort function just orders the array based on how were are grouping the elements. Thanks, Kenny

PS - I was not sure of what to call this so I figured "Array Folding" was a good enough term...if there is another term that people use let me know please :-)

Replies are listed 'Best First'.
Re: Array Folding
by davidrw (Prior) on Mar 05, 2006 at 00:54 UTC
    Re-reading OP again, i think i understood this time ... I believe (untested) that this does the same task:
    sub fold { my $self = shift; my $array = $self->{data}; return unless $array && @$array; my %groups = map { $_ => 1 } @{$self->{groups}; my @keys = grep { $groups{$_} } qw/ date campaign /; my %h; foreach my $el ( @$array ){ my $k = join ":", @{$_}{@keys}; # This only works if the +date & campaign values do not contain ':' if( exists $h{$k} ) { $h{$k}->{views} += $el->{views}; $h{$k}->{clicks} += $el->{clicks}; }else{ $h{$k} = $el; } } @$array = sort { fold_sort($groups{campaign},$groups{date}) } values + %h; }
    Note that the basic approach is to hash up on the commonality (date and/or campaign) to combine everything, then take those values and sort for final result.
Re: Array Folding
by borisz (Canon) on Mar 05, 2006 at 01:04 UTC
    Here is my try: untested. propably you need to sort the keys for your output. And the keys in groups must match the data keys. say date and campain_id in your example.
    sub fold { my $self = shift; my %h; my @groups = @{ $self->{groups} }; for ( @{ $self->{data} } ) { push @{ $h{ join $;, @$_{ @groups } } }, $_; } $self->{data} = [ map { my $x; @$x{ @groups } = @{ $h{$_}->[0] }{ @groups }; for ( @{ $h{$_} } ) { $x->{views} += $_->{views}; $x->{clicks} += $_->{clicks}; } $x; } keys %h ]; }
    Boris
      Thanks everyone for the feedback :-)
Re: Array Folding
by davidrw (Prior) on Mar 05, 2006 at 00:35 UTC
    Can you show the source for fold_sort()? It's not clear to me why/who this line works, since the sort block isn't dependent upon $a or $b at all (in any obvious way)..
    @$array = sort {fold_sort($campaign_grouped,$date_grouped)} @$arra +y;
    Can you also provide some data samples? Input, actual output, and desired output?