Rev 45 | Rev 75 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
## Compute Hare Ballot#### Tim Korb (jtk@cs.purdue.edu), May 2007#### A few definitions and notes...#### tally: dictionary in which the keys are candidate names and the## values are lists of ballots currently assigned to that candidate#### ballot: list of candidates in the order determined by the voter#### winners: list of candidates that have reached the quota of ballots## or have remained in the running long enough to be declared a winner#### losers: list of candidates that have been eliminated from the running#### Note that plurals are generally used to indicate lists of other## items, e.g., ballots is a list of ballot items.#### To download the complete source distribution, use Subversion:#### % svn co http://www.bikmort.com/hare##import sysimport mathimport randomfdOut = NonefTrace = 1randcalls = 0def trace(s):global fTrace, fdOutif fTrace: print >>fdOut, sreturndef findWinner(winners, losers, tally, quota, nWinners):global randcallscWin = quota # number of votes for highest winnerlWin = [] # list of candidates with highest votesfor c in tally.keys():if c not in losers and c not in winners and len(tally[c]) >= cWin:if len(tally[c]) == cWin:lWin.append(c)else:lWin = [c]cWin = len(tally[c])if len(lWin) == 1:return lWin[0]elif len(lWin) > 1:trace("\tselecting winning candidate randomly from %s" % lWin)randcalls += 1return random.choice(lWin)# Check to see if only enough candidates remain## TODO: sort by len(tally[c]) to choose larger winners first## randomize and count if some with equal votesn = 0last = ""for c in tally.keys():if c not in winners and c not in losers:last = cn = n + 1if nWinners - len(winners) >= n:trace("\tremaining winner(s) have fewer than quota (%d) ballots" %quota)return lastreturndef redistributeWinner(winner, winners, losers, tally, quota):global randcallsexcess = len(tally[winner]) - quotaif excess <= 0:trace("\tno excess ballots to redistribute")else:lEffective = gatherEffectiveBallots(winner, winners, losers, tally)nRedistribute = min(excess, len(lEffective))trace("\tredistributing %d effective of %d excess ballot(s) at random from %s" %(nRedistribute, excess, winner))for ballot in random.sample(lEffective, nRedistribute):randcalls += 1trace("\trandom choice = %s" % ballot)tally[winner].remove(ballot)redistributeBallot(ballot, winners, losers, tally)nRedistribute -= 1traceTally(quota, tally)def gatherEffectiveBallots(winner, winners, losers, tally):lEffective = []for ballot in tally[winner]:for candidateTo in ballot:if candidateTo not in winners and candidateTo not in losers:lEffective.append(ballot)breakreturn lEffectivedef redistributeBallot(ballot, winners, losers, tally):for candidateTo in ballot:if candidateTo not in winners and candidateTo not in losers:trace("\tto %s: %s" % (candidateTo, ballot))if not tally.has_key(candidateTo): tally[candidateTo] = []tally[candidateTo].append(ballot)ballot = ""breakif ballot:trace("\tineffective ballot dropped: %s" % ballot)def findLoser(losers, winners, tally):global randcallscMin = sys.maxint # least number of votes for candidate loserlMin = [] # list of candidates with least votesfor c in tally.keys():if c not in losers and c not in winners and len(tally[c]) <= cMin:if len(tally[c]) == cMin:lMin.append(c)else:lMin = [c]cMin = len(tally[c])if len(lMin) == 0:return Noneelif len(lMin) == 1:return lMin[0]else:trace("\teliminating low candidate randomly from %s" % lMin)randcalls += 1return random.choice(lMin)def redistributeLoser(loser, losers, winners, tally, quota):excess = len(tally[loser])if excess <= 0:trace("\tno ballots to redistribute")else:trace("\tredistributing %d ballot(s) from %s" % (excess, loser))while len(tally[loser]) > 0:ballot = tally[loser][0]tally[loser] = tally[loser][1:]redistributeBallot(ballot, winners, losers, tally)traceTally(quota, tally)returndef traceTally(quota, tally):global fTraceif not fTrace: returntrace("\nCURRENT ASSIGNMENT OF BALLOTS (%d needed to win)" % quota)for candidate in tally.keys():trace("\t%s:" % candidate)for ballot in tally[candidate]:trace("\t\t%s" % ballot)return# The basic Single Transferable Vote algorithm with Hare quota## while winners < nWinners:# if a candidate has more than quota votes:# redistribute excess votes to next priority candidate# else:# eliminate lowest ranking candidate# redistribute wasted votes to next priority candidate#def dotally(nWinners, ballots):global randcallsrandcalls = 0nBallots = len(ballots)quota = int(math.ceil((nBallots + 1.0)/(nWinners + 1)))trace("INPUT SUMMARY AND SINGLE RUN TRACE")trace("\t%d ballots" % nBallots)trace("\tChoosing %s winners" % nWinners)trace("\tNeed ceil((%d + 1)/(%d + 1)) = %d ballots to win" %(nBallots, nWinners, quota))# Create initial tally and statistics#tally = {}for ballot in ballots:candidate = ballot[0]if not tally.has_key(candidate):tally[candidate] = []tally[candidate].append(ballot)total1st = {}total2nd = {}totalMrk = {}for ballot in ballots:for c in ballot:total1st[c] = 0total2nd[c] = 0totalMrk[c] = 0for ballot in ballots:total1st[ballot[0]] += 1if len(ballot) > 1:total2nd[ballot[1]] += 1for c in ballot:totalMrk[c] += 1trace("\n\tBALLOT MARKS\t1ST\t2ND\tNONE")candidates = totalMrk.keys()candidates.sort()for c in candidates:trace("\t%-16s%3d\t%3d\t%4d" % (c, total1st[c], total2nd[c], len(ballots)-totalMrk[c]))traceTally(quota, tally)winners = []losers = []while len(winners) < nWinners:winner = findWinner(winners, losers, tally, quota, nWinners)if winner:winners.append(winner)trace("\nSELECTION #%d: %s" % (len(winners), winner))redistributeWinner(winner, winners, losers, tally, quota)else:loser = findLoser(losers, winners, tally)if loser:losers.append(loser)trace("\nELIMINATED: %s" % loser)redistributeLoser(loser, losers, winners, tally, quota)else:trace("Not enough chosen candidates to fill all positions")breaktrace("\nNumber of random choices made: %d" % randcalls)return winners