Adding the rubyvote library to do the actually election-making. The only
[selectricity] / lib / rubyvote / election.rb
diff --git a/lib/rubyvote/election.rb b/lib/rubyvote/election.rb
new file mode 100644 (file)
index 0000000..7fd8396
--- /dev/null
@@ -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
+

Benjamin Mako Hill || Want to submit a patch?