line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
39
|
|
|
39
|
|
294
|
use strict; |
|
39
|
|
|
|
|
84
|
|
|
39
|
|
|
|
|
1302
|
|
2
|
39
|
|
|
39
|
|
194
|
use warnings; |
|
39
|
|
|
|
|
75
|
|
|
39
|
|
|
|
|
996
|
|
3
|
39
|
|
|
39
|
|
735
|
use 5.024; |
|
39
|
|
|
|
|
358
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
package Vote::Count::RankCount; |
6
|
|
|
|
|
|
|
|
7
|
39
|
|
|
39
|
|
205
|
use feature qw /postderef signatures/; |
|
39
|
|
|
|
|
79
|
|
|
39
|
|
|
|
|
3653
|
|
8
|
39
|
|
|
39
|
|
256
|
no warnings 'experimental'; |
|
39
|
|
|
|
|
75
|
|
|
39
|
|
|
|
|
1907
|
|
9
|
39
|
|
|
39
|
|
473
|
use List::Util qw( min max sum); |
|
39
|
|
|
|
|
94
|
|
|
39
|
|
|
|
|
3461
|
|
10
|
39
|
|
|
39
|
|
15390
|
use Vote::Count::TextTableTiny qw/generate_table/; |
|
39
|
|
|
|
|
118
|
|
|
39
|
|
|
|
|
2615
|
|
11
|
39
|
|
|
39
|
|
13158
|
use Sort::Hash; |
|
39
|
|
|
|
|
20866
|
|
|
39
|
|
|
|
|
57465
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our $VERSION='2.01'; |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 NAME |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
Vote::Count::RankCount |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 VERSION 2.01 |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=cut |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
# ABSTRACT: RankCount object for Vote::Count. Toolkit for vote counting. |
24
|
|
|
|
|
|
|
|
25
|
795
|
|
|
795
|
|
1245
|
sub _RankResult ( $rawcount ) { |
|
795
|
|
|
|
|
1177
|
|
|
795
|
|
|
|
|
1111
|
|
26
|
795
|
|
|
|
|
3497
|
my %rc = ( $rawcount->%* ); # destructive process needs to use a copy. |
27
|
795
|
|
|
|
|
1700
|
my %ordered = (); |
28
|
795
|
|
|
|
|
1155
|
my %byrank = (); |
29
|
795
|
|
|
|
|
1289
|
my $pos = 0; |
30
|
795
|
|
|
|
|
1405
|
my $maxpos = scalar( keys %rc ); |
31
|
795
|
|
|
|
|
1978
|
while ( 0 < scalar( keys %rc ) ) { |
32
|
3749
|
|
|
|
|
4631
|
$pos++; |
33
|
3749
|
|
|
|
|
6934
|
my @vrc = values %rc; |
34
|
3749
|
|
|
|
|
7164
|
my $max = max @vrc; |
35
|
3749
|
|
|
|
|
7188
|
for my $k ( keys %rc ) { |
36
|
19632
|
100
|
|
|
|
30276
|
if ( $rc{$k} == $max ) { |
37
|
5213
|
|
|
|
|
7669
|
$ordered{$k} = $pos; |
38
|
5213
|
|
|
|
|
6812
|
delete $rc{$k}; |
39
|
5213
|
100
|
|
|
|
8480
|
if ( defined $byrank{$pos} ) { |
40
|
1464
|
|
|
|
|
1808
|
push @{ $byrank{$pos} }, $k; |
|
1464
|
|
|
|
|
2971
|
|
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
else { |
43
|
3749
|
|
|
|
|
8704
|
$byrank{$pos} = [$k]; |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
} |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
# uncoverable branch true |
48
|
3749
|
50
|
|
|
|
9249
|
die "Vote::Count::RankCount::Rank in infinite loop\n" |
49
|
|
|
|
|
|
|
if $pos > $maxpos; |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
# %byrank[1] is arrayref of 1st position, |
52
|
|
|
|
|
|
|
# $pos still has last position filled, %byrank{$pos} is the last place. |
53
|
|
|
|
|
|
|
# sometimes byranks came in as var{byrank...} deref and reref fixes this |
54
|
|
|
|
|
|
|
# although it would be better if I understood why it happened. |
55
|
|
|
|
|
|
|
# It is useful to sort the arrays anyway, for display they would likely be |
56
|
|
|
|
|
|
|
# sorted anyway. For testing it makes the element order predictable. |
57
|
|
|
|
|
|
|
|
58
|
795
|
|
|
|
|
2151
|
for my $O ( keys %byrank ) { |
59
|
3749
|
|
|
|
|
8685
|
$byrank{$O} = [ sort $byrank{$O}->@* ]; |
60
|
|
|
|
|
|
|
} |
61
|
795
|
|
|
|
|
1392
|
my @top = @{ $byrank{1} }; |
|
795
|
|
|
|
|
1905
|
|
62
|
795
|
|
|
|
|
1250
|
my @bottom = @{ $byrank{$pos} }; |
|
795
|
|
|
|
|
1611
|
|
63
|
795
|
100
|
|
|
|
1835
|
my $tie = scalar(@top) > 1 ? 1 : 0; |
64
|
|
|
|
|
|
|
return { |
65
|
795
|
|
|
|
|
5117
|
'rawcount' => $rawcount, |
66
|
|
|
|
|
|
|
'ordered' => \%ordered, |
67
|
|
|
|
|
|
|
'byrank' => \%byrank, |
68
|
|
|
|
|
|
|
'top' => \@top, |
69
|
|
|
|
|
|
|
'bottom' => \@bottom, |
70
|
|
|
|
|
|
|
'tie' => $tie, |
71
|
|
|
|
|
|
|
}; |
72
|
|
|
|
|
|
|
} |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=head2 Rank |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
Takes a single argument of a hashref containing Choices as Keys and Votes as Values. Returns an Object. This method is also aliased as new. |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=head2 newFromList |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
Takes an ordered list and returns a RankCount Object where the RawCount values are zero minus the position: Item 3 in the list will have -3 votes while Item 1 will have -1. |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
my $ordered_rank_count = Vote::Count::RankCount->newFromList( @ordered_list ); |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=cut |
85
|
|
|
|
|
|
|
|
86
|
794
|
|
|
794
|
1
|
1992
|
sub Rank ( $class, $rawcount ) { |
|
794
|
|
|
|
|
1276
|
|
|
794
|
|
|
|
|
1175
|
|
|
794
|
|
|
|
|
1011
|
|
87
|
794
|
|
|
|
|
1906
|
my $I = _RankResult($rawcount); |
88
|
794
|
|
|
|
|
8626
|
return bless $I, $class; |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
|
91
|
1
|
|
|
1
|
0
|
6
|
sub new ( $class, $rawcount ) { |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
2
|
|
92
|
1
|
|
|
|
|
3
|
my $I = _RankResult($rawcount); |
93
|
1
|
|
|
|
|
3
|
return bless $I, $class; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
39
|
|
|
39
|
1
|
659
|
sub newFromList ( @list ) { |
|
39
|
|
|
|
|
106
|
|
|
39
|
|
|
|
|
62
|
|
97
|
39
|
|
|
|
|
62
|
shift @list; |
98
|
39
|
|
|
|
|
126
|
my $pos = 0; |
99
|
|
|
|
|
|
|
my $newobj = Vote::Count::RankCount->Rank({ |
100
|
39
|
|
|
|
|
82
|
map { $_ => --$pos } @list} ); |
|
233
|
|
|
|
|
461
|
|
101
|
39
|
|
|
|
|
122
|
$newobj->{'orderedlist'} = \@list; |
102
|
39
|
|
|
|
|
198
|
return $newobj; |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=head2 Methods |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
The following Methods are available from RankCount Objects. |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=head3 RawCount |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
Returns the original HashRef used for Object Creation. |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=head3 HashWithOrder |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
Returns a HashRef with the Choices as Keys and the position of the choice, the value for the Leader would be 1 and the Third Place Choice would be 3. If choices are tied they will share the same value for their position. |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=head3 HashByRank |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
Returns a HashRef where the keys are numbers and the values an ArrayRef of the Choices in that position. The ArrayRefs are sorted alphanumerically. |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=head3 ArrayTop, ArrayBottom |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
Returns an ArrayRef of the Choices in the Top or Bottom Positions. |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=head3 OrderedList |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
Returns the array that was to create the RankCount object if it was created from a List. Returns an exception if the object was created from a HashRef, because RankCount does not deal with ties. Returning a list with ties resolved by randomness or a sort would not be correct. |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=head3 CountVotes |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
Returns the number of votes in the RawCount. This is not the same as the votes in the BallotSet from which that was derived. For TopCount it is the number of non-exhausted ballots in the round that generated RawCount, for Approval and Boorda it is probably not useful. |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=head3 Leader |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
Returns a HashRef with the keys tie, tied, winner where winner is the winner, tie is true or false and tied is an array ref of the choices in the tie. |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=head3 RankTable |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Generates a MarkDown formatted table. |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
say $Election->TopCount->RankTable; |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
| Rank | Choice | Votes | |
144
|
|
|
|
|
|
|
|------|------------|-------| |
145
|
|
|
|
|
|
|
| 1 | VANILLA | 7 | |
146
|
|
|
|
|
|
|
| 2 | MINTCHIP | 5 | |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=head3 RankTableWeighted ($votevalue) |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
Ranktable for use with weighted votes. Displays both the Vote Value and the Vote Total (rounded to two places). Requires Vote Value as an argument. |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
say $WeightedElection->TopCount->RankTableWeighted( 100 ); |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
| Rank | Choice | Votes | VoteValue | |
155
|
|
|
|
|
|
|
|:-----|:-----------|------:|----------:| |
156
|
|
|
|
|
|
|
| 1 | VANILLA | 7.00 | 700 | |
157
|
|
|
|
|
|
|
| 2 | MINTCHIP | 5.00 | 500 | |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
=cut |
160
|
|
|
|
|
|
|
|
161
|
502
|
|
|
502
|
1
|
6792
|
sub RawCount ( $I ) { return $I->{'rawcount'} } |
|
502
|
|
|
|
|
835
|
|
|
502
|
|
|
|
|
690
|
|
|
502
|
|
|
|
|
1421
|
|
162
|
51
|
|
|
51
|
1
|
1616
|
sub HashWithOrder ( $I ) { return $I->{'ordered'} } |
|
51
|
|
|
|
|
91
|
|
|
51
|
|
|
|
|
76
|
|
|
51
|
|
|
|
|
194
|
|
163
|
58
|
|
|
58
|
1
|
1048
|
sub HashByRank ( $I ) { return $I->{'byrank'} } |
|
58
|
|
|
|
|
126
|
|
|
58
|
|
|
|
|
102
|
|
|
58
|
|
|
|
|
257
|
|
164
|
35
|
|
|
35
|
1
|
5117
|
sub ArrayTop ( $I ) { return $I->{'top'} } |
|
35
|
|
|
|
|
56
|
|
|
35
|
|
|
|
|
54
|
|
|
35
|
|
|
|
|
110
|
|
165
|
194
|
|
|
194
|
1
|
336
|
sub ArrayBottom ( $I ) { return $I->{'bottom'} } |
|
194
|
|
|
|
|
265
|
|
|
194
|
|
|
|
|
262
|
|
|
194
|
|
|
|
|
726
|
|
166
|
318
|
|
|
318
|
1
|
993
|
sub CountVotes ($I) { return sum( values $I->{'rawcount'}->%* ) } |
|
318
|
|
|
|
|
510
|
|
|
318
|
|
|
|
|
493
|
|
|
318
|
|
|
|
|
1926
|
|
167
|
|
|
|
|
|
|
|
168
|
5
|
|
|
5
|
1
|
1018
|
sub OrderedList ($I) { |
|
5
|
|
|
|
|
8
|
|
|
5
|
|
|
|
|
7
|
|
169
|
5
|
100
|
|
|
|
44
|
return $I->{'orderedlist'}->@* if defined $I->{'orderedlist'}; |
170
|
1
|
|
|
|
|
8
|
die "OrderedList may only be used if the RankCount object was created from an ordered list.\n"; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
27
|
|
|
27
|
1
|
59
|
sub Leader ( $I ) { |
|
27
|
|
|
|
|
48
|
|
|
27
|
|
|
|
|
36
|
|
174
|
27
|
|
|
|
|
81
|
my @leaders = $I->ArrayTop()->@*; |
175
|
27
|
|
|
|
|
122
|
my %return = ( 'tie' => 0, 'winner' => '', 'tied' => [] ); |
176
|
27
|
100
|
|
|
|
106
|
if ( 1 == @leaders ) { $return{'winner'} = $leaders[0] } |
|
22
|
50
|
|
|
|
53
|
|
177
|
5
|
|
|
|
|
11
|
elsif ( 1 < @leaders ) { $return{'tie'} = 1; $return{'tied'} = \@leaders } |
|
5
|
|
|
|
|
9
|
|
178
|
0
|
|
|
|
|
0
|
else { die "Does not compute in sub RankCount->Leader\n" } |
179
|
27
|
|
|
|
|
92
|
return \%return; |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
395
|
|
|
395
|
1
|
33295
|
sub RankTable( $self ) { |
|
395
|
|
|
|
|
601
|
|
|
395
|
|
|
|
|
506
|
|
183
|
395
|
|
|
|
|
1140
|
my @rows = ( [ 'Rank', 'Choice', 'Votes' ] ); |
184
|
395
|
|
|
|
|
1535
|
my %rc = $self->{'rawcount'}->%*; |
185
|
395
|
|
|
|
|
1379
|
my %byrank = $self->{'byrank'}->%*; |
186
|
395
|
|
|
|
|
1835
|
for my $r ( sort { $a <=> $b } ( keys %byrank ) ) { |
|
1659
|
|
|
|
|
2946
|
|
187
|
1335
|
|
|
|
|
2742
|
my @choice = sort $byrank{$r}->@*; |
188
|
1335
|
|
|
|
|
2095
|
for my $choice (@choice) { |
189
|
1734
|
|
|
|
|
2441
|
my $votes = $rc{$choice}; |
190
|
1734
|
|
|
|
|
2953
|
my @row = ( $r, $choice, $votes ); |
191
|
1734
|
|
|
|
|
3559
|
push @rows, ( \@row ); |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
} |
194
|
395
|
|
|
|
|
1527
|
return generate_table( rows => \@rows, style => 'markdown' ) . "\n"; |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
|
197
|
1
|
|
|
1
|
1
|
1
|
sub RankTableWeighted( $self, $votevalue ) { |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
2
|
|
198
|
1
|
|
|
|
|
3
|
my @rows = ( [ 'Rank', 'Choice', 'Votes', 'VoteValue' ] ); |
199
|
1
|
|
|
|
|
5
|
my %rc = $self->{'rawcount'}->%*; |
200
|
1
|
|
|
|
|
4
|
my %byrank = $self->{'byrank'}->%*; |
201
|
1
|
|
|
|
|
4
|
for my $r ( sort { $a <=> $b } ( keys %byrank ) ) { |
|
7
|
|
|
|
|
11
|
|
202
|
5
|
|
|
|
|
10
|
my @choice = sort $byrank{$r}->@*; |
203
|
5
|
|
|
|
|
8
|
for my $choice (@choice) { |
204
|
8
|
|
|
|
|
10
|
my $votes = $rc{$choice}; |
205
|
8
|
|
|
|
|
34
|
my @row = ( $r, $choice, sprintf("%.2f", $votes/$votevalue), $votes ); |
206
|
8
|
|
|
|
|
15
|
push @rows, ( \@row ); |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
} |
209
|
1
|
|
|
|
|
5
|
return generate_table( |
210
|
|
|
|
|
|
|
rows => \@rows, |
211
|
|
|
|
|
|
|
style => 'markdown', |
212
|
|
|
|
|
|
|
align => [qw/ l l r r /] |
213
|
|
|
|
|
|
|
) . "\n"; |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
1; |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
#FOOTER |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=pod |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
BUG TRACKER |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
L<https://github.com/brainbuz/Vote-Count/issues> |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
AUTHOR |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
John Karr (BRAINBUZ) brainbuz@cpan.org |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
CONTRIBUTORS |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
Copyright 2019-2021 by John Karr (BRAINBUZ) brainbuz@cpan.org. |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
LICENSE |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
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>. |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
SUPPORT |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
This software is provided as is, per the terms of the GNU Public License. Professional support and customisation services are available from the author. |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=cut |
242
|
|
|
|
|
|
|
|