Pathologically Eclectic Rubbish Lister PerlMonks

### Summing Up Array Elements By Group

by neversaint (Deacon)
 on Jan 21, 2009 at 07:18 UTC Need Help??

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

Dear Masters,
I have arrays of which the sizes are always multiple of 3, e.g:
```my @arr = (0,0,1, 3,2,0, 1,1,1);
Now what I want to do is to sum the elements in consecutive group of three, returning:
```my @output = (1, 5, 3);
Is there a way to achieve that efficiently?

---
neversaint and everlastingly indebted.......

Replies are listed 'Best First'.
Re: Summing Up Array Elements By Group
by ivancho (Hermit) on Jan 21, 2009 at 07:30 UTC
Is
```\$output[int(\$_/3)] += \$arr[\$_] for 0..@arr-1;
efficient enough, or is there something I am missing? Too many "int()"s? My guess is that
```my \$k = @arr / 3;
for (0..\$k) {
\$output[\$_] += \$arr[3*\$_] + \$arr[3*\$_+1] + \$arr[3*\$_ +2];
}
is about as efficient as you'd get.
Edit
Actually, it seems to me that
```my \$k = @arr / 3;
my \$b = 0;
for (0..\$k) {
\$output[\$_] = \$arr[\$b] + \$arr[\$b+1] + \$arr[\$b +2];
\$b += 3;
}
is probably a tad faster. Although if your problem really is as big as to make a difference between those 2 versions, then you shouldn't really be using Perl.
Even faster:
```tin4=> sub {
my \$b = 0;
\$output[\$_] = \$arr[\$b++] + \$arr[\$b++] + \$arr[\$b++]
for 0..@arr/3;
},
tin5=> sub {
my \$b = -1;
\$output[\$_] = \$arr[++\$b] + \$arr[++\$b] + \$arr[++\$b]
for 0..@arr/3;
},
```       Rate tin1 tin2 tin3 tin4 tin5
tin1 10.2/s   -- -54% -59% -65% -68%
tin2 22.2/s 118%   --  -9% -23% -30%
tin3 24.5/s 141%  10%   -- -15% -23%
tin4 28.8/s 183%  30%  17%   --  -9%
tin5 31.7/s 211%  43%  29%  10%   --
ikegami, this one knocks me down! Did you know in advance that ++\$b would be faster than \$b++? Do you know why that is?
Nice. The int is not necessary:
```\$output[\$_/3] += \$arr[\$_] for 0 .. \$#arr;
The first one you wrote was the fastest:
```Benchmark: timing 1000000 iterations of tin1, tin2, tin3...
tin1:  1 wallclock secs ( 0.59 usr +  0.00 sys =  0.59 CPU) @ 16
+86340.64/s (n=1000000)
tin2:  1 wallclock secs ( 0.75 usr +  0.00 sys =  0.75 CPU) @ 13
+33333.33/s (n=1000000)
tin3:  3 wallclock secs ( 2.22 usr +  0.00 sys =  2.22 CPU) @ 45
+0450.45/s (n=1000000)
Rate tin3 tin2 tin1
tin3  450450/s   -- -66% -73%
tin2 1333333/s 196%   -- -21%
tin1 1686341/s 274%  26%   --
the input to get this was:
```\$results = timethese(1000000,{
tin1 => '\$output[int(\$_/3)] += \$arr[\$_] for 0..@arr-1;',
tin2 =>'\$out[\$_] = (\$arr[(3*\$_)]+\$arr[(3*\$_+1)]+\$arr[(3*\$_+2)]) for 0.
+.(scalar(@arr)/3 - 1);',
tin3=>'for (0..\$k) {
\$output[\$_] = \$arr[\$b] + \$arr[\$b+1] + \$arr[\$b +2];
\$b += 3;
}
'});
Benchmark::cmpthese( \$results );
Raghu
clearly the massive differences between tin2 and tin3 must at least alert you that something is wrong with the benchmark... Frankly I have no idea what goes on once you start eval-ing strings in the Benchmark module, but from the presented examples, something seems suspicious.

Here is a benchmark by me

```use Benchmark qw(cmpthese);

my @arr = map {rand} 0..900000;
my @output = map {rand} 0..300000;;

Benchmark::cmpthese(10,{
tin1 => sub {
\$output[int(\$_/3)] += \$arr[\$_] for 0..@arr-1;
},
tin2 => sub {
\$output[\$_] =  \$arr[3*\$_] + \$arr[3*\$_+1] + \$arr[3*\$_+2]
for 0..@arr/3;
},
tin3=> sub {
my \$b = 0;
for (0..@arr /3) {
\$output[\$_] = \$arr[\$b] + \$arr[\$b+1] + \$arr[\$b +2];
\$b += 3;
}
}
});
With result
```\$ perl ../../../perlmonks/bench-trip.pl
Rate tin1 tin2 tin3
tin1 1.81/s   -- -41% -46%
tin2 3.08/s  70%   --  -9%
tin3 3.39/s  87%  10%   --
Re: Summing Up Array Elements By Group
by moritz (Cardinal) on Jan 21, 2009 at 07:42 UTC
Since you've already got a good Perl 5 answer, here's a Perl 6 answer (that works with present day Rakudo):
```my @arr = (0,0,1, 3,2,0, 1,1,1);
my @output;
for @arr -> \$a, \$b, \$c {
@output.push: \$a + \$b + \$c;
}

say @output.perl;
# output: [1, 5, 3]
Or a bit more idiomatic:
```my @arr = (0,0,1, 3,2,0, 1,1,1);
my @output = gather {
for @arr -> \$a, \$b, \$c {
take \$a + \$b + \$c;
}
}

(This one would even be lazy if Rakudo did laziness yet).

Re: Summing Up Array Elements By Group
by fullermd (Priest) on Jan 21, 2009 at 08:51 UTC

For another alternate approach, first roll up the groups of 3 into array refs, then sum them up:

```# Initial values
my @arr = (1,2,3,  4,5,6,  7,8,9);

# Replace with a set of array refs to 3-element arrays
my \$nblk = @arr / 3;
my @a2;
push @a2, [@arr[\$_*3, \$_*3+1, \$_*3+2]] for (0..\$nblk-1);

# Sum up each array ref
use List::Util qw(sum);
my @a3 = map { sum @\$_ } @a2;

Thus we end up with:

```@a3 = ('6', '15', '24');

By itself, it's a pretty roundabout version of what ivancho did above. But if you can use the grouping in other places in the program, it can be useful to group them up like that early on.

Of course, if you don't, you skip the extra step and it collapses down to

```use List::Util qw(sum);
push @a3, sum @arr[\$_*3, \$_*3+1, \$_*3+2] for (0..\$nblk-1);

which is just another way of writing ivancho's second version.

Re: Summing Up Array Elements By Group
by jwkrahn (Monsignor) on Jan 21, 2009 at 17:04 UTC

Another way to do it:

```\$ perl -le'
use List::Util qw/sum/;
use List::MoreUtils qw/natatime/;

my @arr = (0,0,1, 3,2,0, 1,1,1);
my \$it = natatime 3, @arr;
my @output;
while ( my @vals = \$it->() ) {
push @output, sum @vals;
}

print "@output";
'
1 5 3

Re: Summing Up Array Elements By Group
by targetsmart (Curate) on Jan 21, 2009 at 14:49 UTC
I might not be effective!, but a solution here.
```my @arr = (0,0,1, 3,2,0, 1,1,1, 11,100,12);
push (@Res, eval join("+",splice(@arr,0,3))) while (@arr/3);
print "@Res";

eval EXPR? oh my! No, use sum!

```use List::Util qw( sum );
push @Res, sum splice(@arr,0,3) while @arr;
Thank you, ikegami, but one more question on this,
I have seen the Util.pm code and I included only the reduce and sum part into the below code, now the code looks like this.
```sub reduce (&@) {
my \$code = shift;
no strict 'refs';

return shift unless @_ > 1;

use vars qw(\$a \$b);

my \$caller = caller;
local(*{\$caller."::a"}) = \my \$a;
local(*{\$caller."::b"}) = \my \$b;

\$a = shift;
foreach (@_) {
\$b = \$_;
print ">\$code<\n";
\$a = &{\$code}();
}

\$a;
}
use vars qw(\$a \$b);
sub sum (@) { reduce { \$a + \$b } @_ }
@arr = (0,0,1, 3,2,0, 1,1,1, 11,100,12);
push @Res, sum splice(@arr,0,3) while @arr;
print "@Res\n";

Now I see the magic part is '&{\$code}()', could you please explain me about this magic part and tell me how this is better than eval?.

Create A New User
Node Status?
node history
Node Type: perlquestion [id://737761]
Approved by moritz
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (7)
As of 2020-04-03 11:41 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
The most amusing oxymoron is:

Results (27 votes). Check out past polls.

Notices?