Merged from Mako.
[selectricity] / lib / rubyvote / election.rb
1 # election library -- a ruby library for elections
2 # copyright © 2005 MIT Media Lab and Benjamin Mako Hill
3
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # General Public License for more details.
13
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 # 02110-1301, USA.
18
19 #################################################################
20 ## ==== election.rb ====
21 ##
22 ## This file contains the core ElectionVote and ElectionResults
23 ## classes and the most common and simple election methods including
24 ## plurality and approval voting.
25 #################################################################
26
27 ##################################################################
28 ## ElectionVote Classes and SubClasses
29 ##
30 ## There classes are used to store, verify, and "tally" (i.e. count
31 ## votes for the standard Election superclass and for the most common
32 ## types of elections.
33
34 class ElectionVote
35   attr_reader :votes
36   attr_reader :candidates
37
38   def initialize(votes=nil)
39     @votes = Hash.new unless defined?(@votes)
40     @candidates = Array.new unless defined?(@candidates)
41
42     if votes
43       if votes.instance_of?( Array )
44         votes.each do |vote|
45           if self.verify_vote(vote)
46             self.tally_vote(vote)
47           else
48             raise InvalidVoteError.new("Invalid vote object", vote)
49           end
50         end
51       else
52         raise ElectionError, "Votes must be in the form of an array.", caller
53       end
54     end
55   end
56
57   protected
58   # by default, this is set to look if the vote is defined. it should
59   # be overridden in each class
60   def verify_vote(vote=nil)
61     vote ? true : false
62   end
63
64   # by default, this does nothing. it must be redefined in any subclass
65   def tally_vote
66     self.verify_vote(vote)
67   end
68
69 end
70
71 class PluralityVote < ElectionVote
72   def result
73     PluralityResult.new(self)
74   end
75   
76   protected
77   def verify_vote(vote=nil)
78     vote ? true : false
79   end
80
81   def tally_vote(candidate)
82     if @votes.has_key?(candidate)
83       @votes[candidate] += 1
84     else
85       @votes[candidate] = 1
86       @candidates << candidate
87     end
88   end
89 end
90
91 class ApprovalVote < PluralityVote
92   def result
93     ApprovalResult.new(self)
94   end
95
96   protected
97   def verify_vote(vote=nil)
98     vote.instance_of?( Array ) and vote.length >= 1
99   end
100
101   def tally_vote(approvals)
102     approvals.each {|candidate| super(candidate)}
103   end
104 end
105
106
107 ##################################################################
108 ## Election Result Classes
109 ##
110
111 ## There classes are used to compute and report the results of an
112 ## election. In almost all cases, these will be returned by the
113 ## #results method of a corresponding ElectionVote subclass.
114
115 class ElectionResult
116   attr_reader :winners
117   attr_reader :election
118
119   def initialize(voteobj=nil)
120     unless voteobj and voteobj.kind_of?( ElectionVote )
121       raise ArgumentError, "You must pass a ElectionVote array.", caller
122     end
123
124     @election = voteobj
125     @winners = Array.new
126   end
127
128   def winner
129     @winners[0] if @winners.length > 0
130   end
131
132   def winner?
133     @winners.length > 0 and not @winners[0].nil?
134   end
135   
136 end
137
138 class PluralityResult < ElectionResult
139   attr_reader :ranked_candidates
140   attr_reader :points
141   
142   def initialize(voteobj=nil)
143     super(voteobj)
144
145     votes = @election.votes
146     candidates = @election.candidates
147     
148     @ranked_candidates = votes.sort do |a, b|
149       b[1] <=> a[1]
150     end.collect {|a| a[0]}
151     
152     @points = @election.votes
153     
154     # winners are anyone who has the same number of votes as the
155     # first person
156     @winners = @ranked_candidates.find_all do |i|
157       votes[i] == votes[@ranked_candidates[0]]
158     end
159   end
160 end
161
162 # this class is complete because results for approval are computed
163 # identically to results from plurality
164 class ApprovalResult < PluralityResult
165 end
166   
167 class ElectionError < ArgumentError
168 end
169
170 class InvalidVoteError < ElectionError
171   attr_accessor :voteobj
172   def initialize(msg=nil, voteobj=nil)
173     super(msg)
174     @voteobj=voteobj
175   end
176 end

Benjamin Mako Hill || Want to submit a patch?