Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Re: Printing combinations, code review request

by demerphq (Chancellor)
on Apr 26, 2003 at 14:50 UTC ( [id://253365]=note: print w/replies, xml ) Need Help??


in reply to Printing combinations, code review request

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


Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://253365]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (4)
As of 2024-03-29 05:41 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found