File Coverage

blib/lib/Vote/Count/RankCount.pm
Criterion Covered Total %
statement 141 142 99.3
branch 12 14 92.8
condition n/a
subroutine 22 22 100.0
pod 12 13 92.3
total 187 191 98.4


line stmt bran cond sub pod time code
1 39     39   287 use strict;
  39         101  
  39         1259  
2 39     39   223 use warnings;
  39         93  
  39         934  
3 39     39   721 use 5.024;
  39         368  
4              
5              
6             use feature qw /postderef signatures/;
7 39     39   230 no warnings 'experimental';
  39         86  
  39         3734  
8 39     39   267 use List::Util qw( min max sum);
  39         101  
  39         1980  
9 39     39   514 use Vote::Count::TextTableTiny qw/generate_table/;
  39         104  
  39         3476  
10 39     39   15915 use Sort::Hash;
  39         101  
  39         2842  
11 39     39   11521  
  39         22935  
  39         60637  
12             our $VERSION='2.02';
13              
14             =head1 NAME
15              
16             Vote::Count::RankCount
17              
18             =head1 VERSION 2.02
19              
20             =cut
21              
22             # ABSTRACT: RankCount object for Vote::Count. Toolkit for vote counting.
23              
24             my %rc = ( $rawcount->%* ); # destructive process needs to use a copy.
25 798     798   1130 my %ordered = ();
  798         1091  
  798         1107  
26 798         3493 my %byrank = ();
27 798         1623 my $pos = 0;
28 798         1159 my $maxpos = scalar( keys %rc );
29 798         1188 while ( 0 < scalar( keys %rc ) ) {
30 798         1306 $pos++;
31 798         1907 my @vrc = values %rc;
32 3769         4789 my $max = max @vrc;
33 3769         7102 for my $k ( keys %rc ) {
34 3769         7008 if ( $rc{$k} == $max ) {
35 3769         7300 $ordered{$k} = $pos;
36 19774 100       31842 delete $rc{$k};
37 5249         7927 if ( defined $byrank{$pos} ) {
38 5249         7077 push @{ $byrank{$pos} }, $k;
39 5249 100       8811 }
40 1480         1812 else {
  1480         3130  
41             $byrank{$pos} = [$k];
42             }
43 3769         8900 }
44             }
45             # uncoverable branch true
46             die "Vote::Count::RankCount::Rank in infinite loop\n"
47             if $pos > $maxpos;
48 3769 50       9723 }
49             # %byrank[1] is arrayref of 1st position,
50             # $pos still has last position filled, %byrank{$pos} is the last place.
51             # sometimes byranks came in as var{byrank...} deref and reref fixes this
52             # although it would be better if I understood why it happened.
53             # It is useful to sort the arrays anyway, for display they would likely be
54             # sorted anyway. For testing it makes the element order predictable.
55              
56             for my $O ( keys %byrank ) {
57             $byrank{$O} = [ sort $byrank{$O}->@* ];
58 798         2073 }
59 3769         8775 my @top = @{ $byrank{1} };
60             my @bottom = @{ $byrank{$pos} };
61 798         1333 my $tie = scalar(@top) > 1 ? 1 : 0;
  798         1925  
62 798         1237 return {
  798         1590  
63 798 100       1811 'rawcount' => $rawcount,
64             'ordered' => \%ordered,
65 798         4778 'byrank' => \%byrank,
66             'top' => \@top,
67             'bottom' => \@bottom,
68             'tie' => $tie,
69             };
70             }
71              
72             =head2 Rank
73              
74             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.
75              
76             =head2 newFromList
77              
78             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.
79              
80             my $ordered_rank_count = Vote::Count::RankCount->newFromList( @ordered_list );
81              
82             =cut
83              
84             my $I = _RankResult($rawcount);
85             return bless $I, $class;
86 797     797 1 2157 }
  797         1335  
  797         1133  
  797         1027  
87 797         1820  
88 797         7531 my $I = _RankResult($rawcount);
89             return bless $I, $class;
90             }
91 1     1 0 6  
  1         3  
  1         2  
  1         2  
92 1         4 shift @list;
93 1         4 my $pos = 0;
94             my $newobj = Vote::Count::RankCount->Rank({
95             map { $_ => --$pos } @list} );
96 40     40 1 719 $newobj->{'orderedlist'} = \@list;
  40         100  
  40         61  
97 40         64 return $newobj;
98 40         77 }
99              
100 40         80 =head2 Methods
  245         459  
