X-Git-Url: https://projects.mako.cc/source/selectricity/blobdiff_plain/1260415e640fec24f8c970ae8e61f4a7ab4594d4..560828ebdd364de4ca09d61764f19215a9e29e59:/lib/rubyvote/election.rb diff --git a/lib/rubyvote/election.rb b/lib/rubyvote/election.rb new file mode 100644 index 0000000..7fd8396 --- /dev/null +++ b/lib/rubyvote/election.rb @@ -0,0 +1,160 @@ +# election library -- a ruby library for elections +# copyright © 2005 MIT Media Lab and Benjamin Mako Hill + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +################################################################# +## ==== election.rb ==== +## +## This file contains the core ElectionVote and ElectionResults +## classes and the most common and simple election methods including +## plurality and approval voting. +################################################################# + +################################################################## +## ElectionVote Classes and SubClasses +## +## There classes are used to store, verify, and "tally" (i.e. count +## votes for the standard Election superclass and for the most common +## types of elections. + +class ElectionVote + attr_reader :votes + attr_reader :candidates + + def initialize(votes=nil) + @votes = Hash.new unless defined?(@votes) + @candidates = Array.new unless defined?(@candidates) + + if votes + if votes.instance_of?( Array ) + votes.each do |vote| + self.tally_vote(vote) if self.verify_vote(vote) + end + else + raise ElectionError, "Votes must be in the form of an array.", caller + end + end + end + + protected + # by default, this is set to look if the vote is defined. it should + # be overridden in each class + def verify_vote(vote=nil) + vote ? true : false + end + + # by default, this does nothing. it must be redefined in any subclass + def tally_vote + self.verify_vote(vote) + end +end + +class PluralityVote < ElectionVote + def result + PluralityResult.new(self) + end + + protected + def verify_vote(vote=nil) + vote.instance_of?( String ) + end + + def tally_vote(candidate) + if @votes.has_key?(candidate) + @votes[candidate] += 1 + else + @votes[candidate] = 1 + @candidates << candidate + end + end +end + +class ApprovalVote < PluralityVote + def result + ApprovalResult.new(self) + end + + protected + def verify_vote(vote=nil) + vote.instance_of?( Array ) and vote.length >= 1 + end + + def tally_vote(approvals) + approvals.each {|candidate| super(candidate)} + end +end + + +################################################################## +## Election Result Classes +## + +## There classes are used to compute and report the results of an +## election. In almost all cases, these will be returned by the +## #results method of a corresponding ElectionVote subclass. + +class ElectionResult + attr_reader :winners + + def initialize(voteobj=nil) + unless voteobj and voteobj.kind_of?( ElectionVote ) + raise ArgumentError, "You must pass a ElectionVote array.", caller + end + + @election = voteobj + @winners = Array.new + end + + def winner + @winners[0] if @winners.length > 0 + end + + def winner? + @winners.length > 0 + end + +end + +class PluralityResult < ElectionResult + attr_reader :ranked_candidates + + def initialize(voteobj=nil) + super(voteobj) + + votes = @election.votes + candidates = @election.candidates + + @ranked_candidates = votes.sort do |a, b| + b[1] <=> a[1] + end.collect {|a| a[0]} + + # winners are anyone who has the same number of votes as the + # first person + @winners = @ranked_candidates.find_all do |i| + votes[i] == votes[@ranked_candidates[0]] + end + end +end + +# this class is complete because results for approval are computed +# identically to results from plurality +class ApprovalResult < PluralityResult +end + +class ElectionError < ArgumentError +end +