Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Summing Up Array Elements By Group

by neversaint (Deacon)
on Jan 21, 2009 at 07:18 UTC ( #737761=perlquestion: print w/replies, xml ) 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?.

Log In?
Username:
Password:

What's my password?
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?