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

I needed to multiply together the numbers in an array the other day, and I couldn't figure out any neater way to do it than this:
@numbers = (2,3,4,5); $total = 1; for(@numbers){ $total = ($total * $_); } print $total;

Is there a smarter way? It kind of bugged me that I need to use that variable and do one meaningless multiplication.



($_='kkvvttuubbooppuuiiffssqqffssmmiibbddllffss')
=~y~b-v~a-z~s; print

Replies are listed 'Best First'.
Re: Multiplying together the numers in an array
by BrowserUk (Patriarch) on Apr 09, 2004 at 01:07 UTC

    use List::Util qw[ reduce ]; my @a = 2..6; print reduce{ $a*=$b } @a; 720

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
      Is that *= intended as an optimization? I'd expect just *.

        It wasn't intended at all. I started out with the postfix for loop and then though, reduce would avoid the temp var and modified the line, leaving the *= behind.

        That said, it does appear to run approximately 20% quicker which might come in handy some day.


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "Think for yourself!" - Abigail

        To be completely honest, *= is just a short hand for

        $total = $total * $_;
        It is a holdover from C and is actually pretty useful as shorthand.

Re: Multiplying together the numers in an array
by tilly (Archbishop) on Apr 09, 2004 at 02:31 UTC
    Here is a stupid but concise way for your amusement:
    print eval join "*", @numbers;

      And you can also lose precision by stringifying the numbers:

      @a= (1/3, 3); $prod1= eval join "*", @a; $prod2= 1; $prod2*= $_ for @a; print $prod1,$/, $prod2,$/;
      outputs
      0.999999999999999 1

      I'm not trying to attack you, but this can be a trap at other times when you store floating-point numbers as text. I learned in when I wrote the obfu Fun with duff's device and AUTOLOAD.

      Also look at what happens if the numbers are object with stringification. Look:

      use Math::Complex; @a= (1+i, 2); $prod1= eval join "*", @a; $prod2= 1; + $prod2*= $_ for @a; print $prod1,$/, $prod2,$/;
      output is:
      1+2i 2+2i

      And that will fail if some of the numbers is a NaN or Infinity.

        I said it was stupid.
Re: Multiplying together the numers in an array
by davido (Cardinal) on Apr 09, 2004 at 01:08 UTC
    Your method is fine. Here's another solution which is quite similar to yours, except that it use a "modifier" type for loop and the *= operator.

    my @numbers = (2, 3, 4, 5); my $total=1; $total *= $_ for @numbers; print "$total\n";

    Dave

Re: Multiplying together the numers in an array
by tachyon (Chancellor) on Apr 09, 2004 at 04:18 UTC

    Longer but quite possibly faster if it matters....

    #!/usr/bin/perl use Inline 'C'; @ary = 1..10; print multiply( \@ary ); __DATA__ __C__ double multiply (SV * terms) { I32 numterms = 0; double res = 1.0; int i; if ((!SvROK(terms)) || (SvTYPE(SvRV(terms)) != SVt_PVAV) || ((numterms = av_len((AV *)SvRV(terms))) < 0)) { return 0; } for (i = 0; i <= numterms; i++) { res *= SvNV(* av_fetch((AV *)SvRV(terms), i, 0)); } return res; }

    cheers

    tachyon

