File Coverage

blib/lib/Algorithm/Voting/Sortition.pm
Criterion Covered Total %
statement 87 87 100.0
branch 16 16 100.0
condition n/a
subroutine 19 19 100.0
pod 11 11 100.0
total 133 133 100.0


line stmt bran cond sub pod time code
1             # $Id: Sortition.pm 60 2008-09-02 12:11:49Z johntrammell $
2             # $URL: https://algorithm-voting.googlecode.com/svn/tags/rel-0.01-1/lib/Algorithm/Voting/Sortition.pm $
3              
4             package Algorithm::Voting::Sortition;
5              
6 6     6   45865 use strict;
  6         18  
  6         238  
7 6     6   35 use warnings;
  6         11  
  6         223  
8 6     6   36 use Scalar::Util qw/reftype looks_like_number/;
  6         13  
  6         710  
9 6     6   41 use Digest::MD5;
  6         19  
  6         213  
10 6     6   10486 use Math::BigInt;
  6         162381  
  6         35  
11 6     6   148303 use Params::Validate 'validate';
  6         63334  
  6         483  
12 6     6   51 use base 'Class::Accessor::Fast';
  6         12  
  6         5932  
13              
14             =pod
15              
16             =head1 NAME
17              
18             Algorithm::Voting::Sortition - implements RFC 3797, "Publicly Verifiable
19             Nominations Committee (NomCom) Random Selection"
20              
21             =head1 SYNOPSIS
22              
23             To choose two of our favorite Hogwarts pals via sortition:
24              
25             use Algorithm::Voting::Sortition;
26              
27             # choose a list of candidates
28             my @candidates = qw/
29             Harry Hermione Ron Neville Albus
30             Severus Ginny Hagrid Fred George
31             /;
32              
33             # the results of our predetermined entropy source
34             my @keysource = (
35             [32,40,43,49,53,21], # 8/9/08 powerball numbers
36             "W 4-1", # final score of 8/8/08 Twins game
37             );
38              
39             # use sortition to determine the winners
40             my $race = Algorithm::Voting::Sortition->new(
41             candidates => \@candidates,
42             source => \@keysource,
43             n => 2,
44             );
45             printf "Key string is: '%s'\n", $race->keystring;
46             print $race->as_string;
47              
48             =head1 DESCRIPTION
49              
50             Sortition is an unbiased method for "drawing straws" or "casting lots". This
51             package implements the Sortition algorithm as described in RFC 3797, "Publicly
52             Verifiable Nominations Committee (NomCom) Random Selection"
53             (L):
54              
55             =over 4
56              
57             This document describes a method for making random selections in such a way
58             that the unbiased nature of the choice is publicly verifiable. As an example,
59             the selection of the voting members of the IETF Nominations Committee (NomCom)
60             from the pool of eligible volunteers is used. Similar techniques would be
61             applicable to other cases.
62              
63             =back
64              
65             =head1 METHODS
66              
67             =head2 Algorithm::Voting::Sortition->new( %args )
68              
69             Constructs a new sortition object.
70              
71             Example:
72              
73             my $s = Algorithm::Voting::Sortition->new(
74             candidates => [ 'A' .. 'Z' ],
75             n => 3,
76             source => [ $scalar, \@array, \%hash ],
77             );
78              
79             =cut
80              
81             sub new {
82 8     8 1 7452 my $class = shift;
83 8         66 my %valid = (
84             candidates => 1,
85             n => { default => -1 },
86             source => 0,
87             keystring => 0,
88             );
89 8         301 my %args = validate(@_, \%valid);
90 8         82 return bless \%args, $class;
91             }
92              
93             =head2 $obj->candidates
94              
95             Returns a list containing the current candidates.
96              
97             =cut
98              
99             sub candidates {
100 5     5 1 10 return @{ $_[0]->{candidates} };
  5         52  
101             }
102              
103             =head2 $obj->n
104              
105             Returns the number of candidates that are to be chosen from the master list.
106             If C is unspecified when the sortition object is constructed, the total
107             number of candidates is used, i.e. the sortition will return a list containing
108             all candidates.
109              
110             =cut
111              
112             sub n {
113 8     8 1 28 my $self = shift;
114 8 100       65 if ($self->{n} < 1) {
115 3         10 $self->{n} = scalar($self->candidates);
116             }
117 8         42 return $self->{n};
118             }
119              
120             =head2 $obj->source()
121              
122             Mutates the entropy source to be used in the sortition.
123              
124             Example:
125              
126             $obj->source(@entropy); # sets the entropy value
127             my @e = $obj->source; # retrieves the entropy
128              
129             =cut
130              
131             sub source {
132 2     2 1 7 my $self = shift;
133 2 100       6 if (@_) { $self->{source} = \@_; }
  1         15  
134 2         4 return @{ $self->{source} };
  2         7  
135             }
136              
137             =head2 $obj->keystring()
138              
139             Uses the current value of C<< $self->source >> to create and cache a master
140             "key string".
141              
142             =cut
143              
144             sub keystring {
145 41     41 1 62 my $self = shift;
146 41 100       117 unless (exists $self->{keystring}) {
147 1         22 $self->{keystring} = $self->make_keystring($self->source);
148             }
149 41         278 return $self->{keystring};
150             }
151              
152             =head2 $obj->make_keystring(@source)
153              
154             Creates a "key string" from the input values in C<@source>.
155              
156             =cut
157              
158             sub make_keystring {
159 4     4 1 1847 my ($self,@source) = @_;
160 4         10 return join q(), map { $self->stringify($_) . q(/) } @source;
  11         25  
161             }
162              
163             =head2 $obj->stringify($thing)
164              
165             Converts C<$thing> into a string. C<$thing> can be a scalar, an arrayref, or a
166             hashref. If C<$thing> is anything else, this method Cs.
167              
168             =cut
169              
170             sub stringify {
171 20     20 1 1560 my ($self, $thing) = @_;
172 20 100       63 if (reftype($thing)) {
173 13 100       53 if (reftype($thing) eq 'ARRAY') {
    100          
174 10         29 return join q(), map { "$_." } $self->_sort(@$thing);
  40         120  
175             }
176             elsif (reftype($thing) eq 'HASH') {
177 3         17 return join q(),
178 2         8 map { $_ . q(:) . $thing->{$_} . q(.) }
179             $self->_sort(keys %$thing);
180             }
181             else {
182 1         12 die "Can't stringify: $thing";
183             }
184             }
185             else {
186 7         36 return "$thing.";
187             }
188             }
189              
190             =head2 $class->_sort(@items)
191              
192             Returns a list containing the values of C<@items>, but sorted. Sorts
193             numerically if C<@items> contains only numbers (according to
194             C), otherwise sorts lexically.
195              
196             =cut
197              
198             sub _sort {
199 12     12   26 my ($class, @items) = @_;
200 12 100       21 if (grep { !looks_like_number($_) } @items) {
  43         113  
201 5         23 return sort @items;
202             }
203             else {
204 7         37 return sort { $a <=> $b } @items;
  45         68  
205             }
206             }
207              
208             =head2 $obj->digest($n)
209              
210             Calculates and returns the Ith digest of the current keystring. This is
211             done by bracketing C<< $obj->keystring >> with a "stringified" version of
212             C<$n>, then calculating the MD5 digest of the result.
213              
214             The value returned is a 32-character string containing the checksum in
215             hexadecimal format.
216              
217             =cut
218              
219             sub digest {
220 36     36 1 7691 my ($self, $n) = @_;
221 36         91 my $pre = pack("n",$n); # "n" => little-endian, 2-byte ("short int")
222 36         77 return Digest::MD5::md5_hex($pre . $self->keystring . $pre);
223             }
224              
225             =head2 $obj->seq
226              
227             Returns a list of integers based on the dynamic keystring digest. These
228             integers will be used will be used to choose the winners from the candidate
229             pool.
230              
231             =cut
232              
233             sub seq {
234 4     4 1 54 my $self = shift;
235 26         98 return map {
236 4         16 my $hex = $self->digest($_);
237 26         126 my $i = Math::BigInt->new("0x${hex}");
238 26 100       7548 if ($i->is_nan) {
239 1         20 die("got invalid hex from digest($_): '$hex'");
240             }
241 25         439 $i;
242             } 0 .. $self->n - 1;
243             }
244              
245             =head2 $obj->result
246              
247             Returns a data structure containing the contest results. For sortition, the
248             structure is a list of candidates, with the first winner at list position 0,
249             etc.
250              
251             =cut
252              
253             sub result {
254 2     2 1 14 my $self = shift;
255 2         9 my $n = $self->n;
256 2         10 my @seq = $self->seq;
257 2         55 my @candidates = $self->candidates;
258 2         5 my @result;
259 2         8 while ($n) {
260 20         31 my $j = shift @seq;
261 20         67 $j->bmod(scalar @candidates); # modifies $j
262             # splice() out the chosen candidate into @result
263 20         2680 push @result, splice(@candidates, $j, 1);
264 20         369 $n--;
265             }
266 2         22 return @result;
267             }
268              
269             =head2 $obj->as_string
270              
271             Returns the election results, formatted as a multiline string.
272              
273             =cut
274              
275             sub as_string {
276 1     1 1 3 my $self = shift;
277 1         2 my $i = 0;
278 1         3 my $str = qq(Keystring: "@{[ $self->keystring]}"\n);
  1         4  
279 1         7 $str .= join q(), map { $i++; "$i. $_\n" } $self->result;
  10         12  
  10         23  
280 1         6 return $str;
281             }
282              
283             1;
284