Algorithm-Pair-Best2
view release on metacpan or search on metacpan
lib/Algorithm/Pair/Best2.pm view on Meta::CPAN
# Sliding window:
while (1) {
# create new list containing only a windows-worth of items
my @w_idxs; # items for this window
for my $idx (0 .. $#{$items}) {
if (not exists $paired{$idx}) {
push @w_idxs, $idx;
last if (@w_idxs >= $window * 2) # window filled
}
}
my $score = 0; # need an initial score, might as well count
# initial items as passed to us
for (my $idx = 0; $idx < @w_idxs; $idx += 2) {
$score += $self->get_score($w_idxs[$idx], $w_idxs[$idx + 1]);
}
# pair this window
($score, @w_idxs) = $self->_r_best(0, $score, @w_idxs);
### my $combs = 1;
### map { $combs *= (2 * $_ - 1) } (1 .. @w_idxs / 2);
### print scalar keys %all, ' combinations';
### print " (should be $combs)" if ($combs != scalar keys %all);
### print "\n";
### for my $key (keys %all) {
### my $score = 0;
### for my $pair (split ',', $key) {
### $score += $self->get_score(split('-', $pair));
### }
### $all{$key} = $score;
### }
### for my $key (sort { $all{$b} <=> $all{$a} } keys %all) {
### print "$key = $all{$key}\n";
### }
if (scalar keys %paired < (scalar(@{$items}) - (2 * $window))) {
# keep top pair
$paired{$w_idxs[0]} = 1;
$paired{$w_idxs[1]} = 1;
push @results, $items->[$w_idxs[0]], $items->[$w_idxs[1]];
&$progress($items->[$w_idxs[0]], $items->[$w_idxs[1]], # the paired items
$w_idxs[0], $w_idxs[1]); # item indices
push @scores, $self->get_score(@w_idxs[0..1]);
}
else {
# keep all the results, we are done
push @results, map {$items->[$_]} @w_idxs;
for (my $idx = 0; $idx < @w_idxs; $idx += 2) {
&$progress($items->[$w_idxs[$idx]], $items->[$w_idxs[$idx + 1]], # the paired items
$w_idxs[$idx], $w_idxs[$idx + 1]); # item indices
push @scores, $self->get_score(@w_idxs[$idx..$idx+1]);
}
last;
}
}
return wantarray ? @results : \@results;
}
# find best pairing of @idxs. try the first item in @idxs against every
# other item in the array. after picking the first and the current second
# item, recursively find the best arrangement of all the remaining items.
# the return values are the score followed by the new arrangment.
sub _r_best {
my ($self, $depth, $best_score, @idxs) = @_;
if (@idxs <= 2) {
croak sprintf("%d items left", scalar @idxs) if (@idxs <= 1);
return ($self->get_score(@idxs), @idxs);
}
my @best_trial = @idxs; # copy in case there is no improvement
my ($trial_0, $trial_1, @tail) = @idxs; # working copy
### push @head, $trial_0;
for my $idx (0 .. @idxs - 2) {
### push @head, $trial_1;
### $all{$self->make_key(@head, @tail)} = 0 if (@tail == 2); # collect every combination
# recursively get best pairing for tail
my ($trial_score, @trial_tail) = $self->_r_best($depth + 1, $best_score, @tail);
# add score for top pair
$trial_score += $self->get_score($trial_0, $trial_1); # first pair
### print join(', ', $trial_0, $trial_1, @trial_tail, $self->make_key($trial_0, $trial_1, @trial_tail)), "\n" if ($depth == 0);
### $self->dbg_hash($self->make_key($trial_0, $trial_1, @trial_tail), $trial_score);
if ($trial_score < $best_score) {
# aha! a potential candidate. save it
$best_score = $trial_score;
@best_trial = ($trial_0, $trial_1, @trial_tail);
## printf "%2d %2d Best %8.5f idxs %s\n",
## $depth,
## $idx,
## $best_score,
## $self->print_items(@best_trial) if ($depth < 2);
}
else {
## printf "%2d %2d Not best %8.5f idxs %s\n",
## $depth,
## $idx,
## $trial_score,
## $self->print_items($trial_0, $trial_1, @trial_tail) if ($depth < 2);
}
# hold $trial_0 in slot 0, rotate all items below it
push @tail, $trial_1; # add second item to end of tail
$trial_1 = shift @tail; # move third item into second slot
### pop @head;
}
### pop @head;
### my $key = $self->make_key(@best_trial);
### print "best: $key = $best_score\n" if ($depth == 0);
return ($best_score, @best_trial);
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Algorithm::Pair::Best2 - select pairings (designed for Go tournaments, but can be used for anything).
=head1 VERSION
version 2.040
=head1 SYNOPSIS
use Algorithm::Pair::Best2;
my $pair = Algorithm::Pair::Best2->new( [ options ] );
$pair->add( item, [ item, ... ] );
@new_pairs = $pair->pick( [ window ] );
=head1 DESCRIPTION
This is a re-write of Algorithm::Pair::Best. The interface is simplified
and the implementation is significantly streamlined.
After creating an Algorithm::Pair::Best2 object (with -E<gt>B<new>), B<add>
items to the list of items (i.e: players) to be paired. The final list
must contain an even number of items or B<pick>ing the pairs will throw an
exception.
Algorithm::Pair::Best2-E<gt>B<pick> explores all combinations of items and
returns the pairing list with the best (lowest) score. This can be an
expensive proposition - the number of combinations goes up very fast with
respect to the number of items:
items combinations
2 1 (1)
4 3 (1 * 3)
6 15 (1 * 3 * 5)
8 105 (1 * 3 * 5 * 7)
10 945 (1 * 3 * 5 * 7 * 9
12 10395 (1 * 3 * 5 * 7 * 9 * 11)
14 135135 (1 * 3 * 5 * 7 * 9 * 11 * 13)
It is clearly unreasonable to try to pair a significant number of items.
Trying to completely pair even 30 items would take too long.
( run in 1.340 second using v1.01-cache-2.11-cpan-39bf76dae61 )