For learning purposes, I made this little class which determines the Condorcet winner of a rank voting election (basically, a Condorcet winner is a candidate that would beat every other candidate in a head-to-head election).
This code does work, but I'd like to learn two-three things by posting it here:
- Is this class written in a correct Pythonic way?
- Do you see any ways to improve this code?
- (bonus) If there are voting specialists among you, could you tell if that way of going through a Condorcet algorithm seems correct?
Condorcet.py:
import itertools
class Condorcet:
"""
Gives Concorcet winner from a list of votes
"""
def __init__(self, filename):
self.candidates = set()
self.scores = dict() # score for each permutation of two candidates
self.results = dict() # winner of each pair of candidates
self.winner = None # Condorcet winner of the election
self.voters = list() # ranking of each voter
self.filename = filename
def process(self):
self._get_data_from_file()
self._build_dict()
self._matches_candidates()
self._elect_winner()
return self.winner
def _get_data_from_file(self):
with open(self.filename, encoding='utf-8') as file:
for lines in file:
# currently hard-coding the number of candidates at 4
# needs fixing but it's not my point here
# (more a question for StackOverflow actually)
(one, two, three, four) = lines.split(None, 4)
self.voters.append((one, two, three, four))
def _build_dict(self):
"""
Builds a dictionary of scores
for each permutation of two candidates
"""
for voting in self.voters:
for candidate in voting:
self.candidates.add(candidate)
for pair in list(itertools.permutations(voting, 2)):
if pair not in self.scores:
self.scores[pair] = 0
if voting.index(pair[0]) < voting.index(pair[1]):
self.scores[pair] += 1
def _matches_candidates(self):
"""
Analyses the dictionary of scores and
gives the winner of every pair of candidates
"""
for match in list(itertools.combinations(self.candidates, 2)):
reverse = tuple(reversed(match))
if self.scores[match] > self.scores[reverse]:
self.results[match] = match[0]
else:
self.results[match] = match[1]
def _elect_winner(self):
"""
If a candidates is the winner against
every other candidate, declares him the winner
(Note: does not detect Condorcet cycles yet)
"""
for candidate in self.candidates:
candidate_score = 0
for result in self.results:
if candidate in result and self.results[result] == candidate:
candidate_score += 1
if candidate_score == len(self.candidates) - 1:
self.winner = candidate
execute_condorcet.py:
import Condorcet
condorcet = Condorcet.Condorcet('condorcet.txt')
winner = condorcet.process()
print(winner)
condorcet.txt:
# each line represents the ranking
# of candidates A, B, C and D
# by a unique voter
A B C D # this voter ranked A first, B second, C third, D fourth
C B D A
A B D C
C A D B
C B A D
C A B D
# and it goes on
and the winner could be C, beating :
- A 4 votes to 2
- B 4 votes to 2
- D 5 votes to 1