line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
39
|
|
|
39
|
|
25690
|
use strict; |
|
39
|
|
|
|
|
99
|
|
|
39
|
|
|
|
|
1354
|
|
2
|
39
|
|
|
39
|
|
219
|
use warnings; |
|
39
|
|
|
|
|
84
|
|
|
39
|
|
|
|
|
1284
|
|
3
|
39
|
|
|
39
|
|
947
|
use 5.024; |
|
39
|
|
|
|
|
132
|
|
4
|
39
|
|
|
39
|
|
208
|
use feature qw /postderef signatures/; |
|
39
|
|
|
|
|
81
|
|
|
39
|
|
|
|
|
4589
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
package Vote::Count::IRV; |
7
|
|
|
|
|
|
|
|
8
|
39
|
|
|
39
|
|
278
|
use namespace::autoclean; |
|
39
|
|
|
|
|
117
|
|
|
39
|
|
|
|
|
403
|
|
9
|
39
|
|
|
39
|
|
3919
|
use Moose::Role; |
|
39
|
|
|
|
|
96
|
|
|
39
|
|
|
|
|
414
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
with 'Vote::Count::TopCount'; |
12
|
|
|
|
|
|
|
with 'Vote::Count::TieBreaker'; |
13
|
|
|
|
|
|
|
|
14
|
39
|
|
|
39
|
|
208787
|
use Storable 3.15 'dclone'; |
|
39
|
|
|
|
|
775
|
|
|
39
|
|
|
|
|
2817
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
our $VERSION='2.01'; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 NAME |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
Vote::Count::IRV |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 VERSION 2.01 |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
=cut |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
# ABSTRACT: IRV Method for Vote::Count |
27
|
|
|
|
|
|
|
|
28
|
39
|
|
|
39
|
|
242
|
no warnings 'experimental'; |
|
39
|
|
|
|
|
80
|
|
|
39
|
|
|
|
|
1887
|
|
29
|
39
|
|
|
39
|
|
264
|
use List::Util qw( min max ); |
|
39
|
|
|
|
|
82
|
|
|
39
|
|
|
|
|
39073
|
|
30
|
|
|
|
|
|
|
#use Data::Dumper; |
31
|
|
|
|
|
|
|
# use Data::Printer; |
32
|
|
|
|
|
|
|
|
33
|
179
|
|
|
179
|
|
304
|
sub _ResolveTie ( $self, $active, $tiebreaker, @tiedchoices ) { |
|
179
|
|
|
|
|
256
|
|
|
179
|
|
|
|
|
249
|
|
|
179
|
|
|
|
|
296
|
|
|
179
|
|
|
|
|
364
|
|
|
179
|
|
|
|
|
247
|
|
34
|
179
|
100
|
|
|
|
588
|
return @tiedchoices if @tiedchoices == 1; |
35
|
|
|
|
|
|
|
my %high = |
36
|
44
|
|
|
|
|
275
|
map { $_ => 1 } $self->TieBreaker( $tiebreaker, $active, @tiedchoices ); |
|
56
|
|
|
|
|
161
|
|
37
|
44
|
100
|
|
|
|
147
|
if ( defined $self->{'last_tiebreaker'} ) { |
38
|
5
|
|
|
|
|
19
|
$self->logt( $self->{'last_tiebreaker'}{'terse'} ); |
39
|
5
|
|
|
|
|
17
|
$self->logv( $self->{'last_tiebreaker'}{'verbose'} ); |
40
|
5
|
|
|
|
|
11
|
$self->{'last_tiebreaker'} = undef; |
41
|
|
|
|
|
|
|
} |
42
|
44
|
100
|
|
|
|
146
|
if ( @tiedchoices == scalar( keys %high ) ) { return @tiedchoices } |
|
11
|
|
|
|
|
54
|
|
43
|
|
|
|
|
|
|
# tiebreaker returns winner, we want losers! |
44
|
|
|
|
|
|
|
# use map to remove winner(s) from @tiedchoices. |
45
|
|
|
|
|
|
|
# warning about sort interpreted as function fixed |
46
|
|
|
|
|
|
|
my @low = sort map { |
47
|
33
|
100
|
|
|
|
68
|
if ( $high{$_} ) { } |
|
88
|
|
|
|
|
175
|
|
48
|
70
|
|
|
|
|
154
|
else { $_ } |
49
|
|
|
|
|
|
|
} @tiedchoices; |
50
|
33
|
|
|
|
|
101
|
return @low; |
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
|
53
|
48
|
|
|
48
|
1
|
14685
|
sub RunIRV ( $self, $active = undef, $tiebreaker = undef ) { |
|
48
|
|
|
|
|
93
|
|
|
48
|
|
|
|
|
91
|
|
|
48
|
|
|
|
|
91
|
|
|
48
|
|
|
|
|
80
|
|
54
|
48
|
|
|
|
|
243
|
$self->_IRVDO( active => $active, tiebreaker => $tiebreaker ); |
55
|
|
|
|
|
|
|
} |
56
|
|
|
|
|
|
|
|
57
|
4
|
|
|
4
|
1
|
42
|
sub RunBTRIRV ( $self, %args ) { |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
8
|
|
58
|
4
|
100
|
|
|
|
17
|
my $ranking2 = $args{'ranking2'} ? $args{'ranking2'} : 'precedence'; |
59
|
4
|
|
|
|
|
22
|
$self->_IRVDO( 'btr' => 1, ranking2 => $ranking2 ); |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
# RunIRV needed a new argument and was a long established method, |
63
|
|
|
|
|
|
|
# so now it hands everything off to this private method that uses |
64
|
|
|
|
|
|
|
# named arguments. |
65
|
52
|
|
|
52
|
|
89
|
sub _IRVDO ( $self, %args ) { |
|
52
|
|
|
|
|
101
|
|
|
52
|
|
|
|
|
212
|
|
|
52
|
|
|
|
|
110
|
|
66
|
52
|
|
|
|
|
148
|
local $" = ', '; |
67
|
52
|
100
|
|
|
|
3307
|
my $active = defined $args{'active'} ? dclone $args{'active'} : dclone $self->Active() ; |
68
|
52
|
|
|
|
|
161
|
my $tiebreaker = do { |
69
|
52
|
100
|
|
|
|
590
|
if ( defined $args{'tiebreaker'} ) { $args{'tiebreaker'} } |
|
39
|
100
|
|
|
|
155
|
|
70
|
5
|
|
|
|
|
128
|
elsif ( defined $self->TieBreakMethod() ) { $self->TieBreakMethod() } |
71
|
8
|
|
|
|
|
23
|
else { 'all' } |
72
|
|
|
|
|
|
|
}; |
73
|
52
|
|
|
|
|
112
|
my $roundctr = 0; |
74
|
52
|
|
|
|
|
95
|
my $maxround = scalar( keys %{$active} ); |
|
52
|
|
|
|
|
168
|
|
75
|
|
|
|
|
|
|
$self->logt( "Instant Runoff Voting", |
76
|
52
|
|
|
|
|
145
|
'Choices: ', join( ', ', ( sort keys %{$active} ) ) ); |
|
52
|
|
|
|
|
598
|
|
77
|
|
|
|
|
|
|
# forever loop normally ends with return from $majority |
78
|
|
|
|
|
|
|
# a tie should be detected and also generate a |
79
|
|
|
|
|
|
|
# return from the else loop. |
80
|
|
|
|
|
|
|
# if something goes wrong roundcountr/maxround |
81
|
|
|
|
|
|
|
# will generate exception. |
82
|
|
|
|
|
|
|
IRVLOOP: |
83
|
52
|
|
|
|
|
140
|
until (0) { |
84
|
251
|
|
|
|
|
517
|
$roundctr++; |
85
|
251
|
50
|
|
|
|
675
|
die "IRVLOOP infinite stopped at $roundctr" if $roundctr > $maxround; |
86
|
251
|
|
|
|
|
1093
|
my $round = $self->TopCount($active); |
87
|
251
|
|
|
|
|
1268
|
$self->logv( '---', "IRV Round $roundctr", $round->RankTable() ); |
88
|
251
|
|
|
|
|
1745
|
my $majority = $self->EvaluateTopCountMajority($round); |
89
|
251
|
100
|
|
|
|
1002
|
if ( defined $majority->{'winner'} ) { |
|
|
100
|
|
|
|
|
|
90
|
44
|
|
|
|
|
602
|
return $majority; |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
elsif ( $args{'btr'}) { |
93
|
|
|
|
|
|
|
my $br = $self->BottomRunOff( |
94
|
28
|
|
|
|
|
220
|
'active' => $active, 'ranking2' => $args{'ranking2'} ); |
95
|
28
|
|
|
|
|
171
|
$self->logv( $br->{'runoff'}); |
96
|
28
|
|
|
|
|
99
|
$self->logt( "Eliminating: ${\ $br->{'eliminate'} }" ); |
|
28
|
|
|
|
|
189
|
|
97
|
28
|
|
|
|
|
396
|
delete $active->{ $br->{'eliminate'} }; |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
else { #-- |
100
|
179
|
|
|
|
|
612
|
my @bottom = $self->_ResolveTie( $active, $tiebreaker, $round->ArrayBottom()->@* ); |
101
|
179
|
100
|
|
|
|
309
|
if ( scalar(@bottom) == scalar( keys %{$active} ) ) { |
|
179
|
|
|
|
|
545
|
|
102
|
|
|
|
|
|
|
# if there is a tie at the end, the finalists should |
103
|
|
|
|
|
|
|
# be both top and bottom and the active set. |
104
|
8
|
|
|
|
|
48
|
$self->logt( "Tied: @bottom" ); |
105
|
8
|
|
|
|
|
91
|
return { tie => 1, tied => \@bottom, winner => 0 }; |
106
|
|
|
|
|
|
|
} |
107
|
171
|
|
|
|
|
1006
|
$self->logt( "Eliminating: @bottom" ); |
108
|
171
|
|
|
|
|
420
|
for my $b (@bottom) { |
109
|
227
|
|
|
|
|
1523
|
delete $active->{$b}; |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
} #-- |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
1; |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=pod |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=head1 IRV |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
Implements Instant Runoff Voting for Vote::Count. |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=head1 SYNOPSIS |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
use Vote::Count::Method; |
126
|
|
|
|
|
|
|
use Vote::Count::ReadBallots 'read_ballots'; |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
my $Election = Vote::Count::->new( |
129
|
|
|
|
|
|
|
BallotSet => read_ballots('%path_to_my_ballots'), |
130
|
|
|
|
|
|
|
TieBreakMethod => 'grandjunction'); |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
my $result = $Election->RunIRV(); |
133
|
|
|
|
|
|
|
my $winner = $result->{'winner'}; |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
say $Election->logv(); # Print the full Log. |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=head1 Method Summary |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Instant Runoff Voting Looks for a Majority Winner. If one isn't present the choice with the lowest Top Count is removed. |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
Instant Runoff Voting is easy to count by hand and meets the Later Harm and Condorcet Loser Criteria. It, unfortunately, fails a large number of consistency criteria; the order of candidate dropping matters and small changes to the votes of non-winning choices that result in changes to the dropping order can change the outcome. |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
Instant Runoff Voting is also known as Alternative Vote and as the Hare Method. |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=head2 Tie Handling |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
There is no standard accepted method for IRV tie resolution, Eliminate All is a common one and the default. |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
Returns a tie when all of the remaining choices are in a tie. |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
An optional value to RunIRV is to specify tiebreaker, see L<Vote::Count::TieBreaker>. |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
=head2 RunIRV |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
$Election->RunIRV(); |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
$Election->RunIRV( $active ) |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
$Election->RunIRV( $active, 'approval' ) |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
Runs IRV on the provided Ballot Set. Takes an optional parameter of $active which is a hashref for which the keys are the currently active choices. |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
Returns results in a hashref which will be the results of Vote::Count::TopCount->EvaluateTopCountMajority, if there is no winner hash will instead be: |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
tie => [true or false], |
166
|
|
|
|
|
|
|
tied => [ array of tied choices ], |
167
|
|
|
|
|
|
|
winner => a false value |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
Supports the Vote::Count logt, logv, and logd methods for providing details of the method. |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=head2 RunBTRIRV |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
This is the simplest modification to IRV which meets the Condorcet Winner Criteria. Instead of eliminating the low choice, the lowest two choices enter a virtual runoff, eliminating the loser. This is the easiest possible Hand Count Condorcet method, there will always be fewer pairings than choices. This method fails LNH, when there is no Condorcet Winner LNH can come into play for each runoff. BTR IRV will only eliminate a member of the Smith Set when both members of the runoff are in it, so it can never eliminate the final member of the Smith Set. BTR IRV meets both Condorcet Criteria and the Smith Criteria. |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
my $result = $Election->RunBTRIRV(); |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
RunBTRIRV has no optional arguments. Choices are ordered by TopCount, ties for position are decided by Precedence. It is mandatory that either the TieBreakMethod is Precedence or TieBreakerFallBackPrecedence is True. See UnTieList in L<Vote::Count::TieBreaker>. |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
The returned values and logging are the same as for RunIRV. |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
=cut |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
#FOOTER |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=pod |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
BUG TRACKER |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
L<https://github.com/brainbuz/Vote-Count/issues> |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
AUTHOR |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
John Karr (BRAINBUZ) brainbuz@cpan.org |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
CONTRIBUTORS |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
Copyright 2019-2021 by John Karr (BRAINBUZ) brainbuz@cpan.org. |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
LICENSE |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
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>. |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
SUPPORT |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
This software is provided as is, per the terms of the GNU Public License. Professional support and customisation services are available from the author. |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=cut |
208
|
|
|
|
|
|
|
|