File Coverage

blib/lib/Algorithm/Voting/Plurality.pm
Criterion Covered Total %
statement 66 66 100.0
branch 6 6 100.0
condition n/a
subroutine 13 13 100.0
pod 8 8 100.0
total 93 93 100.0


line stmt bran cond sub pod time code
1             # $Id: Plurality.pm 60 2008-09-02 12:11:49Z johntrammell $
2             # $URL: https://algorithm-voting.googlecode.com/svn/tags/rel-0.01-1/lib/Algorithm/Voting/Plurality.pm $
3              
4             package Algorithm::Voting::Plurality;
5              
6 3     3   3962 use strict;
  3         7  
  3         123  
7 3     3   19 use warnings;
  3         4  
  3         110  
8 3     3   17 use base 'Class::Accessor::Fast';
  3         6  
  3         2141  
9 3     3   7400 use List::Util 'sum';
  3         7  
  3         373  
10 3     3   1789 use Params::Validate qw/ validate validate_pos ARRAYREF /;
  3         22235  
  3         2819  
11              
12             __PACKAGE__->mk_accessors(qw/ tally /);
13              
14             =pod
15              
16             =head1 NAME
17              
18             Algorithm::Voting::Plurality - use "Plurality" to decide the sole winner
19              
20             =head1 SYNOPSIS
21              
22             # construct a "ballot box"
23             use Algorithm::Voting::Ballot;
24             use Algorithm::Voting::Plurality;
25             my $box = Algorithm::Voting::Plurality->new();
26              
27             # add ballots to the box
28             $box->add( Algorithm::Voting::Ballot->new('Ralph') );
29             $box->add( Algorithm::Voting::Ballot->new('Fred') );
30             # ...
31             $box->add( Algorithm::Voting::Ballot->new('Ralph') );
32              
33             # and print the result
34             print $box->as_string;
35              
36             =head1 DESCRIPTION
37              
38             From L:
39              
40             =over 4
41              
42             The plurality voting system is a single-winner voting system often used to
43             elect executive officers or to elect members of a legislative assembly which is
44             based on single-member constituencies.
45              
46             The most common system, used in Canada, India, the UK, and the USA, is simple
47             plurality, first past the post or winner-takes-all, a voting system in which a
48             single winner is chosen in a given constituency by having more votes than any
49             other individual representative.
50              
51             =back
52              
53             And from L:
54              
55             =over 4
56              
57             In voting, a plurality vote is the largest number of votes to be given any
58             candidate or proposition when three or more choices are possible. The candidate
59             or proposition receiving the largest number of votes has a plurality. The
60             concept of "plurality" in voting can be contrasted with the concept of
61             "majority". Majority is "more than half". Combining these two concepts in a
62             sentence makes it clearer, "A plurality of votes is a total vote received by a
63             candidate greater than that received by any opponent but less than a majority
64             of the vote."
65              
66             =back
67              
68             =head1 METHODS
69              
70             =head2 Algorithm::Voting::Plurality->new(%args)
71              
72             Constructs a "ballot box" object that will use the Plurality criterion to
73             decide the winner. Optionally, specify a list of candidates; any ballot added
74             to the box that does not indicate one of the listed candidates throws an
75             exception.
76              
77             Example:
78              
79             # construct a ballot box that accepts only three candidates
80             my @c = qw( John Barack Ralph );
81             my $box = Algorithm::Voting::Plurality->new(candidates => \@c);
82              
83             =cut
84              
85             sub new {
86 2     2 1 1074 my $class = shift;
87 2         12 my %valid = (
88             candidates => { type => ARRAYREF, optional => 1 },
89             );
90 2         75 my %args = validate(@_, \%valid);
91 2         11 my $self = bless \%args, $class;
92 2         9 $self->tally({});
93 2         31 return $self;
94             }
95              
96             =head2 $box->candidates
97              
98             Returns a list containing the candidate names used in the construction of the
99             ballot box. If no candidates were specified at construction of the box, the
100             empty list is returned.
101              
102             =cut
103              
104             sub candidates {
105 17     17 1 22 my $self = shift;
106 17 100       41 if ($self->{candidates}) {
107 12         11 return @{ $self->{candidates} };
  12         37  
108             }
109 5         16 return ();
110             }
111              
112             =head2 $box->add($ballot)
113              
114             Add C<$ballot> to the box. C<$ballot> can be any object that we can call
115             method C on.
116              
117             =cut
118              
119             sub add {
120 11     11 1 22 my $self = shift;
121 11         28 my %valid = ( can => [ 'candidate' ], );
122 11         183 my ($ballot) = validate_pos(@_, \%valid);
123 11         39 $self->validate_ballot($ballot);
124 10         55 $self->increment_tally($ballot->candidate);
125 10         50 return $self->count;
126             }
127              
128             =head2 $box->increment_tally($candidate)
129              
130             Increments the tally for C<$candidate> by 1.
131              
132             =cut
133              
134             sub increment_tally {
135 10     10 1 52 my ($self, $candidate) = @_;
136 10         23 $self->tally->{$candidate} += 1;
137 10         69 return $self->tally->{$candidate};
138             }
139              
140             =head2 $box->validate_ballot($ballot)
141              
142             If this election is limited to a specific list of candidates, this method will
143             C if the candidate on C<$ballot> is not one of them.
144              
145             =cut
146              
147             sub validate_ballot {
148 11     11 1 16 my ($self, $ballot) = @_;
149             # if this ballot box has a list of "valid" candidates, verify that the
150             # candidate on this ballot is one of them.
151 11 100       25 if ($self->candidates) {
152 6 100       12 unless (grep { $_ eq $ballot->candidate } $self->candidates) {
  18         114  
153 1         7 die "Invalid ballot: candidate '@{[ $ballot->candidate ]}'",
  1         4  
154             " is not on the candidate list";
155             }
156             }
157             }
158              
159             =head2 count
160              
161             Returns the total number of ballots cast so far.
162              
163             =cut
164              
165             sub count {
166 23     23 1 4400 my $self = shift;
167 23         43 return sum values %{ $self->tally() };
  23         60  
168             }
169              
170             =head2 result
171              
172             The result is a "digested" version of the ballot tally, ordered by the number
173             of ballots cast for a candidate.
174              
175             This method returns a list of arrayrefs, each of the form C<[$n, @candidates]>,
176             and sorted by decreasing C<$n>. Candidates "tied" with the same number of
177             votes are lumped together.
178              
179             For example, an election with three candidates A, B, and C, getting 100, 200,
180             and 100 votes respectively, would generate a result structure like this:
181              
182             [
183             [ 200, "B" ],
184             [ 100, "A", "C" ],
185             ]
186              
187             =cut
188              
189             sub result {
190 4     4 1 2516 my $self = shift;
191             # %rev is a "reverse" hash, in the sense that the key is the number of
192             # votes, and the value is an arrayref containing the candidates who got
193             # that number of votes.
194 4         6 my %rev;
195 4         5 foreach my $cand (keys %{ $self->tally }) {
  4         14  
196 10         40 my $votes = $self->tally->{$cand};
197 10         39 push @{ $rev{$votes} }, $cand;
  10         31  
198             }
199             return
200 8         11 map { [ $_, @{$rev{$_}} ] }
  8         38  
  4         17  
201 4         24 sort { $b <=> $a } keys %rev;
202             }
203              
204             =head2 $box->as_string
205              
206             Returns a string containing the election results.
207              
208             =cut
209              
210             sub as_string {
211 2     2 1 1974 my $self = shift;
212 2         5 my $pos = 0;
213 2         8 my $count = $self->count;
214 2         16 my $string;
215 2         7 foreach my $r ($self->result) {
216 4         6 $pos++;
217 4         10 my ($n, @cand) = @$r;
218 4         37 my $pct = sprintf '%.2f%%', 100 * $n / $count;
219 4         9 $string .= sprintf "%3d: ", $pos;
220 4         18 $string .= "@cand, $n votes ($pct)\n";
221             }
222 2         9 return $string;
223             }
224              
225             1;
226