Re: Multiplying together the numers in an array
by eXile (Priest) on Apr 09, 2004 at 06:26 UTC
    Nice post to get myself some practice in benchmarking. Here's my try:
    #!/usr/bin/perl use Benchmark qw(cmpthese); use List::Util qw(reduce); use Inline 'C'; cmpthese (-5, { 'initial' => sub { my @numbers = (2,3,4,5); my $total = 1; for(@numbers){ $total = ($total * $_); } }, 'listutil' => sub { my @numbers = (2,3,4,5); reduce{ $a*=$b } @numbers; }, 'eval' => sub { my @numbers = (2,3,4,5); eval join "*", @numbers; }, '*=' => sub { my @numbers = (2,3,4,5); my $total=1; $total *= $_ for @numbers; }, 'reducing' => sub { my @numbers = (2,3,4,5); $numbers[0] *= pop @numbers while ( @numbers > 1 ); }, 'inline' => sub { my @numbers = (2,3,4,5); multiply( \@numbers ); } }); __DATA__ __C__ double multiply (SV * terms) { I32 numterms = 0; double res = 1.0; int i; if ((!SvROK(terms)) || (SvTYPE(SvRV(terms)) != SVt_PVAV) || ((numterms = av_len((AV *)SvRV(terms))) < 0)) { return 0; } for (i = 0; i <= numterms; i++) { res *= SvNV(* av_fetch((AV *)SvRV(terms), i, 0)); } return res; }
    the results:
    Rate eval reducing initial *= listutil inline eval 21683/s -- -84% -84% -85% -85% -89% reducing 134819/s 522% -- -4% -6% -7% -31% initial 139854/s 545% 4% -- -3% -4% -28% *= 144019/s 564% 7% 3% -- -1% -26% listutil 145100/s 569% 8% 4% 1% -- -25% inline 194405/s 797% 44% 39% 35% 34% --
    If I'm doing something wrong here, please correct me.
      FWIW, here are a couple of variations on *= :
      #!/usr/bin/perl -w use strict; use Benchmark qw(cmpthese); cmpthese( -5, { '*=' => sub { my @numbers = (2, 3, 4, 5); my $total = 1; $total *= $_ for @numbers; }, 'pop' => sub { my @numbers = (2, 3, 4, 5); my $total = pop @numbers; $total *= $_ for @numbers; }, 'shift' => sub { my @numbers = (2, 3, 4, 5); my $total = shift @numbers; $total *= $_ for @numbers; } } );
                Rate    *=   pop shift
      *=    167004/s    --   -7%  -12%
      pop   180190/s    8%    --   -5%
      shift 188837/s   13%    5%    --
      
      

      This is a bit odd. I ran the sames tests because i wanted to see how a map solution measured up.

      #!/usr/bin/perl use Benchmark qw(cmpthese); use List::Util qw(reduce); cmpthese (-5, { 'initial' => sub { my @numbers = (2,3,4,5); my $total = 1; for(@numbers){ $total = ($total * $_); } }, 'listutil' => sub { my @numbers = (2,3,4,5); reduce{ $a*=$b } @numbers; }, 'eval' => sub { my @numbers = (2,3,4,5); eval join "*", @numbers; }, '*=' => sub { my @numbers = (2,3,4,5); my $total=1; $total *= $_ for @numbers; }, 'map' => sub { my @numbers = (2,3,4,5); my $total=1; map { $total *= $_ } @numbers; }, 'reducing' => sub { my @numbers = (2,3,4,5); $numbers[0] *= pop @numbers while ( @numbers > 1 ); }, });

      And got the followin results on two runs.

      C:\test>perl reduce.pl Rate eval listutil initial *= map reducin +g eval 19089/s -- -86% -90% -91% -93% -93 +% listutil 136513/s 615% -- -27% -39% -47% -51 +% initial 187039/s 880% 37% -- -16% -27% -33 +% *= 223842/s 1073% 64% 20% -- -13% -20 +% map 256001/s 1241% 88% 37% 14% -- -8 +% reducing 278189/s 1357% 104% 49% 24% 9% - +- C:\test>perl reduce.pl Rate eval listutil initial *= reducing ma +p eval 19656/s -- -87% -92% -92% -93% -94 +% listutil 150598/s 666% -- -35% -39% -47% -51 +% initial 232290/s 1082% 54% -- -6% -19% -24 +% *= 247322/s 1158% 64% 6% -- -14% -20 +% reducing 286024/s 1355% 90% 23% 16% -- -7 +% map 307455/s 1464% 104% 32% 24% 7% - +- C:\test>

      It greatly surpised me how much slower the listutil function was on my machine. Are there different implementations of it for different platforms that would cause this? Or is the small number of array elements makeing everything else meaningless?


      ___________
      Eric Hodges

        Are there different implementations of it for different platforms that would cause this?

        List::Util is implemented in two ways. One XS and one pure Perl, so it depends on your installation.

        ihb

Re: Multiplying together the numers in an array
by Zaxo (Archbishop) on Apr 09, 2004 at 01:15 UTC

    That's pretty much how it's done. It can look like less work with *= and modifier notation, but that's just sugar,

    my @numbers = (2,3,4,5); my $product = 1; $product *= $_ for @numbers; print $product;
    It's possible to do an elegant recursive implementation, but it costs in performance.

    After Compline,
    Zaxo

Re: Multiplying together the numers in an array
by runrig (Abbot) on Apr 09, 2004 at 02:20 UTC
    Besides the previously mentioned List::Util::reduce method, there's:
    @numbers = (1..5); print product(@numbers),"\n"; sub product { my $result = shift; $result *= $_ for @_; $result; }
Re: Multiplying together the numers in an array
by graff (Chancellor) on Apr 09, 2004 at 03:20 UTC
    Since you have the array already, and you don't like having an extra variable around, you could multiply the first element times each of the subsequent ones -- here's one way ("multiplication is commutative"):
    @ary = (2..6); $ary[0] *= pop @ary while ( @ary > 1 ); print $ary[0], $/;
Re: Multiplying together the numers in an array
by tall_man (Parson) on Apr 09, 2004 at 19:38 UTC
    Another idea, especially if the arrays are large and you need to do many operations on them, is the module PDL.
    use strict; use PDL; my $numbers = pdl (2,3,4,5); my $total = prod($numbers); print $total,"\n";
Re: Multiplying together the numers in an array
by Anonymous Monk on Apr 09, 2004 at 04:49 UTC
    Just a thought, but depending on how you are populating the array, do the calculations at that time. Ryan
Re: Multiplying together the numers in an array
by Anonymous Monk on Apr 09, 2004 at 03:55 UTC
    perl -le'print eval join"*",qw/2 3 4/'
Re: Multiplying together the numers in an array
by Cody Pendant (Prior) on Apr 11, 2004 at 07:31 UTC
    Thank you all for your contributions, it was very interesting.


    ($_='kkvvttuubbooppuuiiffssqqffssmmiibbddllffss')
    =~y~b-v~a-z~s; print