File Coverage

blib/lib/Algorithm/Pair/Swiss.pm
Criterion Covered Total %
statement 58 58 100.0
branch 25 32 78.1
condition n/a
subroutine 10 10 100.0
pod 5 5 100.0
total 98 105 93.3


line stmt bran cond sub pod time code
1             # $Id: Swiss.pm 34 2006-06-19 19:19:43Z giel $
2              
3             # Algorithm::Pair::Swiss.pm
4             #
5             # Copyright (C) 2006 Gilion Goudsmit ggoudsmit@shebang.nl
6             #
7             # This library is free software; you can redistribute it and/or modify it
8             # under the same terms as Perl itself, either Perl version 5.8.5 or, at your
9             # option, any later version of Perl 5 you may have available.
10             #
11             # This program is distributed in the hope that it will be useful, but
12             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13             # or FITNESS FOR A PARTICULAR PURPOSE.
14             #
15              
16             =head1 NAME
17              
18             Algorithm::Pair::Swiss - Generate unique pairings for tournaments
19              
20             =head1 VERSION
21              
22             This document describes Algorithm::Pair::Swiss version 0.14
23              
24             =head1 SYNOPSIS
25              
26             use Algorithm::Pair::Swiss;
27              
28             my $pairer = Algorithm::Pair::Swiss->new;
29              
30             $pairer->parties(1,2,3,4);
31              
32             @round_1 = $pairer->pairs;
33              
34             $pairer->exclude(@round_1);
35              
36             @round_2 = $pairer->pairs;
37              
38             =head1 DESCRIPTION
39              
40             This module was created as an alternative for Algorithm::Pair::Best, which
41             probably offers more control over the pairings, in particular regarding
42             ensuring the highest overal quality of pairings. Algorithm::Pair::Swiss is
43             sort of dumb in this regard, but uses a slightly more intuitive interface
44             and an algorithm that should perform noticably faster. The module was
45             primarily designed based on the Swiss rounds system used for Magic: The
46             Gathering tournaments.
47              
48             After creating an Algorithm::Pair::Swiss-EB object, use the B
49             method to supply a list of parties (players or teams) to be paired. At any
50             time the B method can be used to indicate which pairs shouldn't be
51             generated (probably because they've already been paired in an earlier round).
52              
53             The list of parties is sorted and the pairer tries to find a set of pairs that
54             respects the exclude list, and tries to pair the parties that appear first
55             in the sorted list with each other most aggresively.
56              
57             To influence the sort order, use objects as parties and overload either the
58             B or B<0+> operators in the object class to sort as desired.
59              
60             Algorithm::Pair::Swiss-EB explores the parties and returns the first
61             pairing solution which satisfies the excludes. Because it doesn't exhaustively
62             try all possible solutions, performance is generally pretty reasonable.
63              
64             For a large number of parties, it is generally easy to find a non-excluded pair,
65             and for a smaller number of parties traversal of the possible pairs is done
66             reasonably fast.
67              
68             This module uses the parties as keys in a hash, and uses the empty string ('')
69             as a special case in this same hash. For this reason, please observe the
70             following restrictions regarding your party values:
71              
72             =over 1
73              
74             =item - make sure it is defined (not undef)
75              
76             =item - make sure it is defined when stringified
77              
78             =item - make sure each is a non-empty string when stringified
79              
80             =item - make sure each is unique when stringified
81              
82             =back
83              
84             All the restrictions on the stringifications are compatible with the perl's
85             default stringification of objects, and should be safe for any stringification
86             which returns a unique party-identifier (for instance a primary key from a
87             Class::DBI object).
88              
89             =cut
90              
91              
92             package Algorithm::Pair::Swiss;
93 3     3   72369 use strict;
  3         7  
  3         106  
94 3     3   17 use warnings;
  3         6  
  3         97  
95 3     3   15 no warnings 'recursion';
  3         10  
  3         260  
96             require 5.001;
97              
98             our $REVISION = sprintf(q{%d} => q{$Rev: 34 $} =~ /(\d+)/g);
99             our $VERSION = q(0.14);
100              
101 3     3   15 use Carp;
  3         5  
  3         2458  
102              
103             ######################################################
104             #
105             # Public methods
106             #
107             #####################################################
108              
109             =head1 METHODS
110              
111             =over 4
112              
113             =item my $pairer = Bnew>( @parties )
114              
115             A B Algorithm::Pair::Swiss object is used to generate pairings.
116             Optionally @parties can be given when instantiating the object. This is
117             the same as using the B method described below.
118              
119             =cut
120              
121             sub new {
122 4     4 1 743 my $class = shift;
123 4         15 my $self = bless {}, $class;
124 4 100       22 $self->parties(@_) if @_;
125 4         13 return $self;
126             }
127              
128             =item $pairer-EB( @parties )
129              
130             Provides the pairer with a complete list of all individuals that can
131             be paired. If no parties are specified, it returns the sorted list
132             of all parties. This allows you to use this method to extract 'rankings'
133             if you happen to have implemented a B operator overload in the
134             class your parties belong to.
135              
136             =cut
137              
138             sub parties {
139 16     16 1 8605 my $self = shift;
140 16 100       53 return sort @{$self->{parties}} unless @_;
  12         341  
141 4         25 $self->{parties} = [ @_ ];
142 4         10 for my $i (@{$self->{parties}}) {
  4         16  
143 14 50       47 croak q{All parties must have a defined stringification}
144             unless defined "$i";
145 14 50       39 croak qq{All parties must have a unique stringification, but "$i" seems to be a duplicate}
146             if exists $self->{exclude}->{"$i"};
147 14         41 $self->{exclude}->{"$i"}={}
148             }
149             }
150              
151             =item @pairs = $pairer-EB
152              
153             Returns the best pairings found as a list of arrayref's, each containing
154             one pair of parties.
155              
156             =cut
157              
158             sub pairs {
159 8     8 1 39 my $self = shift;
160 8         24 my @pairs = _pairs([$self->parties],$self->{exclude});
161 8         46 return @pairs;
162             }
163              
164             =item $pair-EB( @pairs )
165              
166             Excludes the given pairs from further pairing. The @pairs array
167             should consist of a list of references to arrays, each containing the two
168             parties of that pair. This means you can easily feed it the output of
169             a previous call to $pair-EB. The selection given is added
170             to previously excluded pairs.
171              
172             If there was an odd number of parties, the lowest ranked party will be
173             paired with 'undef', unless it has already been paired with 'undef'. In
174             that case, the second-lowest ranked party will get that pairing. Etcetera,
175             etcetera. 'Lowest-ranked' is defined as being last in the party-list after
176             sorting. In MTG terms, being paired with 'undef' would mean getting a bye
177             (and getting the full three points for that round as a consequence).
178              
179             =cut
180              
181             sub exclude {
182 6     6 1 4952 my $self = shift;
183 6         17 for my $pair (@_) {
184 12         40 my ($x,$y) = @$pair;
185 12 100       68 $self->{exclude}->{"$x"}->{$y?"$y":''} = 1 if $x;
    50          
186 12 50       124 $self->{exclude}->{"$y"}->{$x?"$x":''} = 1 if $y;
    100          
187             }
188             }
189              
190             =item $pair-EB( @parties )
191              
192             Excludes the given parties from further pairing. The given parties will
193             be removed from the internal parties list and won't be returned by the
194             parties method anymore. This method is usually used when a participant
195             has decided to quit playing.
196              
197             =cut
198              
199             sub drop {
200 1     1 1 6617 my $self = shift;
201 1         6 my %parties = map { ( "$_" => $_ ) } $self->parties;
  3         16  
202 1         5 for my $party (@_) { delete $parties{"$party"} }
  1         6  
203 1         9 $self->{parties} = [ values %parties ];
204             }
205              
206             sub _pairs {
207 14     14   20 my ($unpaired,$exclude) = @_;
208 14         35 my @unpaired = @$unpaired;
209 14         20 my $p1 = shift @unpaired;
210 14         25 for my $p2 (@unpaired) {
211 20 100       62 next if exists $exclude->{"$p1"}->{"$p2"}; # already paired
212 9 50       26 next if exists $exclude->{"$p2"}->{"$p1"}; # already paired
213 9 100       33 return [$p1,$p2] if @unpaired==1; # last pair!
214 5         11 my @remaining = grep {"$_" ne "$p2"} @unpaired; # this pair could work
  13         37  
215 5         36 my @pairs = _pairs(\@remaining,$exclude); # so try to pair the rest
216 5 50       21 next unless @pairs; # no luck
217 5         25 return [$p1,$p2],@pairs; # yay! return the resultset
218             }
219 5 100       19 if(@unpaired % 2 == 0) { # single player left
220 4 100       17 return if exists $exclude->{"$p1"}->{''}; # already had a bye before
221 3 100       10 return [$p1,undef] unless @unpaired; # return a bye
222 1         3 my @pairs = _pairs(\@unpaired,$exclude);
223 1 50       4 return unless @pairs;
224 1         3 return @pairs,[$p1,undef];
225             }
226 1         4 return;
227             }
228              
229             1;
230              
231             __END__