Subversion Repositories Remote Hare Voting

Rev

Rev 22 | Rev 39 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
15 jtkorb 1
## Compute Hare Ballot
2
##
3
## Tim Korb (jtk@cs.purdue.edu), May 2007
4
##
5
## A few definitions and notes...
6
##
7
## tally: dictionary in which the keys are candidate names and the
8
## values are lists of ballots currently assigned to that candidate
9
##
10
## ballot: list of candidates in the order determined by the voter
11
##
12
## winners: list of candidates that have reached the quota of ballots
13
## or have remained in the running long enough to be declared a winner
14
##
15
## losers: list of candidates that have been eliminated from the running
16
##
17
## Note that plurals are generally used to indicate lists of other
18
## items, e.g., ballots is a list of ballot items.
19
##
24 jtkorb 20
## To download the complete source distribution, use Subversion:
21
##
22
##	% svn co http://www.bikmort.com/hare
23
##
15 jtkorb 24
 
25
import sys
26
import math 
27
import random
28
 
29
fTrace = 1
30
 
31
def trace(s):
32
    global fTrace
33
    if fTrace: print s
34
    return
35
 
36
def findWinner(winners, losers, tally, quota, nWinners):
37
    for candidate in tally.keys():
38
        if candidate not in winners and len(tally[candidate]) >= quota:
39
            return candidate
40
 
41
    # Check to see if only enough candidates remain
42
    n = 0
43
    last = ""
44
    for candidate in tally.keys():
45
        if candidate not in winners and candidate not in losers:
46
            last = candidate
47
            n = n + 1
48
    if nWinners - len(winners) >= n:
49
        return last
50
    return
51
 
52
def redistributeWinner(winner, winners, losers, tally, quota):
53
    excess = len(tally[winner]) - quota
54
    if excess <= 0:
21 jtkorb 55
        trace("\tno excess ballots to redistribute")
15 jtkorb 56
    else:
21 jtkorb 57
        trace("\tredistributing %d excess ballot(s) at random from %s" %
58
              (excess, winner))
59
        while len(tally[winner]) > quota:
60
            i = int(random.uniform(0, len(tally[winner])))
61
            trace("\trandom choice = ballot %d" % (i+1))
62
            ballot = tally[winner][i]
63
            tally[winner] = tally[winner][0:i] + tally[winner][i+1:]
64
            redistributeBallot(ballot, winner, winners, losers, tally)
15 jtkorb 65
    traceTally(quota, tally)
66
 
67
def redistributeBallot(ballot, candidateFrom, winners, losers, tally):
68
    for candidateTo in ballot:
21 jtkorb 69
        if candidateTo not in winners and candidateTo not in losers:
70
            trace("\tto %s: %s" % (candidateTo, ballot))
71
            if not tally.has_key(candidateTo): tally[candidateTo] = []
72
            tally[candidateTo].append(ballot)
73
            ballot = ""
74
            break
15 jtkorb 75
    if ballot:
21 jtkorb 76
        trace("\tineffective ballot dropped: %s" % ballot)
15 jtkorb 77
 
78
def findLoser(losers, winners, tally):
79
    cMin = sys.maxint  # least number of votes for candidate loser
80
    lMin = []          # list of candidates with least votes
81
    for c in tally.keys():
82
        if c not in losers and c not in winners and len(tally[c]) <= cMin:
21 jtkorb 83
            if len(tally[c]) == cMin:
84
                lMin.append(c)
85
            else:
86
                lMin = [c]
87
                cMin = len(tally[c])
15 jtkorb 88
    trace("\nELIMINATING LOW CANDIDATE RANDOMLY FROM %s" % lMin)
89
    if len(lMin) == 0:
90
        return None
91
    else:
92
        return random.choice(lMin)
93
 
94
def redistributeLoser(loser, losers, winners, tally, quota):
95
    excess = len(tally[loser])
96
    if excess <= 0:
21 jtkorb 97
        trace("\tno ballots to redistribute")
15 jtkorb 98
    else:
21 jtkorb 99
        trace("\tredistributing %d ballot(s) from %s" % (excess, loser))
100
        while len(tally[loser]) > 0:
101
            ballot = tally[loser][0]
102
            tally[loser] = tally[loser][1:]
103
            redistributeBallot(ballot, loser, winners, losers, tally)
15 jtkorb 104
    traceTally(quota, tally)
105
    return
106
 
107
def traceTally(quota, tally):
108
    global fTrace
109
    if not fTrace: return
110
    trace("\nCURRENT ASSIGNMENT OF BALLOTS (%d needed to win)" % quota)
111
    for candidate in tally.keys():
112
        trace("\t%s:" % candidate)
113
        for ballot in tally[candidate]:
114
            trace("\t\t%s" % ballot)
115
    return
116
 
117
# The basic Single Transferable Vote algorithm with Hare quota
118
#
119
# while winners < nWinners:
120
#   if a candidate has more than quota votes:
121
#       redistribute excess votes to next priority candidate
122
#   else:
123
#       eliminate lowest ranking candidate
124
#       redistribute wasted votes to next priority candidate
125
#
126
def dotally(nWinners, ballots):
127
    nBallots = len(ballots)
128
    quota = int(math.ceil((nBallots + 1.0)/(nWinners + 1)))
129
 
130
    trace("INPUT SUMMARY")
131
    trace("\t%d ballots" % nBallots)
132
    trace("\tChoosing %s winners" % nWinners)
133
    trace("\tNeed ceil((%d + 1)/(%d + 1)) = %d ballots to win" %
21 jtkorb 134
          (nBallots, nWinners, quota))
15 jtkorb 135
 
136
    # Create initial tally
137
    #
138
    tally = {}
139
    for ballot in ballots:
140
        candidate = ballot[0]
141
        if not tally.has_key(candidate):
142
            tally[candidate] = []
143
        tally[candidate].append(ballot)
144
    traceTally(quota, tally)
145
 
146
    winners = []
147
    losers = []
148
 
149
    while len(winners) < nWinners:
150
        winner = findWinner(winners, losers, tally, quota, nWinners)
151
        if winner:
152
            winners.append(winner)
153
            trace("\nSELECTION #%d: %s" % (len(winners), winner))
154
            redistributeWinner(winner, winners, losers, tally, quota)
155
        else:
156
            loser = findLoser(losers, winners, tally)
157
            if loser:
158
                losers.append(loser)
159
                trace("\nELIMINATED: %s" % loser)
160
                redistributeLoser(loser, losers, winners, tally, quota)
161
            else:
162
                trace("Not enough chosen candidates to fill all positions")
163
                break
164
 
165
    return winners
166