cooldood3490
cooldood3490

Reputation: 2488

How to sum multiple arrays element-wise in Perl?

There is a question very similar to this already but I would like to do this for multiple arrays. I have an array of arrays.

my @AoA = (
    $arr1 = [ 1, 0, 0, 0, 1 ],
    $arr2 = [ 1, 1, 0, 1, 1 ],
    $arr3 = [ 2, 0, 2, 1, 0 ]
);

I want to sum the items of all the three (or more) arrays to get a new one like

( 4, 1, 2, 2, 2 )

The use List::MoreUtils qw/pairwise/ requires two array arguments.

@new_array = pairwise { $a + $b } @$arr1, @$arr2;

One solution that comes to mind is to loop through @AoA and pass the first two arrays into the pairwise function. In the subsequent iterations, I will pass the next @$arr in @AoA and the @new_array into the pairwise function. In the case of an odd sized array of arrays, after I've passed in the last @$arr in @AoA, I will pass in an equal sized array with elements of 0's.

Is this a good approach? And if so, how do I implement this? thanks

Upvotes: 1

Views: 2795

Answers (4)

Joel Berger
Joel Berger

Reputation: 20280

You might actually be looking for PDL, the Perl Data Language. It is a numerical array module for Perl. It has many functions for processing arrays of data. Unlike other numerical array modules for other languages it has this handy ability to use its functionality on arbitrary dimensions and it will do what you mean. Note that this is all done at the C level, so it is efficient and fast!

In your case you are looking for the projection method sumover which will take an N dimensional object and return an N-1 dimensional object created by summing over the first dimension. Since in your system you want to sum over the second we first have to transpose by exchanging dimensions 0 and 1.

#!/usr/bin/env perl

use strict;
use warnings;

use PDL;

my @AoA = (
    [ 1, 0, 0, 0, 1 ],
    [ 1, 1, 0, 1, 1 ],
    [ 2, 0, 2, 1, 0 ],
);

my $pdl = pdl \@AoA;

my $sum = $pdl->xchg(0,1)->sumover;
print $sum . "\n"; 
# [4 1 2 2 2]

The return from sumover is another PDL object, if you need a Perl list you can use list

print "$_\n" for $sum->list;

Upvotes: 4

amon
amon

Reputation: 57590

You can easily implement a “n-wise” function:

sub nwise (&@) # ← take a code block, and any number of further arguments
{
  my ($code, @arefs) = @_;
  return map {$code->( do{ my $i = $_; map $arefs[$_][$i], 0 .. $#arefs } )}
             0 .. $#{$arefs[0]};
}

That code is a bit ugly because Perl does not support slices of multidimensional arrays. Instead I use nested maps.

A quick test:

use Test::More;
my @a = (1, 0, 0, 0, 1);
my @b = (1, 1, 0, 1, 1);
my @c = (2, 0, 2, 1, 0);
is_deeply [ nwise { $_[0] + $_[1] + $_[2] } \@a, \@b, \@c], [4, 1, 2, 2, 2];

I prefer passing the arrays as references instead of using the \@ or + prototype: This allows you to do

my @arrays = (\@a, \@b, \@c);
nwise {...} @arrays;

From List::MoreUtils you could have also used each_arrayref:

use List::Util qw/sum/;
use List::MoreUtils qw/each_arrayref/;
my $iter = each_arrayref @arrays;
my @out;
while (my @vals = $iter->()) {
  push @out, sum @vals;
}
is_deeply \@out, [4, 1, 2, 2, 2];

Or just plain old loops:

my @out;
for my $i (0 .. $#a) {
  my $accumulator = 0;
  for my $array (@arrays) {
    $accumulator += $array->[$i];
  }
  push @out, $accumulator;
}
is_deeply \@out, [4, 1, 2, 2, 2];

The above all assumed that all arrays were of the same length.


A note on your snippet:

Your example of the array structure is of course legal perl, which will even run as intended, but it would be best to leave out the inner assignments:

my @AoA = (
    [ 1, 0, 0, 0, 1 ],
    [ 1, 1, 0, 1, 1 ],
    [ 2, 0, 2, 1, 0 ],
);

Upvotes: 4

bigiain
bigiain

Reputation: 809

Assumptions:

  • each row in your AoA will have the same number of columns as the first row.
  • each value in the arrayrefs will be a number (specifically, a value in a format that "works" with the += operator)
  • there will be at least one "row" with sat least one "column"

Note: "$#{$AoA[0]}" means, "the index of the last element ($#) of the array that is the first arrayref in @AoA ({$AoA[0]})"

(shebang)/usr/bin/perl
use strict;
use warnings;

my @AoA = (
    [ 1, 0, 0, 0, 1 ],
    [ 1, 1, 0, 1, 1 ],
    [ 2, 0, 2, 1, 0 ]
);

my @sums;

foreach my $column (0..$#{$AoA[0]}) {
  my $sum;
  foreach my $aref (@AoA){
    $sum += $aref->[$column];
  }
  push @sums,$sum;
}

use Data::Dumper;
print Dumper \@sums;

Upvotes: 2

patrickmdnet
patrickmdnet

Reputation: 3392

Here's a simple iterative approach. It probably will perform terribly for large data sets. If you want a better performing solution you will probably need to change the data structure, or look on CPAN for one of the statistical packages. The below assumes that all arrays are the same size as the first array.

$sum = 0;
@rv = ();
for ($y=0; $y < scalar @{$AoA[0]}; $y++) {
    for ($x=0; $x < scalar @AoA; $x++) {
        $sum += ${$AoA[$x]}[$y];
    }
    push @rv, $sum;
    $sum = 0;
}

print '('.join(',',@rv).")\n";

Upvotes: 2

Related Questions