http://qs1969.pair.com?node_id=323385

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

I would assume that this is a FAQ, but it seems to be impossible to get reasonable search results for overloaded terms like "perl product" or "multiply array."

At any rate, I have this procedure for multiplying a list of numbers:

sub product { my $list = shift; my $prod = 1; foreach my $num (@$list) { $prod *= $num; } return $prod; }

However, with the loop and all, it seems like a terribly inefficient method of doing such a thing. Anyone have a smarter way?

Replies are listed 'Best First'.
Re: Product of a list of numbers?
by Zaxo (Archbishop) on Jan 23, 2004 at 03:09 UTC

    Your way is fine, but if you need more speed, look at PDL,

    sub product { pdl( shift )->prodover; }
    In the interest of speed, you'd probably want to work with piddles from the start, rather than converting like that.

    After Compline,
    Zaxo

      Now the most idiomatic approach would be:
      PDL->topdl(shift)->prodover
      as topdl will not duplicate the data if it is already a pdl/ndarray.
Re: Product of a list of numbers?
by BrowserUk (Patriarch) on Jan 23, 2004 at 02:51 UTC

      For anyone wishing to do this now, note that since version 1.35 (October 2013) List::Util has had a product function to perform precisely this task.

      #!/usr/bin/env perl use strict; use warnings; use List::Util 1.35 'product'; my @a = (1, 2, 3, 4, 5); print product @a;

      🦛

