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
|
|
|
|
|
|
|
|