I'm currently learning Python and having a great time so far (coming from PHP, I have a feeling of suddenly seeing the light).
At the same time, I'm studying voting algorithms. So, for training 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?
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) < voting.index(pair): 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 else: self.results[match] = match 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
import Condorcet condorcet = Condorcet.Condorcet('condorcet.txt') winner = condorcet.process() print(winner)
# 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