Array Folding
BarMeister
created: 2006-03-04 18:07:01
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)} @$array;
	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
		#look ahead 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 :-)

Re: Array Folding
created: 2006-03-04 19:35:19
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)} @$array;
Can you also provide some data samples? Input, actual output, and desired output?
Re: Array Folding
created: 2006-03-04 19:54:25
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
created: 2006-03-04 20:04:05
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;
  return if !@{ $self->{data} };
  my %h;
  for ( @{ $self->{data} } ) {
    push @{ $h{ join $;, @$_{ @{ $self->{groups} } } } }, $_;
  }

  $self->{data} = [
    map {
      my $x = {
                date        => $h{$_}->[0]->{date},
                campaign_id => $h{$_}->[0]->{campain_id},
      };
      for ( @{ $h{$_} } ) {
        $x->{views}  += $_->{views};
        $x->{clicks} += $_->{clicks};
      }
      $x;
      } keys %h
  ];
}
Boris
Re^2: Array Folding
created: 2006-03-07 09:53:41
Thanks everyone for the feedback :-)

perlmonks.org content © perlmonks.org and BarMeister, borisz, davidrw

prlmnks.org © 2006 edmund von der burg (eccles & toad)

v 0.03