101 40         130  
102 40         167 The following Methods are available from RankCount Objects.
103              
104             =head3 RawCount
105              
106             Returns the original HashRef used for Object Creation.
107              
108             =head3 HashWithOrder
109              
110             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.
111              
112             =head3 HashByRank
113              
114             Returns a HashRef where the keys are numbers and the values an ArrayRef of the Choices in that position. The ArrayRefs are sorted alphanumerically.
115              
116             =head3 ArrayTop, ArrayBottom
117              
118             Returns an ArrayRef of the Choices in the Top or Bottom Positions.
119              
120             =head3 OrderedList
121              
122             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.
123              
124             =head3 CountVotes
125              
126             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 Borda it is probably not useful.
127              
128             =head3 Leader
129              
130             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.
131              
132             =head3 RankTable
133              
134             Generates a MarkDown formatted table.
135              
136             say $Election->TopCount->RankTable;
137              
138             | Rank | Choice | Votes |
139             |------|------------|-------|
140             | 1 | VANILLA | 7 |
141             | 2 | MINTCHIP | 5 |
142              
143             =head3 RankTableWeighted ($votevalue)
144              
145             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.
146              
147             say $WeightedElection->TopCount->RankTableWeighted( 100 );
148              
149             | Rank | Choice | Votes | VoteValue |
150             |:-----|:-----------|------:|----------:|
151             | 1 | VANILLA | 7.00 | 700 |
152             | 2 | MINTCHIP | 5.00 | 500 |
153              
154             =cut
155              
156              
157             return $I->{'orderedlist'}->@* if defined $I->{'orderedlist'};
158             die "OrderedList may only be used if the RankCount object was created from an ordered list.\n";
159             }
160              
161 502     502 1 8177 my @leaders = $I->ArrayTop()->@*;
  502         800  
  502         717  
  502         1453  
162 52     52 1 1775 my %return = ( 'tie' => 0, 'winner' => '', 'tied' => [] );
  52         83  
  52         70  
  52         159  
163 59     59 1 1292 if ( 1 == @leaders ) { $return{'winner'} = $leaders[0] }
  59         112  
  59         86  
  59         223  
164 35     35 1 5517 elsif ( 1 < @leaders ) { $return{'tie'} = 1; $return{'tied'} = \@leaders }
  35         63  
  35         61  
  35         137  
165 194     194 1 281 else { die "Does not compute in sub RankCount->Leader\n" }
  194         288  
  194         281  
  194         658  
166 318     318 1 898 return \%return;
  318         476  
  318         463  
  318         1549  
167             }
168 6     6 1 1214  
  6         14  
  6         12  
169 6 100       73 my @rows = ( [ 'Rank', 'Choice', 'Votes' ] );
170 1         9 my %rc = $self->{'rawcount'}->%*;
171             my %byrank = $self->{'byrank'}->%*;
172             for my $r ( sort { $a <=> $b } ( keys %byrank ) ) {
173 27     27 1 65 my @choice = sort $byrank{$r}->@*;
  27         53  
  27         46  
174 27         94 for my $choice (@choice) {
175 27         163 my $votes = $rc{$choice};
176 27 100       122 my @row = ( $r, $choice, $votes );
  22 50       60  
177 5         12 push @rows, ( \@row );
  5         10  
178 0         0 }
179 27         118 }
180             return generate_table( rows => \@rows, style => 'markdown' ) . "\n";
181             }
182 395     395 1 41598  
  395         611  
  395         548  
183 395         1051 my @rows = ( [ 'Rank', 'Choice', 'Votes', 'VoteValue' ] );
184 395         1500 my %rc = $self->{'rawcount'}->%*;
185 395         1296 my %byrank = $self->{'byrank'}->%*;
186 395         1581 for my $r ( sort { $a <=> $b } ( keys %byrank ) ) {
  1660         2998  
187 1335         2766 my @choice = sort $byrank{$r}->@*;
188 1335         2045 for my $choice (@choice) {
189 1734         2467 my $votes = $rc{$choice};
190 1734         3059 my @row = ( $r, $choice, sprintf("%.2f", $votes/$votevalue), $votes );
191 1734         3671 push @rows, ( \@row );
192             }
193             }
194 395         1369 return generate_table(
195             rows => \@rows,
196             style => 'markdown',
197 1     1 1 3 align => [qw/ l l r r /]
  1         2  
  1         2  
  1         2  
198 1         5 ) . "\n";
199 1         7 }
200 1         4 1;
201 1         7  
  8         15  
202 5         12 #FOOTER
203 5         9  
204 8         11 =pod
205 8         38  
206 8         20 BUG TRACKER
207              
208             L<https://github.com/brainbuz/Vote-Count/issues>
209 1         7  
210             AUTHOR
211              
212             John Karr (BRAINBUZ) brainbuz@cpan.org
213              
214             CONTRIBUTORS
215              
216             Copyright 2019-2021 by John Karr (BRAINBUZ) brainbuz@cpan.org.
217              
218             LICENSE
219              
220             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>.
221              
222             SUPPORT
223              
224             This software is provided as is, per the terms of the GNU Public License. Professional support and customisation services are available from the author.
225              
226             =cut
227