-
Notifications
You must be signed in to change notification settings - Fork 9
/
schulze.py
102 lines (74 loc) · 3.68 KB
/
schulze.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
"""Ranks candidates by the Schulze method.
For more information read http://en.wikipedia.org/wiki/Schulze_method.
"""
__author__ = "Michael G. Parker"
__contact__ = "http://omgitsmgp.com/"
from collections import defaultdict
def _add_remaining_ranks(d, candidate_name, remaining_ranks, weight):
for remaining_rank in remaining_ranks:
for other_candidate_name in remaining_rank:
d[candidate_name, other_candidate_name] += weight
def _add_ranks_to_d(d, ranks, weight):
for i, rank in enumerate(ranks):
remaining_ranks = ranks[i+1:]
for candidate_name in rank:
_add_remaining_ranks(d, candidate_name, remaining_ranks, weight)
def _compute_d(weighted_ranks):
"""Computes the d array in the Schulze method.
d[V,W] is the number of voters who prefer candidate V over W.
"""
d = defaultdict(int)
for ranks, weight in weighted_ranks:
_add_ranks_to_d(d, ranks, weight)
return d
def _compute_p(d, candidate_names):
"""Computes the p array in the Schulze method.
p[V,W] is the strength of the strongest path from candidate V to W.
"""
p = {}
for candidate_name1 in candidate_names:
for candidate_name2 in candidate_names:
if candidate_name1 != candidate_name2:
strength = d.get((candidate_name1, candidate_name2), 0)
if strength > d.get((candidate_name2, candidate_name1), 0):
p[candidate_name1, candidate_name2] = strength
for candidate_name1 in candidate_names:
for candidate_name2 in candidate_names:
if candidate_name1 != candidate_name2:
for candidate_name3 in candidate_names:
if (candidate_name1 != candidate_name3) and (candidate_name2 != candidate_name3):
curr_value = p.get((candidate_name2, candidate_name3), 0)
new_value = min(
p.get((candidate_name2, candidate_name1), 0),
p.get((candidate_name1, candidate_name3), 0))
if new_value > curr_value:
p[candidate_name2, candidate_name3] = new_value
return p
def _rank_p(candidate_names, p):
"""Ranks the candidates by p."""
candidate_wins = defaultdict(list)
for candidate_name1 in candidate_names:
num_wins = 0
# Compute the number of wins this candidate has over all other candidates.
for candidate_name2 in candidate_names:
if candidate_name1 == candidate_name2:
continue
candidate1_score = p.get((candidate_name1, candidate_name2), 0)
candidate2_score = p.get((candidate_name2, candidate_name1), 0)
if candidate1_score > candidate2_score:
num_wins += 1
candidate_wins[num_wins].append(candidate_name1)
sorted_wins = sorted(candidate_wins.iterkeys(), reverse=True)
return [candidate_wins[num_wins] for num_wins in sorted_wins]
def compute_ranks(candidate_names, weighted_ranks):
"""Returns the candidates ranked by the Schulze method.
See http://en.wikipedia.org/wiki/Schulze_method for details.
Parameter candidate_names is a sequence containing all the candidate names.
Parameter weighted_ranks is a sequence of (ranks, weight) pairs.
The first element, ranks, is a ranking of the candidates. It is an array of arrays so that we
can express ties. For example, [[a, b], [c], [d, e]] represents a = b > c > d = e.
The second element, weight, is typically the number of voters that chose this ranking.
"""
d = _compute_d(weighted_ranks)
p = _compute_p(d, candidate_names)
return _rank_p(candidate_names, p)