1

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

4 Answers 4

4

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 ],
);
Sign up to request clarification or add additional context in comments.

2 Comments

Even though difficult to read, your explanation on how to extend the perl language via code calls is commendable. In addition, the use of Test::More as part of the demo is something that everyone in every language should work towards, and is the perfect way to write a demo of what you wrote. Kudos.
BTW, PDL does support very fancy slices :-)
4

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;

3 Comments

what if I wanted to return an array? would it be $sum->array?
you would just store the list as an array. my @array = $sum->list.
note that to generalize list to higher dimensions, in the very newest PDL (2.006) we have introduced the unpdl method which attempts to preserve dimensionality as arrayref of arrayrefs: my $arrayref = $pdl->unpdl;
2

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";

2 Comments

"It probably will perform terribly for large data sets" - That's inevitably true of the problem, not the simple iterative solution. You need to "visit" every value in the 2D array to sum them, so you need at least X x Y additions. (If the data set gets to the size where the AoA doesn't fit in memory, you'd need to know a lot more about how you're string and retrieving them to optimize.)
especially if large datasets are a concern, see my answer for a good solution (also see 'in-place' PDL operations if you find yourself close to your ram limit to avoid any copying). PDL is a really thin wrapper around C and great for this stuff.
2

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;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.