Subversion Repositories Local Hare Voting

Rev

Go to most recent revision | Details | 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
##
20
 
21
import sys
22
import math 
23
import random
24
 
25
fTrace = 1
26
 
27
def trace(s):
28
    global fTrace
29
    if fTrace: print s
30
    return
31
 
32
def findWinner(winners, losers, tally, quota, nWinners):
33
    for candidate in tally.keys():
34
        if candidate not in winners and len(tally[candidate]) >= quota:
35
            return candidate
36
 
37
    # Check to see if only enough candidates remain
38
    n = 0
39
    last = ""
40
    for candidate in tally.keys():
41
        if candidate not in winners and candidate not in losers:
42
            last = candidate
43
            n = n + 1
44
    if nWinners - len(winners) >= n:
45
        return last
46
    return
47
 
48
def redistributeWinner(winner, winners, losers, tally, quota):
49
    excess = len(tally[winner]) - quota
50
    if excess <= 0:
21 jtkorb 51
        trace("\tno excess ballots to redistribute")
15 jtkorb 52
    else:
21 jtkorb 53
        trace("\tredistributing %d excess ballot(s) at random from %s" %
54
              (excess, winner))
55
        while len(tally[winner]) > quota:
56
            i = int(random.uniform(0, len(tally[winner])))
57
            trace("\trandom choice = ballot %d" % (i+1))
58
            ballot = tally[winner][i]
59
            tally[winner] = tally[winner][0:i] + tally[winner][i+1:]
60
            redistributeBallot(ballot, winner, winners, losers, tally)
15 jtkorb 61
    traceTally(quota, tally)
62
 
63
def redistributeBallot(ballot, candidateFrom, winners, losers, tally):
64
    for candidateTo in ballot:
21 jtkorb 65
        if candidateTo not in winners and candidateTo not in losers:
66
            trace("\tto %s: %s" % (candidateTo, ballot))
67
            if not tally.has_key(candidateTo): tally[candidateTo] = []
68
            tally[candidateTo].append(ballot)
69
            ballot = ""
70
            break
15 jtkorb 71
    if ballot:
21 jtkorb 72
        trace("\tineffective ballot dropped: %s" % ballot)
15 jtkorb 73
 
74
def findLoser(losers, winners, tally):
75
    cMin = sys.maxint  # least number of votes for candidate loser
76
    lMin = []          # list of candidates with least votes
77
    for c in tally.keys():
78
        if c not in losers and c not in winners and len(tally[c]) <= cMin:
21 jtkorb 79
            if len(tally[c]) == cMin:
80
                lMin.append(c)
81
            else:
82
                lMin = [c]
83
                cMin = len(tally[c])
15 jtkorb 84
    trace("\nELIMINATING LOW CANDIDATE RANDOMLY FROM %s" % lMin)
85
    if len(lMin) == 0:
86
        return None
87
    else:
88
        return random.choice(lMin)
89
 
90
def redistributeLoser(loser, losers, winners, tally, quota):
91
    excess = len(tally[loser])
92
    if excess <= 0:
21 jtkorb 93
        trace("\tno ballots to redistribute")
15 jtkorb 94
    else:
21 jtkorb 95
        trace("\tredistributing %d ballot(s) from %s" % (excess, loser))
96
        while len(tally[loser]) > 0:
97
            ballot = tally[loser][0]
98
            tally[loser] = tally[loser][1:]
99
            redistributeBallot(ballot, loser, winners, losers, tally)
15 jtkorb 100
    traceTally(quota, tally)
101
    return
102
 
103
def traceTally(quota, tally):
104
    global fTrace
105
    if not fTrace: return
106
    trace("\nCURRENT ASSIGNMENT OF BALLOTS (%d needed to win)" % quota)
107
    for candidate in tally.keys():
108
        trace("\t%s:" % candidate)
109
        for ballot in tally[candidate]:
110
            trace("\t\t%s" % ballot)
111
    return
112
 
113
# The basic Single Transferable Vote algorithm with Hare quota
114
#
115
# while winners < nWinners:
116
#   if a candidate has more than quota votes:
117
#       redistribute excess votes to next priority candidate
118
#   else:
119
#       eliminate lowest ranking candidate
120
#       redistribute wasted votes to next priority candidate
121
#
122
def dotally(nWinners, ballots):
123
    nBallots = len(ballots)
124
    quota = int(math.ceil((nBallots + 1.0)/(nWinners + 1)))
125
 
126
    trace("INPUT SUMMARY")
127
    trace("\t%d ballots" % nBallots)
128
    trace("\tChoosing %s winners" % nWinners)
129
    trace("\tNeed ceil((%d + 1)/(%d + 1)) = %d ballots to win" %
21 jtkorb 130
          (nBallots, nWinners, quota))
15 jtkorb 131
 
132
    # Create initial tally
133
    #
134
    tally = {}
135
    for ballot in ballots:
136
        candidate = ballot[0]
137
        if not tally.has_key(candidate):
138
            tally[candidate] = []
139
        tally[candidate].append(ballot)
140
    traceTally(quota, tally)
141
 
142
    winners = []
143
    losers = []
144
 
145
    while len(winners) < nWinners:
146
        winner = findWinner(winners, losers, tally, quota, nWinners)
147
        if winner:
148
            winners.append(winner)
149
            trace("\nSELECTION #%d: %s" % (len(winners), winner))
150
            redistributeWinner(winner, winners, losers, tally, quota)
151
        else:
152
            loser = findLoser(losers, winners, tally)
153
            if loser:
154
                losers.append(loser)
155
                trace("\nELIMINATED: %s" % loser)
156
                redistributeLoser(loser, losers, winners, tally, quota)
157
            else:
158
                trace("Not enough chosen candidates to fill all positions")
159
                break
160
 
161
    return winners
162