Hmm, I was right about changing the algorithm, this routine doesnt need recursion, and it doesnt need to be throwing arrays around everywhere. I get a significant speed up by using the following code

readmore is the full benchmark!
#!/usr/bin/perl use strict; use warnings; use Benchmark 'cmpthese'; use Data::Dumper; sub comb { my @items = @{ $_[0] }; my $group = $_[1]; my @list = @{ $_[2] }; my $ret = $_[3]||[]; unless ($group) { push @$ret,\@list; } else { my (@newitems,@newlist,$i); foreach $i (0 .. $#items) { @newlist = @list; push (@newlist, shift (@items)); @newitems = @items; comb([@newitems], $group - 1, [@newlist],$ret); } } return $ret; } sub comb_demq { my ($items,$group,$list)=@_; return _comb_demerphq([@{$items||[]}],$group,[@{$list||[]}]); } sub _comb_demerphq { my ($items,$group,$list,$ret) = @_; $ret||=[]; unless ($group) { push @$ret,[@$list]; } else { my @newlist = (@$list,undef); while (@$items) { $newlist[-1]=shift (@$items); _comb_demerphq([@$items], $group - 1, \@newlist,$ret); } } return $ret } sub comb_integral_np { my ($items, $group, $list, $next,$ret) = @_; $list ||= []; $next ||= 0; $ret||=[]; if ($group == 1) { push @$ret,[@$list,$_] for @$items[$next..$#$items]; } else { for my $i ($next..$#$items) { comb_integral_np($items, $group - 1, [@$list, $$items[$i]], $i + + 1,$ret); } } return $ret } sub comb_integral_pp { my ($items, $group, $list, $next,$ret) = @_; $list ||= []; $next ||= 0; $ret||=[]; if ($group == 1) { push @$ret,[@$list,$_] for @$items[$next..$#$items]; } else { for my $i ($next..$#$items) { push @$list, $items->[$i]; comb_integral_pp($items, $group - 1, $list, $i + 1,$ret); pop @$list; } } return $ret } sub comb_integral_ni { my ($items, $group, $next) = @_; $next ||= 0; if ($group == 1) { return map [$_], @$items[$next..$#$items]; } else { my @returns; for my $i ($next..$#$items) { push @returns, map [$$items[$i], @$_], comb_integral_ni($items, +$group - 1, $i + 1); } return @returns; } } sub comb_integral_ni2 { my ($items, $group, $next) = @_; $next ||= 0; if ($group == 1) { return map [$_], @$items[$next..$#$items]; } else { my @returns; for my $i ($next..$#$items) { push @returns, my @combs = comb_integral_ni2($items, $group - +1, $i + 1); unshift @$_, $$items[$i] for @combs; } return @returns; } } my $Tests={ Sparky => 'comb([1..5],2,[])', demphq => 'comb_demq([1..5],2,[])', iterat => 'comb_iter([1..5],2)', int_np => 'comb_integral_np([1..5],2)', int_pp => 'comb_integral_pp([1..5],2)', int_ni => '[comb_integral_ni([1..5],2)]', int_i2 => '[comb_integral_ni2([1..5],2)]', }; # First run them all on groups of 2 through 5 # and print the output foreach my $count (2..5) { foreach my $name (keys %$Tests) { (my $eval=$Tests->{$name})=~s/,2/,$count/; print "$name: $eval\n"; my $ret=eval($eval); die "Error!" unless $ret; printf "%2d -> @{$ret->[$_-1]}\n",$_ for 1..@$ret; print "---\n"; } } # Benchmark for at least 1 second each cmpthese -1,$Tests;
sub comb_iter { my $items = shift || []; # Think 1,2,3,4,5 my $group = shift || 2; # Think 2 @$items>=$group or die "Insufficient elements ".scalar(@$items). " to make groups of $group\n"; my @index=(0..$group-1); # Think 0,1 my @last=(@$items-$group..$#$items); # Think 3,4 my @ret; while ($index[0]<=$last[0]) { # Think first pass: 0 < 3 push @ret,[@$items[@index]]; # increment the last digit, and if we rollover carry it left unless (++$index[-1]<=$last[-1]) { # we've rolled over, we need to go left until # until the result after adding 1 is below the # respective last. my $pos=$#index; --$pos while ($pos and $index[$pos]+1>$last[$pos]); # increment the apropriate index, if we increment # $index[0] too high the loop exits because of the # while condition. my $v=++$index[$pos]; # and then move right assigning the values in sequential o +rder $index[++$pos]=++$v while $pos<$#index; } } return \@ret; }

A good point of attack to make an algorithm more efficient is to look at eliminating recursion where you can. People have a tendency to use the stack and recursion to take care of their messy business. This is ok in some respects like ease of design, or speed of implementation, but often has the side effect of being slow, as usually more ends up being moved around and done than needs to be. Whenever possible its good to look for the iterative solution, and only fall back to a recursive one if the iterative eludes you.

<edited tofit="your screen, for your viewing pleasure"> Benchmark: running Sparky, demphq, int_i2, int_ni, int_np, int_pp, ite +rat, each for at least 1 CPU seconds... Sparky: 1 w-secs ( 1.07 usr + 0.00 sys = 1.07 CPU) @ 8358.21/s +(n=8960) demphq: 1 w-secs ( 1.12 usr + 0.00 sys = 1.12 CPU) @ 11914.36/s + (n=13356) int_i2: 1 w-secs ( 1.04 usr + 0.00 sys = 1.04 CPU) @ 15889.53/s + (n=16541) int_ni: 1 w-secs ( 1.05 usr + 0.00 sys = 1.05 CPU) @ 18582.70/s + (n=19549) int_np: 1 w-secs ( 1.06 usr + 0.00 sys = 1.06 CPU) @ 18407.72/s + (n=19549) int_pp: 1 w-secs ( 1.04 usr + 0.00 sys = 1.04 CPU) @ 18761.04/s + (n=19549) iterat: 1 w-secs ( 1.06 usr + 0.00 sys = 1.06 CPU) @ 22394.91/s + (n=23761) Rate Sparky demphq int_i2 int_np int_ni int_pp iterat Sparky 8358/s -- -30% -47% -55% -55% -55% -63% demphq 11914/s 43% -- -25% -35% -36% -36% -47% int_i2 15890/s 90% 33% -- -14% -14% -15% -29% int_np 18408/s 120% 55% 16% -- -1% -2% -18% int_ni 18583/s 122% 56% 17% 1% -- -1% -17% int_pp 18761/s 124% 57% 18% 2% 1% -- -16% iterat 22395/s 168% 88% 41% 22% 21% 19% -- </edited>

Anyway, that was fun, thanks.


---
demerphq

<Elian> And I do take a kind of perverse pleasure in having an OO assembly language...

• Update:  
Added integrals code to the BM. I couldnt get the non push/pop version working correctly, probably my fault. Also I should mention that originally I altered all of the solutions to return an AoA of the results, instead of printing them out. This was needed to make the benchmark possible.
• Update:  
Ok, now I feel stupid. I got both version of integrals code to work now. It was a dumb mistake that I noticed after I updated. I also reorganized to the BM code to make it easier to update the node here.
• Update:  
Added integrals two new variants. Renamed tests to make graph fit



In reply to Re: Printing combinations, code review request by demerphq
in thread Printing combinations, code review request by SparkeyG

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.