Re: Product of a list of numbers?
by PodMaster (Abbot) on Jan 23, 2004 at 01:12 UTC
    FYI, for future searches, you can safely omit "perl" from "perl product" (what's next, "perl array" ? :)).

    However, with the loop and all, it seems like a terribly inefficient method of doing such a thing. Anyone have a smarter way?
    Why does it seem inefficient? It's the most straight forward way to accomplish the task. Maybe not as "elegant" (matter of opinion) as
    sub product { my $list = shift; my $prod = 1; $prod *= $_ for @$list; return $prod; }
    but it's pretty much as efficient as it gets.

    MJD says "you can't just make shit up and expect the computer to know what you mean, retardo!"
    I run a Win32 PPM repository for perl 5.6.x and 5.8.x -- I take requests (README).
    ** The third rule of perl club is a statement of fact: pod is sexy.

Re: Product of a list of numbers?
by Roger (Parson) on Jan 23, 2004 at 01:11 UTC
    I think your method is quite efficient already in purl Perl. If you are looking for ways to speed up, you should have a look at Inline::C or perlxs.

Re: Product of a list of numbers?
by Popcorn Dave (Abbot) on Jan 23, 2004 at 05:59 UTC
    Just out of curiosity, is there a speed issue when using map or would it really depend on the size of the array being mapped over?

    Given:

    use strict; my @list = (1,2,3,4,5); my $result = 1; map {$result *= $_} @list; print $result;

    I realize that TIMTOWTDI, but is this a good place to use map, is the loop preferable, or are they about the same?

    There is no emoticon for what I'm feeling now.

      Why don't you Benchmark it?

      Updated: Added my own Benchmark result.

      use strict; use Benchmark qw/ cmpthese timethese /; use List::Util qw/ reduce /; my @list = 1..10; cmpthese( timethese(1000000, { 'map_void' => '&map_void', 'for_loop' => '&for_loop', 'list_reduce' => '&list_reduce', })); # printf "%d, %d, %d\n", map_void(), for_loop(), list_reduce(); sub map_void { my $result = 1; map {$result *= $_} @list; return $result; } sub for_loop { my $result = 1; $result *= $_ foreach @list; return $result; } sub list_reduce { return reduce { $a * $b } @list; } __END__ Benchmark: timing 1000000 iterations of for_loop, list_reduce, map_voi +d... for_loop: 4 wallclock secs ( 5.43 usr + 0.00 sys = 5.43 CPU) @ 18 +4229.92/s (n=1000000) list_reduce: 9 wallclock secs ( 8.46 usr + 0.00 sys = 8.46 CPU) @ 1 +18175.37/s (n=1000000) map_void: 5 wallclock secs ( 4.52 usr + 0.00 sys = 4.52 CPU) @ 22 +1385.88/s (n=1000000) Rate list_reduce for_loop map_void list_reduce 118175/s -- -36% -47% for_loop 184230/s 56% -- -17% map_void 221386/s 87% 20% --

        Ok....

        use strict; use warnings; use Benchmark; my @list = ( 1 .. 500 ); my $count = 25000; timethese ( $count, { 'Map' => sub { my $result = 1; map { $result *= $_ } @list; }, 'For' => sub { my $result = 1; $result *= $_ for @list; } } ); __OUTPUT__ Benchmark: timing 25000 iterations of For, Map... For: 11 wallclock secs (10.62 usr + 0.00 sys = 10.62 CPU) @ 2355.16/s + (n=25000) Map: 11 wallclock secs (11.18 usr + 0.00 sys = 11.18 CPU) @ 2236.94/s + (n=25000)

        The moral... There's no way to perform a mathematical operation based on the value of each element of a list of arbitrary values, without looking at each element of the list. And that implies (and requires) some from of iteration. map and for seem to be pretty close to each other in terms of efficiency in iterating over the list.

        On the other hand, if the list consists of a known sequence, it is possible to break out the math-fu to derive a non-iterative solution. Brute force is necessary in the absence of a known mathematical sequence though.


        Dave

        I'm quite surprised by your results. It shows the reduce version to be the slowests, and it suggests that using a map with a block is faster than a for statement modifier. The former needs to enter/leave a block for each iteration, and the latter doesn't. A benchmark on my box shows different results:
        #!/usr/bin/perl use strict; use warnings; use Benchmark qw /cmpthese timethese/; use List::Util qw /reduce/; our @list = 1 .. 50; our ($map_b, $map_e, $for_m, $for_b, $red, $bc, $eval); print "Perl version: $].\n"; system "cat /proc/version"; cmpthese -10 => { map_block => '$::map_b = 1; map {$::map_b *= $_} @::list', map_expr => '$::map_e = 1; map $::map_e *= $_, @::list', for_mod => '$::for_m = 1; $::for_m *= $_ for @::list', for_block => '$::for_b = 1; foreach (@::list) {$::for_b *= $_}', reduce => '$::red = reduce {$a * $b} @::list', bc => q !local $" = "*"; $::bc = `echo '@::list' | bc`!, eval => '$::eval = eval join "*" => @::list', }; print "Map block: $map_b\n"; print "Map expres: $map_e\n"; print "For modifier: $for_m\n"; print "For block: $for_b\n"; print "Reduce: $red\n"; print "bc: $bc\n"; print "Eval: $eval\n"; __END__ Perl version: 5.008003. Linux version 2.4.18-3 (bhcompile@daffy.perf.redhat.com) (gcc version +2.96 20000731 (Red Hat Linux 7.3 2.96-110)) #1 Thu Apr 18 07:37:53 ED +T 2002 Rate bc eval map_block for_block map_expr for_m +od reduce bc 2779/s -- -55% -88% -94% -95% -9 +5% -95% eval 6199/s 123% -- -73% -87% -88% -8 +8% -89% map_block 22904/s 724% 269% -- -53% -55% -5 +7% -60% for_block 49041/s 1664% 691% 114% -- -3% - +9% -13% map_expr 50669/s 1723% 717% 121% 3% -- - +6% -11% for_mod 53638/s 1830% 765% 134% 9% 6% +-- -5% reduce 56651/s 1938% 814% 147% 16% 12% +6% -- Map block: 3.0414093201713378e+64 Map expres: 3.0414093201713378e+64 For modifier: 3.0414093201713378e+64 For block: 3.0414093201713378e+64 Reduce: 3.0414093201713378e+64 bc: 30414093201713378043612608166064768844377641568960512000 +000000000 Eval: 3.0414093201713378e+64

        Abigail

Re: Product of a list of numbers?
by Skeeve (Parson) on Jan 23, 2004 at 10:14 UTC
    Benchmark on your own, but I'd give this a try:
    sub product { return eval join '*',@_; }
Re: Product of a list of numbers?
by Abigail-II (Bishop) on Jan 23, 2004 at 10:20 UTC
    For a quick and dirty way, I like to do stuff like:
    sub product { my $list = shift; local $" = "*"; `echo '@$list' | bc` }

    Abigail

Language::Functional
by sleepingsquirrel (Chaplain) on Jan 23, 2004 at 18:38 UTC
    Maybe not 100% on-topic, but you might look into Language::Functional which has a lot of interesging array processing subroutines taken from the realm of functional programming. (and yes, there is one for finding the product).