File Coverage

blib/lib/Vote/Count/Approval.pm
Criterion Covered Total %
statement 83 84 98.8
branch 11 12 91.6
condition n/a
subroutine 13 13 100.0
pod 1 3 33.3
total 108 112 96.4


line stmt bran cond sub pod time code
1 39     39   22328 use strict;
  39         88  
  39         1278  
2 39     39   204 use warnings;
  39         76  
  39         1073  
3 39     39   732 use 5.024;
  39         125  
4              
5 39     39   192 use feature qw /postderef signatures/;
  39         87  
  39         3295  
6              
7             package Vote::Count::Approval;
8 39     39   268 use Moose::Role;
  39         80  
  39         301  
9              
10 39     39   196105 no warnings 'experimental';
  39         87  
  39         1741  
11 39     39   239 use Carp;
  39         76  
  39         39398  
12              
13             our $VERSION='2.01';
14              
15             =head1 NAME
16              
17             Vote::Count::Approval
18              
19             =head1 VERSION 2.01
20              
21             =cut
22              
23             # ABSTRACT: Approval for Vote::Count. Toolkit for vote counting.
24              
25             =head1 Definition of Approval
26              
27             In Approval Voting, voters indicate which Choices they approve of indicating no preference. Approval can be infered from a Ranked Choice Ballot, by treating each ranked Choice as Approved.
28              
29             =head1 Method Approval
30              
31             Returns a RankCount object for the current Active Set taking an optional argument of an active list as a HashRef.
32              
33             my $Approval = $Election->Approval();
34             say $Approval->RankTable;
35              
36             # to specify the active set
37             my $Approval = $Election->Approval( $activeset );
38              
39             # to specify a cutoff on Range Ballots
40             my $Approval = $Election->Approval( $activeset, $cutoff );
41              
42             For RCV, Approval respects weighting, 'votevalue' is defaulted to 1 by readballots. Integers or Floating point values may be used.
43              
44             =head2 Method LastApprovalBallots
45              
46             Returns a hashref of the unweighted raw count from the last Approval operation.
47              
48             =head2 Method NonApproval
49              
50             The opposite of Approval. Returns a RankCount object for the current Active Set of the non-exhausted ballots not supporting a choice. It does not have the option to provide an Active Set. Only available for Ranked Ballots.
51              
52             =head3 Cutoff (Range Ballots Only)
53              
54             When counting Approval on Range Ballots an optional cutoff value may be provided as a second argument to the Approval Method. When doing so the Active Set argument must be provided. The Cutoff value is a score below which the ballot is considered to not approve of the choice.
55              
56             When counting Approval on Range Ballots it is appropriate to set a threshold below which a choice is not considered to be supported by the voter, but indicated to represent a preference to even lower or unranked choices.
57              
58             For Ranked Ballots the equivalent would be to provide a 'Nuetral Preference', for voters to indicate that any choices ranked lower should not be considered approved. This is not presently implemented, but may be at some point in the future.
59              
60             =cut
61              
62             has 'LastApprovalBallots' => (
63             is => 'rw',
64             isa => 'HashRef',
65             required => 0,
66             );
67              
68 115     115   175 sub _approval_rcv_do ( $I, $active, $ballots ) {
  115         337  
  115         164  
  115         153  
  115         174  
69 115         351 my %approval = ( map { $_ => 0 } keys( $active->%* ) );
  542         939  
70 115         300 my %lastappcount = ( map { $_ => 0 } keys( $active->%* ) );
  542         815  
71 115         223 for my $b ( keys %{$ballots} ) {
  115         459  
72 2005         3661 my @votes = $ballots->{$b}->{'votes'}->@*;
73 2005         2961 for my $v (@votes) {
74 5966 100       9513 if ( defined $approval{$v} ) {
75 3896         5732 $approval{$v} += $ballots->{$b}{'count'} * $ballots->{$b}{'votevalue'} ;
76 3896         5958 $lastappcount{$v} += $ballots->{$b}{'count'};
77             }
78             }
79             }
80 115         3371 $I->LastApprovalBallots( \%lastappcount );
81 115         575 return Vote::Count::RankCount->Rank( \%approval );
82             }
83              
84 157     157   186 sub _approval_range_do ( $active, $ballots, $cutoff ) {
  157         168  
  157         175  
  157         180  
  157         162  
85 157         402 my %approval = ( map { $_ => 0 } keys( $active->%* ) );
  1745         2374  
86 157         325 for my $b ( @{$ballots} ) {
  157         255  
87 916         1456 APPROVALKEYSC: for my $c ( keys $b->{'votes'}->%* ) {
88             # croak "key is $c " . "value is $b->{'votes'}{$c}";
89 3364 100       4513 next APPROVALKEYSC if $b->{'votes'}{$c} < $cutoff;
90 3329 100       5141 $approval{$c} += $b->{'count'} if defined $approval{$c};
91             }
92             }
93 157         483 return Vote::Count::RankCount->Rank( \%approval );
94             }
95              
96 269     269 0 17208 sub Approval ( $self, $active = undef, $cutoff = 0 ) {
  269         356  
  269         367  
  269         360  
  269         313  
97 269         5987 my %BallotSet = $self->BallotSet()->%*;
98 269 100       4122 $active = $self->Active() unless defined $active;
99 269 100       669 if ( $BallotSet{'options'}{'range'} ) {
100 157         339 return _approval_range_do( $active, $BallotSet{'ballots'},
101             $cutoff );
102             }
103             else {
104 112         375 $self->_approval_rcv_do( $active, $BallotSet{'ballots'} );
105             }
106             }
107              
108 3     3 0 17 sub approval { Approval(@_) }
109              
110 3     3   6 sub _non_approval_rcv_do ( $I, $ballots ) {
  3         6  
  3         5  
  3         5  
111 3         69 my $active = $I->Active();
112 3         13 my %nonapproval = ( map { $_ => 0 } keys( $active->%* ) );
  24         46  
113 3         8 my %approval = %{$I->_approval_rcv_do ( $active, $ballots )->RawCount()};
  3         10  
114 3         25 my $activevotes = $I->VotesActive();
115 3         12 for my $A ( keys %nonapproval) {
116 24         36 $nonapproval{ $A } = $activevotes - $approval{ $A };
117             }
118 3         15 return Vote::Count::RankCount->Rank( \%nonapproval );
119             }
120              
121             # For each choice in the active set counts ballots
122 3     3 1 9 sub NonApproval ( $I, $cutoff = 0 ) {
  3         6  
  3         6  
  3         3  
123 3         80 my %BallotSet = $I->BallotSet()->%*;
124 3         9 my $ballots = $BallotSet{'ballots'};
125 3 50       11 if ( $BallotSet{'options'}{'rcv'} ) {
126 3         10 return _non_approval_rcv_do( $I, $ballots );
127             }
128             else {
129 0           die "NonApproval currently implemented for RCV ballots."
130             }
131             }
132              
133             1;
134              
135             #FOOTER
136              
137             =pod
138              
139             BUG TRACKER
140              
141             L<https://github.com/brainbuz/Vote-Count/issues>
142              
143             AUTHOR
144              
145             John Karr (BRAINBUZ) brainbuz@cpan.org
146              
147             CONTRIBUTORS
148              
149             Copyright 2019-2021 by John Karr (BRAINBUZ) brainbuz@cpan.org.
150              
151             LICENSE
152              
153             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>.
154              
155             SUPPORT
156              
157             This software is provided as is, per the terms of the GNU Public License. Professional support and customisation services are available from the author.
158              
159             =cut
160