line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
39
|
|
|
39
|
|
24593
|
use strict; |
|
39
|
|
|
|
|
97
|
|
|
39
|
|
|
|
|
1349
|
|
2
|
39
|
|
|
39
|
|
234
|
use warnings; |
|
39
|
|
|
|
|
91
|
|
|
39
|
|
|
|
|
1044
|
|
3
|
39
|
|
|
39
|
|
769
|
use 5.024; |
|
39
|
|
|
|
|
147
|
|
4
|
|
|
|
|
|
|
|
5
|
39
|
|
|
39
|
|
232
|
use feature qw /postderef signatures/; |
|
39
|
|
|
|
|
95
|
|
|
39
|
|
|
|
|
3715
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
package Vote::Count::TieBreaker; |
8
|
39
|
|
|
39
|
|
261
|
use Moose::Role; |
|
39
|
|
|
|
|
77
|
|
|
39
|
|
|
|
|
334
|
|
9
|
|
|
|
|
|
|
|
10
|
39
|
|
|
39
|
|
211649
|
no warnings 'experimental'; |
|
39
|
|
|
|
|
113
|
|
|
39
|
|
|
|
|
2035
|
|
11
|
39
|
|
|
39
|
|
274
|
use List::Util qw( min max sum ); |
|
39
|
|
|
|
|
93
|
|
|
39
|
|
|
|
|
3422
|
|
12
|
39
|
|
|
39
|
|
306
|
use Path::Tiny; |
|
39
|
|
|
|
|
84
|
|
|
39
|
|
|
|
|
2133
|
|
13
|
39
|
|
|
39
|
|
271
|
use Data::Dumper; |
|
39
|
|
|
|
|
87
|
|
|
39
|
|
|
|
|
2009
|
|
14
|
39
|
|
|
39
|
|
274
|
use Vote::Count::RankCount; |
|
39
|
|
|
|
|
106
|
|
|
39
|
|
|
|
|
1050
|
|
15
|
39
|
|
|
39
|
|
237
|
use Carp; |
|
39
|
|
|
|
|
118
|
|
|
39
|
|
|
|
|
63332
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
our $VERSION='2.00'; |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 NAME |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
Vote::Count::TieBreaker |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 VERSION 2.00 |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 Synopsis |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
my $Election = Vote::Count->new( |
28
|
|
|
|
|
|
|
BallotSet => $ballotsirvtie2, |
29
|
|
|
|
|
|
|
TieBreakMethod => 'approval' |
30
|
|
|
|
|
|
|
); |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
=cut |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# ABSTRACT: TieBreaker object for Vote::Count. Toolkit for vote counting. |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
=head1 Tie Breakers |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
The most important thing for a Tie Breaker to do is it should use some reproducible difference in the Ballots to pick a winner from a Tie. The next thing it should do is make sense. Finally, the ideal Tie Breaker will resolve when there is any difference to be found. The only fully resolvable method is unfortunately Random, but that is not reproducable between runs. Precedence sets a fixed resolution order and can be used to make Random reproducible. |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
TieBreakMethod is specified as an argument to Vote::Count->new(). The TieBreaker is called internally from the resolution method via the TieBreaker function, which requires the caller to pass its TieBreakMethod. |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=head1 TieBreakMethod argument to Vote::Count->new |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
'approval' |
45
|
|
|
|
|
|
|
'all' [ eliminate all tied choices ] |
46
|
|
|
|
|
|
|
'borda' [ applies Borda Count to current Active set ] |
47
|
|
|
|
|
|
|
'grandjunction' [ more resolveable than simple TopCount would be ] |
48
|
|
|
|
|
|
|
'none' [ eliminate no choices ] |
49
|
|
|
|
|
|
|
'precedence' [ requires also setting PrecedenceFile ] |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=head1 Grand Junction |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
The Grand Junction (also known as Bucklin) method is one of the simplest and easiest to Hand Count RCV resolution methods. Other than that it is generally not considered a good method. |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
Because it is simple, and always resolves, except when ballots are perfectly matched up, it is a great TieBreaker. It is not Later Harm Safe, but heavily favors higher rankings. It is the Vote::Count author's preferred Tie-Breaker. |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
=head2 The (Standard) Grand Junction Method |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
Only the Tie-Breaker variant is currently implemented in Vote::Count. |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
=over |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
=item 1 |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
Count the Ballots to determine the quota for a majority. |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=item 2 |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
Count the first choices and elect a choice which has a majority. |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=item 3 |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
If there is no winner add the second choices to the totals and elect the choice which has a majority (or the most votes if more than one choice reaches a majority). |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=item 4 |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
Keep adding the next rank to the totals until either there is a winner or all ballots are exhausted. |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=item 5 |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
When all ballots are exhausted the choice with the highest total wins. |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=back |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=head2 As a Tie Breaker |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
The Tie Breaker Method is modified. |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
Instead of Majority, any choice with a current total less than another is eliminated. This allows resolution of any number of choices in a tie. |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
The winner is the last choice remaining. |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=head2 TieBreakerGrandJunction |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
my $resolve = $Election->TieBreakerGrandJunction( $choice1, $choice2 [ $choice3 ... ] ); |
96
|
|
|
|
|
|
|
if ( $resolve->{'winner'}) { say "Tie Winner is $resolve->{'winner'}"} |
97
|
|
|
|
|
|
|
elsif ( $resolve->{'tie'}) { |
98
|
|
|
|
|
|
|
my @tied = $resolve->{'tied'}->@*; |
99
|
|
|
|
|
|
|
say "Still tied between @tied." |
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
The Tie Breaking will be logged to the verbose log, any number of tied choices may be provided. |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
=cut |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
has 'TieBreakMethod' => ( |
107
|
|
|
|
|
|
|
is => 'rw', |
108
|
|
|
|
|
|
|
isa => 'Str', |
109
|
|
|
|
|
|
|
required => 0, |
110
|
|
|
|
|
|
|
); |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# This is only used for the precedence tiebreaker and fallback! |
113
|
|
|
|
|
|
|
has 'PrecedenceFile' => ( |
114
|
|
|
|
|
|
|
is => 'rw', |
115
|
|
|
|
|
|
|
isa => 'Str', |
116
|
|
|
|
|
|
|
required => 0, |
117
|
|
|
|
|
|
|
trigger => \&_triggercheckprecedence, |
118
|
|
|
|
|
|
|
); |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
has 'TieBreakerFallBackPrecedence' => ( |
121
|
|
|
|
|
|
|
is => 'rw', |
122
|
|
|
|
|
|
|
isa => 'Bool', |
123
|
|
|
|
|
|
|
default => 0, |
124
|
|
|
|
|
|
|
lazy => 0, |
125
|
|
|
|
|
|
|
trigger => \&_triggercheckprecedence, |
126
|
|
|
|
|
|
|
); |
127
|
|
|
|
|
|
|
|
128
|
65
|
|
|
65
|
|
4228
|
sub _triggercheckprecedence ( $I, $new, $old = undef ) { |
|
65
|
|
|
|
|
120
|
|
|
65
|
|
|
|
|
119
|
|
|
65
|
|
|
|
|
125
|
|
|
65
|
|
|
|
|
96
|
|
129
|
65
|
100
|
|
|
|
1856
|
unless ( $I->PrecedenceFile() ) { |
130
|
3
|
|
|
|
|
73
|
$I->PrecedenceFile('/tmp/precedence.txt'); |
131
|
3
|
|
|
|
|
63
|
$I->logt( "Generated FallBack TieBreaker Precedence Order: \n" |
132
|
|
|
|
|
|
|
. join( ', ', $I->CreatePrecedenceRandom() ) ); |
133
|
|
|
|
|
|
|
} |
134
|
65
|
|
|
|
|
1659
|
$I->{'PRECEDENCEORDER'} = undef; # clear cached if the file changes. |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
68
|
|
|
68
|
1
|
8279
|
sub TieBreakerGrandJunction ( $self, @tiedchoices ) { |
|
68
|
|
|
|
|
108
|
|
|
68
|
|
|
|
|
160
|
|
|
68
|
|
|
|
|
96
|
|
138
|
68
|
|
|
|
|
2004
|
my $ballots = $self->BallotSet()->{'ballots'}; |
139
|
68
|
|
|
|
|
160
|
my %current = ( map { $_ => 0 } @tiedchoices ); |
|
165
|
|
|
|
|
389
|
|
140
|
68
|
|
|
|
|
143
|
my $deepest = 0; |
141
|
68
|
|
|
|
|
271
|
for my $b ( keys $ballots->%* ) { |
142
|
734
|
|
|
|
|
1151
|
my $depth = scalar $ballots->{$b}{'votes'}->@*; |
143
|
734
|
100
|
|
|
|
1244
|
$deepest = $depth if $depth > $deepest; |
144
|
|
|
|
|
|
|
} |
145
|
68
|
|
|
|
|
154
|
my $round = 1; |
146
|
68
|
|
|
|
|
165
|
while ( $round <= $deepest ) { |
147
|
128
|
|
|
|
|
532
|
$self->logv("Tie Breaker Round: $round"); |
148
|
128
|
|
|
|
|
442
|
for my $b ( keys $ballots->%* ) { |
149
|
1350
|
100
|
|
|
|
2693
|
my $pick = $ballots->{$b}{'votes'}[ $round - 1 ] or next; |
150
|
990
|
100
|
|
|
|
1825
|
if ( defined $current{$pick} ) { |
151
|
222
|
|
|
|
|
407
|
$current{$pick} += $ballots->{$b}{'count'}; |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
} |
154
|
128
|
|
|
|
|
462
|
my $max = max( values %current ); |
155
|
128
|
|
|
|
|
467
|
for my $c ( sort @tiedchoices ) { |
156
|
296
|
|
|
|
|
971
|
$self->logv("\t$c: $current{$c}"); |
157
|
|
|
|
|
|
|
} |
158
|
128
|
|
|
|
|
417
|
for my $c ( sort @tiedchoices ) { |
159
|
296
|
100
|
|
|
|
603
|
if ( $current{$c} < $max ) { |
160
|
75
|
|
|
|
|
140
|
delete $current{$c}; |
161
|
75
|
|
|
|
|
218
|
$self->logv("Tie Breaker $c eliminated"); |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
} |
164
|
128
|
|
|
|
|
398
|
@tiedchoices = ( sort keys %current ); |
165
|
128
|
100
|
|
|
|
327
|
if ( 1 == @tiedchoices ) { |
166
|
52
|
|
|
|
|
194
|
$self->logv("Tie Breaker Won By: $tiedchoices[0]"); |
167
|
52
|
|
|
|
|
323
|
return { 'winner' => $tiedchoices[0], 'tie' => 0, 'tied' => [] }; |
168
|
|
|
|
|
|
|
} |
169
|
76
|
|
|
|
|
250
|
$round++; |
170
|
|
|
|
|
|
|
} |
171
|
16
|
100
|
|
|
|
546
|
if ( $self->TieBreakerFallBackPrecedence() ) { |
172
|
3
|
|
|
|
|
10
|
$self->logv('Applying Precedence fallback'); |
173
|
3
|
|
|
|
|
11
|
return $self->TieBreakerPrecedence(@tiedchoices); |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
else { |
176
|
13
|
|
|
|
|
88
|
return { 'winner' => 0, 'tie' => 1, 'tied' => \@tiedchoices }; |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=head1 TieBreaker |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
Implements some basic methods for resolving ties. The default value for IRV is 'all', and the default value for Matrix is 'none'. 'all' is inappropriate for Matrix, and 'none' is inappropriate for IRV. |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
my @keep = $Election->TieBreaker( $tiebreaker, $active, @tiedchoices ); |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
TieBreaker returns a list containing the winner, if the method is 'all' the list is empty, if 'none' the original @tiedchoices list is returned. If the TieBreaker is a tie there will be multiple elements. |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=head1 Precedence |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
Since many existing Elections Rules call for Random, and Vote::Count does not accept Random as the result will be different bewtween runs, Precedence allows the Administrators of an election to randomly or arbitrarily determine who will win ties before running Vote::Count. |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
The Precedence list takes the choices of the election one per line. Choices defeat any choice lower than them in the list. When Precedence is used an additional attribute must be specified for the Precedence List. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
my $Election = Vote::Count->new( |
195
|
|
|
|
|
|
|
BallotSet => read_ballots('somefile'), |
196
|
|
|
|
|
|
|
TieBreakMethod => 'precedence', |
197
|
|
|
|
|
|
|
PrecedenceFile => '/path/to/precedencefile'); |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
A compound Tie Breaker can be created with a precedence list and any other methods that create an ordered list (Top Count, Approval, Borda), that can then be used for a new Precdence File. This is slight different than using Precedence as a fall back as the methods are normally checked against the current state, this variant only used the initial state. |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=head2 CreatePrecedenceRandom |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
Creates a Predictable Psuedo Random Precedence file, and returns the list. Randomizes the choices using the number of ballots as the Random Seed for Perl's built in rand() function. For any given Ballot File, it will always return the same list. If the precedence filename argument is not given it defaults to '/tmp/precedence.txt'. This is the best solution to use where the Rules call for Random, in a large election the number of ballots cast will be sufficiently random, while anyone with access to Perl can reproduce the Precedence file. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
my @precedence = Vote::Count->new( BallotSet => read_ballots('somefile') ) |
206
|
|
|
|
|
|
|
->CreatePrecedenceRandom( '/tmp/precedence.txt'); |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=head2 TieBreakerFallBackPrecedence |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
This optional argument enables or disables using precedence as a fallback, generates /tmp/precedence.txt if no PrecedenceFile is specified. Default is off. |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=head1 UntieList |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
Sort a list in an order determined by a TieBreaker method, sorted in Descending Order. The TieBreaker must be a method that returns a RankCount object, Borda, TopCount, and Approval, Precedence. To guarrantee reliable resolution Precedence must be used or have been set for fallback. |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
my @orderedlosers = $Election->UntieList( 'Approval', @unorderedlosers ); |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=head1 UntieActive |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
Produces a precedence list of all the active choices in the election. Takes a first and optional second method name, if one of the methods is not Precedence, TieBreakerPrecedence must be true. The methods may be TopCount, Approval, or any other method that returns a RankCount object. Returns a RankCount object (with the OrderedList method enabled). |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
my $precedenceRankCount = $Election->UntieActive( 'TopCount', 'Approval'); |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
=cut |
225
|
|
|
|
|
|
|
|
226
|
34
|
|
|
34
|
|
46
|
sub _precedence_sort ( $I, @list ) { |
|
34
|
|
|
|
|
44
|
|
|
34
|
|
|
|
|
68
|
|
|
34
|
|
|
|
|
48
|
|
227
|
34
|
|
|
|
|
58
|
my %ordered = (); |
228
|
34
|
|
|
|
|
47
|
my $start = 0; |
229
|
34
|
100
|
|
|
|
74
|
if ( defined $I->{'PRECEDENCEORDER'} ) { |
230
|
26
|
|
|
|
|
119
|
%ordered = $I->{'PRECEDENCEORDER'}->%*; |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
else { |
233
|
8
|
|
|
|
|
220
|
for ( split /\n/, path( $I->PrecedenceFile() )->slurp() ) { |
234
|
82
|
|
|
|
|
2527
|
$_ =~ s/\s//g; #strip out any accidental white space |
235
|
82
|
|
|
|
|
189
|
$ordered{$_} = ++$start; |
236
|
|
|
|
|
|
|
} |
237
|
8
|
|
|
|
|
68
|
for my $c ( $I->GetChoices ) { |
238
|
82
|
50
|
|
|
|
139
|
unless ( defined $ordered{$c} ) { |
239
|
0
|
|
|
|
|
0
|
croak "Choice $c missing from precedence file\n"; |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
} |
242
|
8
|
|
|
|
|
26
|
$I->{'PRECEDENCEORDER'} = \%ordered; |
243
|
|
|
|
|
|
|
} |
244
|
34
|
|
|
|
|
123
|
my %L = map { $ordered{$_} => $_ } @list; |
|
138
|
|
|
|
|
292
|
|
245
|
34
|
|
|
|
|
132
|
return ( map { $L{$_} } ( sort { $a <=> $b } keys %L ) ); |
|
138
|
|
|
|
|
263
|
|
|
185
|
|
|
|
|
259
|
|
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
14
|
|
|
14
|
0
|
38
|
sub TieBreakerPrecedence ( $I, @tiedchoices ) { |
|
14
|
|
|
|
|
24
|
|
|
14
|
|
|
|
|
31
|
|
|
14
|
|
|
|
|
20
|
|
249
|
14
|
|
|
|
|
35
|
my @list = $I->_precedence_sort(@tiedchoices); |
250
|
14
|
|
|
|
|
172
|
return { 'winner' => $list[0], 'tie' => 0, 'tied' => [] }; |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
|
253
|
38
|
|
|
38
|
1
|
2629
|
sub CreatePrecedenceRandom ( $I, $outfile = '/tmp/precedence.txt' ) { |
|
38
|
|
|
|
|
81
|
|
|
38
|
|
|
|
|
88
|
|
|
38
|
|
|
|
|
64
|
|
254
|
38
|
|
|
|
|
206
|
my @choices = $I->GetActiveList(); |
255
|
38
|
|
|
|
|
122
|
my %randomized = (); |
256
|
38
|
|
|
|
|
1345
|
srand( $I->BallotSet()->{'votescast'} ); |
257
|
38
|
|
|
|
|
130
|
while (@choices) { |
258
|
325
|
|
|
|
|
532
|
my $next = shift @choices; |
259
|
325
|
|
|
|
|
592
|
my $random = int( rand(1000000) ); |
260
|
325
|
50
|
|
|
|
633
|
if ( defined $randomized{$random} ) { |
261
|
|
|
|
|
|
|
# collision, this choice needs to do again. |
262
|
0
|
|
|
|
|
0
|
unshift @choices, ($next); |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
else { |
265
|
325
|
|
|
|
|
888
|
$randomized{$random} = $next; |
266
|
|
|
|
|
|
|
} |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
my @precedence = |
269
|
38
|
|
|
|
|
284
|
( map { $randomized{$_} } sort { $a <=> $b } ( keys %randomized ) ); |
|
325
|
|
|
|
|
549
|
|
|
683
|
|
|
|
|
1015
|
|
270
|
38
|
|
|
|
|
229
|
path($outfile)->spew( join( "\n", @precedence ) . "\n" ); |
271
|
38
|
|
|
|
|
37124
|
return @precedence; |
272
|
|
|
|
|
|
|
} |
273
|
|
|
|
|
|
|
|
274
|
259
|
|
|
259
|
0
|
4126
|
sub TieBreaker ( $I, $tiebreaker, $active, @tiedchoices ) { |
|
259
|
|
|
|
|
386
|
|
|
259
|
|
|
|
|
413
|
|
|
259
|
|
|
|
|
364
|
|
|
259
|
|
|
|
|
526
|
|
|
259
|
|
|
|
|
352
|
|
275
|
39
|
|
|
39
|
|
378
|
no warnings 'uninitialized'; |
|
39
|
|
|
|
|
94
|
|
|
39
|
|
|
|
|
25579
|
|
276
|
259
|
100
|
|
|
|
620
|
if ( $tiebreaker eq 'none' ) { return @tiedchoices } |
|
157
|
|
|
|
|
530
|
|
277
|
102
|
100
|
|
|
|
260
|
if ( $tiebreaker eq 'all' ) { return () } |
|
17
|
|
|
|
|
60
|
|
278
|
85
|
|
|
|
|
197
|
my $choices_hashref = { map { $_ => 1 } @tiedchoices }; |
|
220
|
|
|
|
|
543
|
|
279
|
85
|
|
|
|
|
182
|
my $ranked = undef; |
280
|
85
|
100
|
|
|
|
439
|
if ( $tiebreaker eq 'borda' ) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
281
|
3
|
|
|
|
|
16
|
$ranked = $I->Borda($active); |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
elsif ( $tiebreaker eq 'borda_all' ) { |
284
|
3
|
|
|
|
|
73
|
$ranked = $I->Borda( $I->BallotSet()->{'choices'} ); |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
elsif ( $tiebreaker eq 'approval' ) { |
287
|
12
|
|
|
|
|
51
|
$ranked = $I->Approval($choices_hashref); |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
elsif ( $tiebreaker eq 'topcount' ) { |
290
|
2
|
|
|
|
|
17
|
$ranked = $I->TopCount($choices_hashref); |
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
elsif ( $tiebreaker eq 'grandjunction' ) { |
293
|
62
|
|
|
|
|
209
|
my $GJ = $I->TieBreakerGrandJunction(@tiedchoices); |
294
|
62
|
100
|
|
|
|
205
|
if ( $GJ->{'winner'} ) { return $GJ->{'winner'} } |
|
50
|
50
|
|
|
|
278
|
|
295
|
12
|
|
|
|
|
76
|
elsif ( $GJ->{'tie'} ) { return $GJ->{'tied'}->@* } |
296
|
0
|
|
|
|
|
0
|
else { croak "unexpected (or no) result from $tiebreaker!\n" } |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
elsif ( $tiebreaker eq 'precedence' ) { |
299
|
|
|
|
|
|
|
# The one nice thing about precedence is that there is always a winner. |
300
|
2
|
|
|
|
|
7
|
return $I->TieBreakerPrecedence(@tiedchoices)->{'winner'}; |
301
|
|
|
|
|
|
|
} |
302
|
1
|
|
|
|
|
128
|
else { croak "undefined tiebreak method $tiebreaker!\n" } |
303
|
20
|
|
|
|
|
40
|
my @highchoice = (); |
304
|
20
|
|
|
|
|
33
|
my $highest = 0; |
305
|
20
|
|
|
|
|
66
|
my $counted = $ranked->RawCount(); |
306
|
20
|
|
|
|
|
50
|
for my $c (@tiedchoices) { |
307
|
66
|
100
|
|
|
|
142
|
if ( $counted->{$c} > $highest ) { |
|
|
100
|
|
|
|
|
|
308
|
24
|
|
|
|
|
45
|
@highchoice = ($c); |
309
|
24
|
|
|
|
|
47
|
$highest = $counted->{$c}; |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
elsif ( $counted->{$c} == $highest ) { |
312
|
37
|
|
|
|
|
63
|
push @highchoice, $c; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
} |
315
|
20
|
|
|
|
|
106
|
my $terse = |
316
|
|
|
|
|
|
|
"Tie Breaker $tiebreaker: " |
317
|
|
|
|
|
|
|
. join( ', ', @tiedchoices ) |
318
|
|
|
|
|
|
|
. "\nwinner(s): " |
319
|
|
|
|
|
|
|
. join( ', ', @highchoice ); |
320
|
20
|
|
|
|
|
65
|
$I->{'last_tiebreaker'} = { |
321
|
|
|
|
|
|
|
'terse' => $terse, |
322
|
|
|
|
|
|
|
'verbose' => $ranked->RankTable(), |
323
|
|
|
|
|
|
|
}; |
324
|
20
|
100
|
|
|
|
86
|
if ( @highchoice > 1 ) { |
325
|
11
|
100
|
|
|
|
383
|
if ( $I->TieBreakerFallBackPrecedence() ) { |
326
|
8
|
|
|
|
|
33
|
return ( $I->TieBreakerPrecedence(@tiedchoices)->{'winner'} ); |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
} |
329
|
12
|
|
|
|
|
94
|
return (@highchoice); |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
28
|
|
|
28
|
0
|
2531
|
sub UnTieList ( $I, $method, @tied ) { |
|
28
|
|
|
|
|
42
|
|
|
28
|
|
|
|
|
39
|
|
|
28
|
|
|
|
|
53
|
|
|
28
|
|
|
|
|
35
|
|
333
|
39
|
|
|
39
|
|
351
|
no warnings 'uninitialized'; |
|
39
|
|
|
|
|
93
|
|
|
39
|
|
|
|
|
25113
|
|
334
|
28
|
100
|
|
|
|
82
|
return $I->_precedence_sort( @tied ) if ( lc($method) eq 'precedence' ); |
335
|
20
|
100
|
100
|
|
|
616
|
unless ( $I->TieBreakerFallBackPrecedence() or $I->TieBreakMethod eq 'precedence') { |
336
|
2
|
|
|
|
|
306
|
croak |
337
|
|
|
|
|
|
|
"TieBreakerFallBackPrecedence must be enabled or the specified method must be precedence to use UnTieList"; |
338
|
|
|
|
|
|
|
} |
339
|
18
|
50
|
|
|
|
47
|
return @tied if scalar(@tied) == 1; |
340
|
18
|
|
|
|
|
33
|
my @ordered = (); |
341
|
18
|
|
|
|
|
39
|
my %active = ( map { $_ => 1 } @tied ); |
|
52
|
|
|
|
|
99
|
|
342
|
|
|
|
|
|
|
# method should be topcount borda or approval which all take argument of active. |
343
|
18
|
|
|
|
|
71
|
my $RC = $I->$method(\%active)->HashByRank(); |
344
|
|
|
|
|
|
|
|
345
|
18
|
|
|
|
|
92
|
for my $level ( sort { $a <=> $b } ( keys $RC->%* ) ) { |
|
22
|
|
|
|
|
58
|
|
346
|
38
|
|
|
|
|
48
|
my @l = @{ $RC->{$level} }; |
|
38
|
|
|
|
|
66
|
|
347
|
|
|
|
|
|
|
my @suborder = |
348
|
38
|
|
|
|
|
84
|
( 1 == @{ $RC->{$level} } ) |
349
|
38
|
100
|
|
|
|
51
|
? @{ $RC->{$level} } |
|
27
|
|
|
|
|
41
|
|
350
|
|
|
|
|
|
|
: $I->_precedence_sort( @l ); |
351
|
38
|
|
|
|
|
80
|
push @ordered, @suborder; |
352
|
|
|
|
|
|
|
} |
353
|
18
|
|
|
|
|
78
|
return @ordered; |
354
|
|
|
|
|
|
|
} |
355
|
|
|
|
|
|
|
|
356
|
14
|
|
|
14
|
0
|
6250
|
sub UntieActive ( $I, $method1, $method2='precedence' ) { |
|
14
|
|
|
|
|
23
|
|
|
14
|
|
|
|
|
24
|
|
|
14
|
|
|
|
|
21
|
|
|
14
|
|
|
|
|
23
|
|
357
|
14
|
100
|
|
|
|
46
|
if ( lc($method1) eq 'precedence' ) { |
358
|
1
|
|
|
|
|
6
|
return Vote::Count::RankCount->newFromList( |
359
|
|
|
|
|
|
|
$I->_precedence_sort( $I->GetActiveList() )); |
360
|
|
|
|
|
|
|
} |
361
|
13
|
|
|
|
|
22
|
my $hasprecedence = 0; |
362
|
13
|
100
|
|
|
|
459
|
$hasprecedence = 1 if 1 == $I->TieBreakerFallBackPrecedence(); |
363
|
13
|
100
|
|
|
|
39
|
$hasprecedence = 1 if lc($method2) eq 'precedence'; |
364
|
13
|
100
|
|
|
|
28
|
unless ($hasprecedence) { |
365
|
1
|
|
|
|
|
185
|
croak |
366
|
|
|
|
|
|
|
"TieBreakerFallBackPrecedence must be enabled or one of the specified methods must be precedence to use UntieActive"; |
367
|
|
|
|
|
|
|
} |
368
|
12
|
|
|
|
|
23
|
my @ordered = (); |
369
|
12
|
|
|
|
|
52
|
my $first = $I->$method1()->HashByRank(); |
370
|
12
|
|
|
|
|
55
|
for my $level ( sort { $a <=> $b } ( keys %{$first} ) ) { |
|
100
|
|
|
|
|
147
|
|
|
12
|
|
|
|
|
52
|
|
371
|
64
|
|
|
|
|
84
|
my @l = @{ $first->{$level} }; |
|
64
|
|
|
|
|
103
|
|
372
|
|
|
|
|
|
|
my @suborder = |
373
|
64
|
|
|
|
|
130
|
( 1 == @{ $first->{$level} } ) |
374
|
64
|
100
|
|
|
|
90
|
? @{ $first->{$level} } |
|
49
|
|
|
|
|
77
|
|
375
|
|
|
|
|
|
|
: $I->UnTieList( $method2, @l ); |
376
|
64
|
|
|
|
|
129
|
push @ordered, @suborder; |
377
|
|
|
|
|
|
|
} |
378
|
12
|
|
|
|
|
48
|
return Vote::Count::RankCount->newFromList( @ordered ); |
379
|
|
|
|
|
|
|
} |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
1; |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
#FOOTER |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
=pod |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
BUG TRACKER |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
L<https://github.com/brainbuz/Vote-Count/issues> |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
AUTHOR |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
John Karr (BRAINBUZ) brainbuz@cpan.org |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
CONTRIBUTORS |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
Copyright 2019-2021 by John Karr (BRAINBUZ) brainbuz@cpan.org. |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
LICENSE |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
This module is released under the GNU Public License Version 3. See license file for details. For more information on this license visit L<http://fsf.org>. |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
SUPPORT |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
This software is provided as is, per the terms of the GNU Public License. Professional support and customisation services are available from the author. |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
=cut |
408
|
|
|
|
|
|
|
|