--- /dev/null
+require 'rubygems'
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+require 'rake/packagetask'
+require 'rake/gempackagetask'
+require 'rake/contrib/rubyforgepublisher'
+
+PKG_NAME = "rubyvote"
+PKG_VERSION = "0.2"
+PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
+RELEASE_NAME = "REL #{PKG_VERSION}"
+
+RUBY_FORGE_PROJECT = "rubyvote"
+RUBY_FORGE_USER = "mako"
+
+desc "Default Task"
+task :default => [ :test ]
+
+# Run the unit tests
+Rake::TestTask.new { |t|
+ t.libs << "test"
+ t.pattern = 'test/*_test.rb'
+ t.verbose = true
+}
+
+
+# Genereate the RDoc documentation
+Rake::RDocTask.new { |rdoc|
+ rdoc.rdoc_dir = 'doc'
+ rdoc.title = "RubyVote -- Election Methods in Ruby"
+ rdoc.rdoc_files.include('README', 'ChangeLog', 'COPYING')
+ rdoc.rdoc_files.include('lib/rubyvote.rb')
+ rdoc.rdoc_files.include('lib/rubyvote/*.rb')
+}
+
+
+# Create compressed packages
+spec = Gem::Specification.new do |s|
+ s.platform = Gem::Platform::RUBY
+ s.name = PKG_NAME
+ s.summary = "Election methods library in ruby."
+ s.description = %q{Provides a variety of different election types.}
+ s.version = PKG_VERSION
+
+ s.author = "Benjamin Mako Hill"
+ s.email = "mako @nospam@ atdot.cc"
+ s.rubyforge_project = RUBY_FORGE_PROJECT
+ s.homepage = "http://rubyvote.rubyforge.org"
+
+ s.has_rdoc = true
+ s.requirements << 'none'
+ s.require_path = 'lib'
+ s.autorequire = 'rubyvote'
+
+ s.files = [ "Rakefile", "README", "ChangeLog", "COPYING" ]
+ s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
+ s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
+end
+
+Rake::GemPackageTask.new(spec) do |p|
+ p.gem_spec = spec
+ p.need_tar = true
+ p.need_zip = true
+end
+
+###TODO: Configure for api documentation login and path
+desc "Publish the API documentation"
+task :pgem => [:package] do
+ Rake::SshFilePublisher.new("login@foo.com", "path/to/place", "pkg", "#{PKG_FILE_NAME}.gem").upload
+end
+
+desc "Publish the release files to RubyForge."
+task :release => [:package] do
+ files = ["gem", "tgz", "zip"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
+
+ if RUBY_FORGE_PROJECT then
+ require 'net/http'
+ require 'open-uri'
+
+ project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
+ project_data = open(project_uri) { |data| data.read }
+ group_id = project_data[/[?&]group_id=(\d+)/, 1]
+ raise "Couldn't get group id" unless group_id
+
+ # This echos password to shell which is a bit sucky
+ if ENV["RUBY_FORGE_PASSWORD"]
+ password = ENV["RUBY_FORGE_PASSWORD"]
+ else
+ print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
+ password = STDIN.gets.chomp
+ end
+
+ login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
+ data = [
+ "login=1",
+ "form_loginname=#{RUBY_FORGE_USER}",
+ "form_pw=#{password}"
+ ].join("&")
+ http.post("/account/login.php", data)
+ end
+
+ cookie = login_response["set-cookie"]
+ raise "Login failed" unless cookie
+ headers = { "Cookie" => cookie }
+
+ release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
+ release_data = open(release_uri, headers) { |data| data.read }
+ package_id = release_data[/[?&]package_id=(\d+)/, 1]
+ raise "Couldn't get package id" unless package_id
+
+ first_file = true
+ release_id = ""
+
+ files.each do |filename|
+ basename = File.basename(filename)
+ file_ext = File.extname(filename)
+ file_data = File.open(filename, "rb") { |file| file.read }
+
+ puts "Releasing #{basename}..."
+
+ release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
+ release_date = Time.now.strftime("%Y-%m-%d %H:%M")
+ type_map = {
+ ".zip" => "3000",
+ ".tgz" => "3110",
+ ".gz" => "3110",
+ ".gem" => "1400"
+ }; type_map.default = "9999"
+ type = type_map[file_ext]
+ boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
+
+ query_hash = if first_file then
+ {
+ "group_id" => group_id,
+ "package_id" => package_id,
+ "release_name" => RELEASE_NAME,
+ "release_date" => release_date,
+ "type_id" => type,
+ "processor_id" => "8000", # Any
+ "release_notes" => "",
+ "release_changes" => "",
+ "preformatted" => "1",
+ "submit" => "1"
+ }
+ else
+ {
+ "group_id" => group_id,
+ "release_id" => release_id,
+ "package_id" => package_id,
+ "step2" => "1",
+ "type_id" => type,
+ "processor_id" => "8000", # Any
+ "submit" => "Add This File"
+ }
+ end
+
+ query = "?" + query_hash.map do |(name, value)|
+ [name, URI.encode(value)].join("=")
+ end.join("&")
+
+ data = [
+ "--" + boundary,
+ "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
+ "Content-Type: application/octet-stream",
+ "Content-Transfer-Encoding: binary",
+ "", file_data, ""
+ ].join("\x0D\x0A")
+
+ release_headers = headers.merge(
+ "Content-Type" => "multipart/form-data; boundary=#{boundary}"
+ )
+
+ target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
+ http.post(target + query, data, release_headers)
+ end
+
+ if first_file then
+ release_id = release_response.body[/release_id=(\d+)/, 1]
+ raise("Couldn't get release id") unless release_id
+ end
+
+ first_file = false
+ end
+ end
+end
--- /dev/null
+# Extra full path added to fix some require errors on some installations.
+
+require File.dirname(__FILE__) + '/rubyvote/election'
+require File.dirname(__FILE__) + '/rubyvote/condorcet'
+require File.dirname(__FILE__) + '/rubyvote/positional'
+require File.dirname(__FILE__) + '/rubyvote/runoff'
+require File.dirname(__FILE__) + '/rubyvote/range'
+
## The CondorcetVote class is subclassed by the PureCondorcetVote and
## the CloneproofSSDVote classes but should not be used directly.
-require 'election'
-
class CondorcetVote < ElectionVote
def tally_vote(vote=nil)
## These classes inherit from and/or are modeled after the classes in
## election.rb and condorcet.rb
-require 'election'
-
class BordaVote < ElectionVote
def initialize(votes=nil)
--- /dev/null
+# Range voting as described in wikipedia.
+# (http://en.wikipedia.org/wiki/Range_voting)
+
+class RangeVote < ElectionVote
+ def initialize(votes = nil, range = 1..10)
+ @valid_range = range
+ super(votes)
+ end
+
+ def result
+ RangeResult.new(self)
+ end
+
+ protected
+ def verify_vote(vote=nil)
+ vote.instance_of?(Hash) && vote.all?{|c,score| @valid_range.include?(score)}
+ end
+
+ def tally_vote(vote)
+ vote.each do |candidate, score|
+ if @votes.has_key?(candidate)
+ @votes[candidate] += score
+ else
+ @votes[candidate] = score
+ @candidates << candidate
+ end
+ end
+ end
+end
+
+class RangeResult < PluralityResult
+end
-require 'election'
-
class InstantRunoffVote < ElectionVote
def initialize(votes=nil)
@candidates = Array.new
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
-require 'election'
-require 'condorcet'
-require 'positional'
-require 'runoff'
+require 'lib/rubyvote'
def print_winner(result)
if not result.winner?
print_winner( InstantRunoffVote.new(vote_array).result )
end
+def range_test1
+ puts "USING RANGE..."
+ puts "The winner shold be: B"
+
+ vote_array = Array.new
+ 42.times {vote_array << {:A => 10, :B => 5, :C => 2, :D => 1}}
+ 26.times {vote_array << {:A => 1, :B => 10, :C => 5, :D => 2}}
+ 15.times {vote_array << {:A => 1, :B => 2, :C => 10, :D => 5}}
+ 17.times {vote_array << {:A => 1, :B => 2, :C => 5, :D => 10}}
+
+ print_winner( RangeVote.new(vote_array).result )
+end
+
condorcet_test1()
ssd_test1()
ssd_test2()
runoff_test1()
runoff_test2()
runoff_test3()
+range_test1()
--- /dev/null
+#!/usr/bin/ruby
+
+require 'test/unit'
+require 'election_test_helper'
+
+class TestCondorcetVote < Test::Unit::TestCase
+ include ElectionTestHelper
+
+ def test_condorcet
+ vote_array = Array.new
+ 3.times {vote_array << "ABC".split("")}
+ 3.times {vote_array << "CBA".split("")}
+ 2.times {vote_array << "BAC".split("")}
+
+ test_winner( ["B"], PureCondorcetVote.new(vote_array).result )
+ end
+
+ def test_ssd
+ vote_array = Array.new
+ 5.times {vote_array << "ACBED".split("")}
+ 5.times {vote_array << "ADECB".split("")}
+ 8.times {vote_array << "BEDAC".split("")}
+ 3.times {vote_array << "CABED".split("")}
+ 7.times {vote_array << "CAEBD".split("")}
+ 2.times {vote_array << "CBADE".split("")}
+ 7.times {vote_array << "DCEBA".split("")}
+ 8.times {vote_array << "EBADC".split("")}
+
+ test_winner( "E", CloneproofSSDVote.new(vote_array).result )
+ end
+
+ def test_ssd2
+ vote_array = Array.new
+ 5.times {vote_array << "ACBD".split("")}
+ 2.times {vote_array << "ACDB".split("")}
+ 3.times {vote_array << "ADCB".split("")}
+ 4.times {vote_array << "BACD".split("")}
+ 3.times {vote_array << "CBDA".split("")}
+ 3.times {vote_array << "CDBA".split("")}
+ 1.times {vote_array << "DACB".split("")}
+ 5.times {vote_array << "DBAC".split("")}
+ 4.times {vote_array << "DCBA".split("")}
+
+ test_winner( "D", CloneproofSSDVote.new(vote_array).result )
+ end
+
+ def test_ssd3
+ vote_array = Array.new
+ 3.times {vote_array << "ABCD".split("")}
+ 2.times {vote_array << "DABC".split("")}
+ 2.times {vote_array << "DBCA".split("")}
+ 2.times {vote_array << "CBDA".split("")}
+
+ test_winner("B", CloneproofSSDVote.new(vote_array).result )
+ end
+end
--- /dev/null
+#!/usr/bin/ruby
+
+require 'test/unit'
+require 'election_test_helper'
+
+class TestElectionVote < Test::Unit::TestCase
+ include ElectionTestHelper
+
+ def test_plurality
+ vote_array = "ABCABCABCCCBBAAABABABCCCCCCCCCCCCCA".split("")
+
+ test_winner( "C", PluralityVote.new(vote_array).result )
+ end
+
+
+ def test_approval
+ vote_array = Array.new
+ 10.times {vote_array << "AB".split("")}
+ 10.times {vote_array << "CB".split("")}
+ 11.times {vote_array << "AC".split("")}
+ 5.times {vote_array << "A".split("")}
+
+ test_winner( "A", ApprovalVote.new(vote_array).result )
+ end
+end
+
--- /dev/null
+$:.unshift(File.dirname(__FILE__) + "/../lib/")
+
+require 'rubyvote'
+
+module ElectionTestHelper
+ def test_winner(expected, result)
+ puts "\nUsing the #{result.class.to_s.gsub(/Result/,'')} voting method..."
+
+ if result.winner?
+ if expected.is_a?(Array) && expected.length > 1 # Array is passed to test for a tie!
+ msg = "There is a tie: %s" % result.winners.join(", ")
+
+ assert_equal(expected.length, result.winners.length,
+ "Not the correct number of winners!")
+ assert(expected.all?{|c| result.winners.include?(c)},
+ "Tie winners do not match expected!")
+ else
+ msg = "There is a single winner: #{result.winners[0]}"
+ assert_equal(expected, result.winners[0], msg)
+ end
+
+ else
+ msg = "There is no winner"
+ assert_nil(expected, msg)
+ end
+
+ puts msg
+ end
+end
--- /dev/null
+#!/usr/bin/ruby
+
+require 'test/unit'
+require 'election_test_helper'
+
+class TestPositionalVote < Test::Unit::TestCase
+ include ElectionTestHelper
+
+ def test_borda
+ vote_array = Array.new
+ 3.times {vote_array << "ABC".split("")}
+ 3.times {vote_array << "CBA".split("")}
+ 2.times {vote_array << "BAC".split("")}
+
+ test_winner( "B", BordaVote.new(vote_array).result )
+ end
+end
+
--- /dev/null
+#!/usr/bin/ruby
+
+require 'test/unit'
+require 'election_test_helper'
+
+class TestRangeVote < Test::Unit::TestCase
+ include ElectionTestHelper
+
+ def test_range
+ vote_array = []
+ 42.times {vote_array << {'A' => 10, 'B' => 5, 'C' => 2, 'D' => 1}}
+ 26.times {vote_array << {'A' => 1, 'B' => 10, 'C' => 5, 'D' => 2}}
+ 15.times {vote_array << {'A' => 1, 'B' => 2, 'C' => 10, 'D' => 5}}
+ 17.times {vote_array << {'A' => 1, 'B' => 2, 'C' => 5, 'D' => 10}}
+
+ test_winner('B', RangeVote.new(vote_array).result )
+ end
+
+ def test_tie
+ vote_array = []
+ 10.times {vote_array << {'A' => 5, 'B' => 2}}
+ 10.times {vote_array << {'A' => 2, 'B' => 5}}
+
+ test_winner(['A','B'], RangeVote.new(vote_array).result )
+ end
+
+ def test_no_win
+ vote_array = []
+
+ test_winner(nil, RangeVote.new(vote_array).result )
+ end
+end
--- /dev/null
+#!/usr/bin/ruby
+
+require 'test/unit'
+require 'election_test_helper'
+
+class TestRunoffVote < Test::Unit::TestCase
+ include ElectionTestHelper
+
+ def test_runoff
+ vote_array = Array.new
+ 142.times {vote_array << "ABCD".split("")}
+ 26.times {vote_array << "BCDA".split("")}
+ 15.times {vote_array << "CDBA".split("")}
+ 17.times {vote_array << "DCBA".split("")}
+
+ test_winner( "A", InstantRunoffVote.new(vote_array).result )
+ end
+
+ def test_runoff2
+ vote_array = Array.new
+ 42.times {vote_array << "ABCD".split("")}
+ 26.times {vote_array << "BCDA".split("")}
+ 15.times {vote_array << "CDBA".split("")}
+ 17.times {vote_array << "DCBA".split("")}
+
+ test_winner( "D", InstantRunoffVote.new(vote_array).result )
+ end
+
+ def test_runoff3
+ vote_array = Array.new
+ 42.times {vote_array << "ABCD".split("")}
+ 26.times {vote_array << "ACBD".split("")}
+ 15.times {vote_array << "BACD".split("")}
+ 32.times {vote_array << "BCAD".split("")}
+ 14.times {vote_array << "CABD".split("")}
+ 49.times {vote_array << "CBAD".split("")}
+ 17.times {vote_array << "ABDC".split("")}
+ 23.times {vote_array << "BADC".split("")}
+ 37.times {vote_array << "BCDA".split("")}
+ 11.times {vote_array << "CADB".split("")}
+ 16.times {vote_array << "CBDA".split("")}
+ 54.times {vote_array << "ADBC".split("")}
+ 36.times {vote_array << "BDCA".split("")}
+ 42.times {vote_array << "CDAB".split("")}
+ 13.times {vote_array << "CDBA".split("")}
+ 51.times {vote_array << "DABC".split("")}
+ 33.times {vote_array << "DBCA".split("")}
+ 39.times {vote_array << "DCAB".split("")}
+ 12.times {vote_array << "DCBA".split("")}
+
+ test_winner( "C", InstantRunoffVote.new(vote_array).result )
+ end
+end
+