#!/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 order
$index[++$pos]=++$v while $pos<$#index;
}
}
return \@ret;
}
##
##
Benchmark: running Sparky, demphq, int_i2, int_ni, int_np, int_pp, iterat,
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% --