line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
use 5.024; |
3
|
40
|
|
|
40
|
|
1152058
|
use feature qw/postderef signatures/; |
|
40
|
|
|
|
|
182
|
|
4
|
40
|
|
|
40
|
|
267
|
use strict; |
|
40
|
|
|
|
|
431
|
|
|
40
|
|
|
|
|
8744
|
|
5
|
40
|
|
|
40
|
|
296
|
use warnings; |
|
40
|
|
|
|
|
114
|
|
|
40
|
|
|
|
|
945
|
|
6
|
40
|
|
|
40
|
|
2246
|
no warnings qw/experimental/; |
|
40
|
|
|
|
|
94
|
|
|
40
|
|
|
|
|
1580
|
|
7
|
40
|
|
|
40
|
|
217
|
use Path::Tiny 0.108; |
|
40
|
|
|
|
|
91
|
|
|
40
|
|
|
|
|
5969
|
|
8
|
40
|
|
|
40
|
|
935
|
use Carp; |
|
40
|
|
|
|
|
10443
|
|
|
40
|
|
|
|
|
2497
|
|
9
|
40
|
|
|
40
|
|
270
|
use JSON::MaybeXS; |
|
40
|
|
|
|
|
92
|
|
|
40
|
|
|
|
|
2219
|
|
10
|
40
|
|
|
40
|
|
16649
|
use YAML::XS; |
|
40
|
|
|
|
|
171801
|
|
|
40
|
|
|
|
|
2313
|
|
11
|
40
|
|
|
40
|
|
1553
|
# use Data::Dumper; |
|
40
|
|
|
|
|
7573
|
|
|
40
|
|
|
|
|
3275
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
# ABSTRACT: Read Ballots for Vote::Count. Toolkit for vote counting. |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
our $VERSION='2.02'; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
=head1 NAME |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
Vote::Count::ReadBallots |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=head1 VERSION 2.02 |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 SYNOPSIS |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
use Vote::Count::ReadBallots; |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
my $data1 = read_ballots('t/data/data1.txt'); |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=head1 Description |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
Reads a file containing vote data. Retruns a HashRef of a Vote::Count BallotSet. |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
All public methods are exported. |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
=head1 BallotSet Data Structure |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
ballots { |
38
|
|
|
|
|
|
|
CHOCOLATE:MINTCHIP:VANILLA { |
39
|
|
|
|
|
|
|
count 1, |
40
|
|
|
|
|
|
|
votevalue 1, # needed for STV support |
41
|
|
|
|
|
|
|
votes [ |
42
|
|
|
|
|
|
|
[0] "CHOCOLATE", |
43
|
|
|
|
|
|
|
[1] "MINTCHIP", |
44
|
|
|
|
|
|
|
[2] "VANILLA" |
45
|
|
|
|
|
|
|
] |
46
|
|
|
|
|
|
|
}, |
47
|
|
|
|
|
|
|
}, |
48
|
|
|
|
|
|
|
choices { |
49
|
|
|
|
|
|
|
CHOCOLATE 1, |
50
|
|
|
|
|
|
|
MINTCHIP 1, |
51
|
|
|
|
|
|
|
VANILLA 1 |
52
|
|
|
|
|
|
|
}, |
53
|
|
|
|
|
|
|
votescast 1, |
54
|
|
|
|
|
|
|
comment "# Optional Comment", |
55
|
|
|
|
|
|
|
options { |
56
|
|
|
|
|
|
|
rcv 1 |
57
|
|
|
|
|
|
|
} |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
=head1 Data File Format |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# This is a comment, optional. |
62
|
|
|
|
|
|
|
:CHOICES:VANILLA:CHOCOLATE:STRAWBERRY:MINTCHIP:CARAMEL:RUMRAISIN |
63
|
|
|
|
|
|
|
5:VANILLA:CHOCOLATE:STRAWBERRY |
64
|
|
|
|
|
|
|
RUMRAISIN |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
CHOICES must be defined before any vote lines. or an error will be thrown. CHOICES must only be defined once. These two rules are to protect against errors in manually prepared files. |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
A data line may begin with a number or a choice. When there is no number the line is counted as being a single ballot. The number represents the number of ballots identical to that one; this notation will both dramatically shrink the data files and improve performance. |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
=head2 read_ballots |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
Reads a data file in the standard Vote::Count format and returns a BallotSet. |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=head2 write_ballots |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
write_ballots( $BallotSet, $newfile); |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
Write out a ballotset. Useful for creating a compressed version of a raw file. |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=head1 Range Ballots |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
Range Ballots are supported in both JSON and YAML format. The read method doesn't perform validation like B<read_ballots> does. |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=head2 Range Ballot Format in JSON |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
{ |
87
|
|
|
|
|
|
|
"choices": [ |
88
|
|
|
|
|
|
|
"TWEEDLEDEE", |
89
|
|
|
|
|
|
|
"TWEEDLEDUM", |
90
|
|
|
|
|
|
|
"HUMPTYDUMPTY" |
91
|
|
|
|
|
|
|
], |
92
|
|
|
|
|
|
|
"ballots": [ |
93
|
|
|
|
|
|
|
{ |
94
|
|
|
|
|
|
|
"votes": { |
95
|
|
|
|
|
|
|
"TWEEDLEDEE": 1, |
96
|
|
|
|
|
|
|
"TWEEDLEDUM": 1, |
97
|
|
|
|
|
|
|
"HUMPTYDUMPTY": 3 |
98
|
|
|
|
|
|
|
}, |
99
|
|
|
|
|
|
|
"count": 3 |
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
], |
102
|
|
|
|
|
|
|
"depth": 3 |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=head2 read_range_ballots |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
Requires a parameter of a JSON or YAML file. The second parameter may be either 'json' or 'yaml', defaulting to 'json'. |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
my $BestFastFood = read_range_ballots('t/data/fastfood.range.json'); |
110
|
|
|
|
|
|
|
my $BestFastFood = read_range_ballots('t/data/fastfood.range.yml', 'yaml'); |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=head2 write_range_ballots |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
Takes three parameters, a BallotSet, a file location, and a value of 'json' or 'yaml'. The first two parameters are required, the third defaults to 'json'. |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
write_range_ballots( $BestFastFood, '/tmp/fast.json', 'json' ); |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=cut |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
use Exporter::Easy ( EXPORT => |
121
|
40
|
|
|
|
|
2340
|
[qw( read_ballots write_ballots read_range_ballots write_range_ballots)], |
122
|
|
|
|
|
|
|
); |
123
|
40
|
|
|
40
|
|
16305
|
|
|
40
|
|
|
|
|
52335
|
|
124
|
|
|
|
|
|
|
my $coder = Cpanel::JSON::XS->new->ascii->pretty; |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
my %C = (); |
127
|
152
|
|
|
152
|
|
1724
|
$choices =~ m/^\:CHOICES\:(.*)/; |
|
152
|
|
|
|
|
305
|
|
|
152
|
|
|
|
|
253
|
|
128
|
152
|
|
|
|
|
365
|
for my $choice ( split /:/, $1 ) { |
129
|
152
|
|
|
|
|
625
|
$C{$choice} = 1; |
130
|
152
|
|
|
|
|
1047
|
} |
131
|
1372
|
|
|
|
|
2645
|
return \%C; |
132
|
|
|
|
|
|
|
} |
133
|
152
|
|
|
|
|
686
|
|
134
|
|
|
|
|
|
|
my %data = ( |
135
|
|
|
|
|
|
|
'choices' => undef, |
136
|
151
|
|
|
151
|
1
|
277952
|
'ballots' => {}, |
|
151
|
|
|
|
|
384
|
|
|
151
|
|
|
|
|
261
|
|
137
|
151
|
|
|
|
|
1101
|
'options' => { 'rcv' => 1 }, |
138
|
|
|
|
|
|
|
'votescast' => 0, |
139
|
|
|
|
|
|
|
'comment' => '' |
140
|
|
|
|
|
|
|
); |
141
|
|
|
|
|
|
|
BALLOTREADLINES: |
142
|
|
|
|
|
|
|
for my $line_raw ( path($filename)->lines({chomp => 1}) ) { |
143
|
|
|
|
|
|
|
if ( $line_raw =~ m/^\#/ ) { |
144
|
|
|
|
|
|
|
$data{'comment'} .= $line_raw; |
145
|
151
|
|
|
|
|
767
|
next BALLOTREADLINES; |
146
|
9582
|
100
|
|
|
|
118324
|
} |
147
|
116
|
|
|
|
|
432
|
if ( $line_raw =~ m/^\:CHOICES\:/ ) { |
148
|
116
|
|
|
|
|
272
|
if ( $data{'choices'} ) { |
149
|
|
|
|
|
|
|
croak("File $filename redefines CHOICES \n$line_raw\n"); |
150
|
9466
|
100
|
|
|
|
16322
|
} |
151
|
153
|
100
|
|
|
|
547
|
else { $data{'choices'} = _choices($line_raw); } |
152
|
2
|
|
|
|
|
32
|
next; |
153
|
|
|
|
|
|
|
} |
154
|
151
|
|
|
|
|
536
|
my $line = $line_raw; |
155
|
151
|
|
|
|
|
361
|
next unless ( $line =~ /\w/ ); |
156
|
|
|
|
|
|
|
$line =~ s/^(\d+)\://; |
157
|
9313
|
|
|
|
|
13891
|
my $numbals = $1 ? $1 : 1; |
158
|
9313
|
100
|
|
|
|
22823
|
$data{'votescast'} += $numbals; |
159
|
9298
|
|
|
|
|
27921
|
if ( $data{'ballots'}{$line} ) { |
160
|
9298
|
100
|
|
|
|
23097
|
$data{'ballots'}{$line}{'count'} = |
161
|
9298
|
|
|
|
|
15238
|
$data{'ballots'}{$line}{'count'} + $numbals; |
162
|
9298
|
100
|
|
|
|
16407
|
} |
163
|
|
|
|
|
|
|
else { |
164
|
129
|
|
|
|
|
363
|
my @votes = (); |
165
|
|
|
|
|
|
|
for my $choice ( split( /:/, $line ) ) { |
166
|
|
|
|
|
|
|
unless ( $data{'choices'}{$choice} ) { |
167
|
9169
|
|
|
|
|
13336
|
die "Choice: $choice is not in defined choice list: " |
168
|
9169
|
|
|
|
|
26964
|
. join( ", ", keys( $data{'choices'}->%* ) ) |
169
|
43044
|
100
|
|
|
|
76461
|
. "\n -- $line\n"; |
170
|
|
|
|
|
|
|
} |
171
|
2
|
|
|
|
|
38
|
push @votes, $choice; |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
$data{'ballots'}{$line}{'count'} = $numbals; |
174
|
43042
|
|
|
|
|
70332
|
$data{'ballots'}{$line}{'votes'} = \@votes; |
175
|
|
|
|
|
|
|
} |
176
|
9167
|
|
|
|
|
32112
|
} |
177
|
9167
|
|
|
|
|
22021
|
for my $K ( keys $data{'ballots'}->%* ) { $data{'ballots'}{$K}{'votevalue'} = 1 } |
178
|
|
|
|
|
|
|
return \%data; |
179
|
|
|
|
|
|
|
} |
180
|
147
|
|
|
|
|
3190
|
|
|
9147
|
|
|
|
|
16024
|
|
181
|
147
|
|
|
|
|
4526
|
my @data = ('# Data rewritten in compressed form.'); |
182
|
|
|
|
|
|
|
my $choicelist = join( ':', sort keys( $BallotSet->{'choices'}->%* ) ); |
183
|
|
|
|
|
|
|
push @data, "CHOICES:$choicelist"; |
184
|
1
|
|
|
1
|
1
|
6
|
for my $k ( sort keys $BallotSet->{'ballots'}->%* ) { |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
2
|
|
185
|
1
|
|
|
|
|
3
|
my $cnt = $BallotSet->{'ballots'}{$k}{'count'}; |
186
|
1
|
|
|
|
|
11
|
push @data, "$cnt:$k"; |
187
|
1
|
|
|
|
|
4
|
} |
188
|
1
|
|
|
|
|
5
|
for my $D (@data) { $D .= "\n" if $D !~ /\n$/ } |
189
|
5
|
|
|
|
|
10
|
path($destination)->spew(@data); |
190
|
5
|
|
|
|
|
12
|
} |
191
|
|
|
|
|
|
|
|
192
|
1
|
50
|
|
|
|
3
|
$BallotSet->{'choices'} = [ sort keys $BallotSet->{'choices'}->%* ]; |
|
7
|
|
|
|
|
17
|
|
193
|
1
|
|
|
|
|
5
|
if ( $format eq 'json' ) { |
194
|
|
|
|
|
|
|
path($destination)->spew( $coder->encode($BallotSet) ); |
195
|
|
|
|
|
|
|
} |
196
|
1
|
|
|
1
|
1
|
7
|
elsif ( $format eq 'yaml' ) { |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
197
|
1
|
|
|
|
|
10
|
$BallotSet = Load path->($destination)->slurp; |
198
|
1
|
50
|
|
|
|
4
|
path($destination)->spew( Dump $BallotSet); |
|
|
0
|
|
|
|
|
|
199
|
1
|
|
|
|
|
4
|
} |
200
|
|
|
|
|
|
|
else { die "invalid score ballot format $format." } |
201
|
|
|
|
|
|
|
} |
202
|
0
|
|
|
|
|
0
|
|
203
|
0
|
|
|
|
|
0
|
my $BallotSet = undef; |
204
|
|
|
|
|
|
|
if ( $format eq 'json' ) { |
205
|
0
|
|
|
|
|
0
|
$BallotSet = $coder->decode( path($source)->slurp ); |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
elsif ( $format eq 'yaml' ) { |
208
|
20
|
|
|
20
|
1
|
14077
|
$BallotSet = Load path($source)->slurp; |
|
20
|
|
|
|
|
47
|
|
|
20
|
|
|
|
|
46
|
|
|
20
|
|
|
|
|
33
|
|
209
|
20
|
|
|
|
|
40
|
} |
210
|
20
|
100
|
|
|
|
78
|
else { die "invalid score ballot format $format." } |
|
|
50
|
|
|
|
|
|
211
|
17
|
|
|
|
|
76
|
$BallotSet->{'votescast'} = 0; |
212
|
|
|
|
|
|
|
$BallotSet->{'options'} = { 'range' => 1, 'rcv' => 0 }; |
213
|
|
|
|
|
|
|
my @choices = $BallotSet->{'choices'}->@*; |
214
|
3
|
|
|
|
|
12
|
$BallotSet->{'choices'} = { map { $_ => 1 } @choices }; |
215
|
|
|
|
|
|
|
for my $ballot ( $BallotSet->{'ballots'}->@* ) { |
216
|
0
|
|
|
|
|
0
|
$BallotSet->{'votescast'} += $ballot->{'count'}; |
217
|
20
|
|
|
|
|
6567
|
} |
218
|
20
|
|
|
|
|
97
|
return $BallotSet; |
219
|
20
|
|
|
|
|
93
|
} |
220
|
20
|
|
|
|
|
53
|
|
|
144
|
|
|
|
|
300
|
|
221
|
20
|
|
|
|
|
89
|
1; |
222
|
96
|
|
|
|
|
159
|
|
223
|
|
|
|
|
|
|
#buildpod |
224
|
20
|
|
|
|
|
749
|
|
225
|
|
|
|
|
|
|
#FOOTER |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
=pod |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
BUG TRACKER |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
L<https://github.com/brainbuz/Vote-Count/issues> |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
AUTHOR |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
John Karr (BRAINBUZ) brainbuz@cpan.org |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
CONTRIBUTORS |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
Copyright 2019-2021 by John Karr (BRAINBUZ) brainbuz@cpan.org. |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
LICENSE |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
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>. |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
SUPPORT |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
This software is provided as is, per the terms of the GNU Public License. Professional support and customisation services are available from the author. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=cut |
250
|
|
|
|
|
|
|
|