line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
39
|
|
|
39
|
|
27694
|
use strict; |
|
39
|
|
|
|
|
108
|
|
|
39
|
|
|
|
|
1444
|
|
2
|
39
|
|
|
39
|
|
231
|
use warnings; |
|
39
|
|
|
|
|
100
|
|
|
39
|
|
|
|
|
1336
|
|
3
|
39
|
|
|
39
|
|
1037
|
use 5.024; |
|
39
|
|
|
|
|
1473
|
|
4
|
39
|
|
|
39
|
|
278
|
use feature qw /postderef signatures/; |
|
39
|
|
|
|
|
95
|
|
|
39
|
|
|
|
|
5031
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
package Vote::Count::IRV; |
7
|
|
|
|
|
|
|
|
8
|
39
|
|
|
39
|
|
351
|
use namespace::autoclean; |
|
39
|
|
|
|
|
102
|
|
|
39
|
|
|
|
|
457
|
|
9
|
39
|
|
|
39
|
|
4392
|
use Moose::Role; |
|
39
|
|
|
|
|
129
|
|
|
39
|
|
|
|
|
1331
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
with 'Vote::Count::TopCount'; |
12
|
|
|
|
|
|
|
with 'Vote::Count::TieBreaker'; |
13
|
|
|
|
|
|
|
|
14
|
39
|
|
|
39
|
|
230760
|
use Storable 3.15 'dclone'; |
|
39
|
|
|
|
|
796
|
|
|
39
|
|
|
|
|
2962
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
our $VERSION='2.00'; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 NAME |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
Vote::Count::IRV |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 VERSION 2.00 |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
=cut |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
# ABSTRACT: IRV Method for Vote::Count |
27
|
|
|
|
|
|
|
|
28
|
39
|
|
|
39
|
|
283
|
no warnings 'experimental'; |
|
39
|
|
|
|
|
87
|
|
|
39
|
|
|
|
|
2254
|
|
29
|
39
|
|
|
39
|
|
274
|
use List::Util qw( min max ); |
|
39
|
|
|
|
|
108
|
|
|
39
|
|
|
|
|
36039
|
|
30
|
|
|
|
|
|
|
#use Data::Dumper; |
31
|
|
|
|
|
|
|
|
32
|
179
|
|
|
179
|
|
315
|
sub _ResolveTie ( $self, $active, $tiebreaker, @tiedchoices ) { |
|
179
|
|
|
|
|
265
|
|
|
179
|
|
|
|
|
276
|
|
|
179
|
|
|
|
|
292
|
|
|
179
|
|
|
|
|
350
|
|
|
179
|
|
|
|
|
257
|
|
33
|
179
|
100
|
|
|
|
630
|
return @tiedchoices if @tiedchoices == 1; |
34
|
|
|
|
|
|
|
my %high = |
35
|
44
|
|
|
|
|
241
|
map { $_ => 1 } $self->TieBreaker( $tiebreaker, $active, @tiedchoices ); |
|
56
|
|
|
|
|
151
|
|
36
|
44
|
100
|
|
|
|
136
|
if ( defined $self->{'last_tiebreaker'} ) { |
37
|
5
|
|
|
|
|
23
|
$self->logt( $self->{'last_tiebreaker'}{'terse'} ); |
38
|
5
|
|
|
|
|
25
|
$self->logv( $self->{'last_tiebreaker'}{'verbose'} ); |
39
|
5
|
|
|
|
|
17
|
$self->{'last_tiebreaker'} = undef; |
40
|
|
|
|
|
|
|
} |
41
|
44
|
100
|
|
|
|
150
|
if ( @tiedchoices == scalar( keys %high ) ) { return @tiedchoices } |
|
11
|
|
|
|
|
48
|
|
42
|
|
|
|
|
|
|
# tiebreaker returns winner, we want losers! |
43
|
|
|
|
|
|
|
# use map to remove winner(s) from @tiedchoices. |
44
|
|
|
|
|
|
|
# warning about sort interpreted as function fixed |
45
|
|
|
|
|
|
|
my @low = sort map { |
46
|
33
|
100
|
|
|
|
78
|
if ( $high{$_} ) { } |
|
88
|
|
|
|
|
179
|
|
47
|
70
|
|
|
|
|
164
|
else { $_ } |
48
|
|
|
|
|
|
|
} @tiedchoices; |
49
|
33
|
|
|
|
|
116
|
return @low; |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
|
52
|
48
|
|
|
48
|
1
|
16684
|
sub RunIRV ( $self, $active = undef, $tiebreaker = undef ) { |
|
48
|
|
|
|
|
114
|
|
|
48
|
|
|
|
|
99
|
|
|
48
|
|
|
|
|
87
|
|
|
48
|
|
|
|
|
80
|
|
53
|
|
|
|
|
|
|
# external $active should not be changed. |
54
|
48
|
100
|
|
|
|
136
|
if ( defined $active ) { $active = dclone $active } |
|
34
|
|
|
|
|
2404
|
|
55
|
|
|
|
|
|
|
# Object's active is altered by IRV. |
56
|
14
|
|
|
|
|
446
|
else { $active = dclone $self->Active() } |
57
|
48
|
100
|
|
|
|
222
|
unless ( defined $tiebreaker ) { |
58
|
9
|
100
|
|
|
|
303
|
if ( defined $self->TieBreakMethod() ) { |
59
|
1
|
|
|
|
|
28
|
$tiebreaker = $self->TieBreakMethod(); |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
else { |
62
|
8
|
|
|
|
|
19
|
$tiebreaker = 'all'; |
63
|
|
|
|
|
|
|
} |
64
|
|
|
|
|
|
|
} |
65
|
48
|
|
|
|
|
118
|
my $roundctr = 0; |
66
|
48
|
|
|
|
|
88
|
my $maxround = scalar( keys %{$active} ); |
|
48
|
|
|
|
|
144
|
|
67
|
|
|
|
|
|
|
$self->logt( "Instant Runoff Voting", |
68
|
48
|
|
|
|
|
127
|
'Choices: ', join( ', ', ( sort keys %{$active} ) ) ); |
|
48
|
|
|
|
|
571
|
|
69
|
|
|
|
|
|
|
# forever loop normally ends with return from $majority |
70
|
|
|
|
|
|
|
# a tie should be detected and also generate a |
71
|
|
|
|
|
|
|
# return from the else loop. |
72
|
|
|
|
|
|
|
# if something goes wrong roundcountr/maxround |
73
|
|
|
|
|
|
|
# will generate exception. |
74
|
|
|
|
|
|
|
IRVLOOP: |
75
|
48
|
|
|
|
|
134
|
until (0) { |
76
|
219
|
|
|
|
|
435
|
$roundctr++; |
77
|
219
|
50
|
|
|
|
553
|
die "IRVLOOP infinite stopped at $roundctr" if $roundctr > $maxround; |
78
|
219
|
|
|
|
|
852
|
my $round = $self->TopCount($active); |
79
|
219
|
|
|
|
|
1011
|
$self->logv( '---', "IRV Round $roundctr", $round->RankTable() ); |
80
|
219
|
|
|
|
|
1480
|
my $majority = $self->EvaluateTopCountMajority($round); |
81
|
219
|
100
|
|
|
|
676
|
if ( defined $majority->{'winner'} ) { |
82
|
40
|
|
|
|
|
403
|
return $majority; |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
else { |
85
|
179
|
|
|
|
|
630
|
my @bottom = |
86
|
|
|
|
|
|
|
$self->_ResolveTie( $active, $tiebreaker, $round->ArrayBottom()->@* ); |
87
|
179
|
100
|
|
|
|
311
|
if ( scalar(@bottom) == scalar( keys %{$active} ) ) { |
|
179
|
|
|
|
|
535
|
|
88
|
|
|
|
|
|
|
# if there is a tie at the end, the finalists should |
89
|
|
|
|
|
|
|
# be both top and bottom and the active set. |
90
|
8
|
|
|
|
|
62
|
$self->logt( "Tied: " . join( ', ', @bottom ) ); |
91
|
8
|
|
|
|
|
96
|
return { tie => 1, tied => \@bottom, winner => 0 }; |
92
|
|
|
|
|
|
|
} |
93
|
171
|
|
|
|
|
885
|
$self->logv( "Eliminating: " . join( ', ', @bottom ) ); |
94
|
171
|
|
|
|
|
438
|
for my $b (@bottom) { |
95
|
227
|
|
|
|
|
1549
|
delete $active->{$b}; |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
1; |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=pod |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=head1 IRV |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
Implements Instant Runoff Voting for Vote::Count. |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=head1 SYNOPSIS |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
use Vote::Count::Method; |
112
|
|
|
|
|
|
|
use Vote::Count::ReadBallots 'read_ballots'; |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
my $Election = Vote::Count::->new( |
115
|
|
|
|
|
|
|
BallotSet => read_ballots('%path_to_my_ballots'), |
116
|
|
|
|
|
|
|
TieBreakMethod => 'grandjunction'); |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
my $result = $Election->RunIRV(); |
119
|
|
|
|
|
|
|
my $winner = $result->{'winner'}; |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
say $Election->logv(); # Print the full Log. |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=head1 Method Summary |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
Instant Runoff Voting Looks for a Majority Winner. If one isn't present the choice with the lowest Top Count is removed. |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
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. |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
Instant Runoff Voting is also known as Alternative Vote and as the Hare Method. |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=head2 Tie Handling |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
There is no standard accepted method for IRV tie resolution, Eliminate All is a common one and the default. |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
Returns a tie when all of the remaining choices are in a tie. |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
An optional value to RunIRV is to specify tiebreaker, see TieBreaker. |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=head2 RunIRV |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
$Election->RunIRV(); |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
$Election->RunIRV( $active ) |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
$Election->RunIRV( $active, 'approval' ) |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
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. |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
Returns results in a hashref which will be the results of Vote::Count::TopCount->EvaluateTopCountMajority, if there is no winner hash will instead be: |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
tie => [true or false], |
152
|
|
|
|
|
|
|
tied => [ array of tied choices ], |
153
|
|
|
|
|
|
|
winner => a false value |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
Supports the Vote::Count logt, logv, and logd methods for providing details of the method. |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=head2 TieBreaker |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
Uses TieBreaker from the TieBreaker Role. The default is 'all', which is to not break ties. 'none' the default for the Matrix (Condorcet) Object should not be used for IRV. |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
All was chosen as the module default because it is Later Harm safe. Modified Grand Junction is extremely resolvable and simple. |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
In the event that the tie-breaker returns a tie eliminate all that remain tied is used, unless that would eliminate all choices, in which case the election returns a tie. |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=cut |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
#FOOTER |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=pod |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
BUG TRACKER |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
L<https://github.com/brainbuz/Vote-Count/issues> |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
AUTHOR |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
John Karr (BRAINBUZ) brainbuz@cpan.org |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
CONTRIBUTORS |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
Copyright 2019-2021 by John Karr (BRAINBUZ) brainbuz@cpan.org. |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
LICENSE |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
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>. |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
SUPPORT |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
This software is provided as is, per the terms of the GNU Public License. Professional support and customisation services are available from the author. |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=cut |
192
|
|
|
|
|
|
|
|