Module voting
Expand source code
###############################
# N round first past the post #
###############################
def N_rounds (ranked, turns):
"""
Function used for **plurality**, **two rounds**, **instant runoff**, **condorcet** and **borda** voting methods.
*Prototype*:
* **ranked** (*dict*) : keys are the electors, values are lists of candidates sorted by preferences
* **turns** (*int*) : current turn (decreasing value)
* **return** (*int*) : winning candidate
*Notes*:
* **N_rounds()** is recursive, the final return type differs from the others, while there are remaining turns
*Algorithm*:
```
if turns = 0 then
return the winner
else
results <- sorted dictionary
// keys are the candidates and values are the number of voters for
// whom this candidate is the favourite
if the first candidate as more than half of the votes then
return the winner
else
ranked <- dictionary
// keys are the electors, values are lists of candidates
// sorted by preferences, last candidate is removed
return N_rounds(ranked, turns - 1)
```
"""
if turns == 0:
return ranked[0][0]
else:
results = {candidate: len([elector for elector in ranked if ranked[elector][0] == candidate]) for candidate in ranked[0]}
results = {candidate: electors for candidate, electors in sorted(results.items(), key=lambda item: item[1], reverse=True)}
majors = [candidate for candidate in results.keys()][:turns]
if (results[majors[0]] > len(ranked)/2):
return majors[0]
else:
ranked = {elector: [candidate for candidate in ranked[elector] if candidate in majors] for elector in ranked}
return N_rounds (ranked, turns-1)
def condorcet(ranked):
"""
Function used for **condorcet** and **borda** voting methods.
*Prototype*:
* **ranked** (*dict*) : keys are the electors, values are lists of candidates sorted by preferences
* **return** (*int*) : winning candidate
*Notes*:
* In equality cases, a recursive call is performed with the winners, as long as we can remove candidates
* In some cases of a tie, there may not be a winner (*None* returned)
*Algorithm*:
```
pairs <- list of pair of candidates
score <- dictionary
// keys are the candidates, values are the number of duels won
// by this candidate
score <- filtered dictionary, only the candidates with the most wins remain
if there is only one winner then
return the winner
else
subset_ranked <- ranked filtered
// by weither if the candidate is one of the winners or not
if there is less candidates in subset_ranked than in ranked then
return condorcet(subset_ranked)
```
"""
pairs = [(candidateA, candidateB) for candidateA in ranked[0] for candidateB in ranked[0] if candidateA < candidateB]
victories = {}
score = {candidate: 0 for candidate in ranked[0]}
for pair in pairs:
subset_ranked = {elector: [candidate for candidate in ranked[elector] if candidate in pair] for elector in ranked}
victories[pair] = N_rounds(subset_ranked, 1)
score[victories[pair]] += 1
score = {candidate: score[candidate] for candidate in score if score[candidate] == max([value for key, value in score.items()])}
if len(score) == 1:
return [candidate for candidate in score.keys()][0]
subset_ranked = {elector: [candidate for candidate in ranked[elector] if candidate in [candidate for candidate in score.keys()]] for elector in ranked}
if sorted(set(subset_ranked[0])) != sorted(set(ranked[0])):
return (condorcet(subset_ranked))
def borda(ranked):
"""
Function used for **borda** voting method.
*Prototype*:
* **ranked** (*dict*) : keys are the electors, values are lists of candidates sorted by preferences
* **return** (*int*) : winning candidate
*Notes*:
* In equality cases, **condorcet** method is called to decide
* In some cases of a tie, there may not be a winner (*None* returned)
*Algorithm*:
```
results <- sorted dictionary
// keys are the candidates, values are the sum of the points given by the
// voters for this candidate, electors give as many points as there are
// candidates to their favorite, then one point less to the second favorite
results <- filtered dictionary
// by weither if the candidate has the more points or not
if there is only one winner then
return the winner
else
subset_ranked <- ranked filtered
// by weither if the candidate is one of the winners or not
return condorcet(subset_ranked)
```
"""
results = {candidate: 0 for candidate in ranked[0]}
number_of_candidates = len(ranked[0])
for elector in ranked:
for i in range (0, number_of_candidates):
results[ranked[elector][i]] += (number_of_candidates - i)
results = {candidate: electors for candidate, electors in sorted(results.items(), key=lambda item: item[1], reverse=True)}
results = {candidate: results[candidate] for candidate in results if results[candidate] == max([value for key, value in results.items()])}
if len(results) == 1:
return [candidate for candidate in results.keys()][0]
else:
subset_ranked = {elector: [candidate for candidate in ranked[elector] if candidate in [candidate for candidate in results.keys()]] for elector in ranked}
return (condorcet(subset_ranked))
def score_voting(distances, scale_size, threshold):
"""
Function used for **approval** and **majority judgement** voting methods.
*Prototype*:
* **ranked** (*dict*) : keys are the electors, values are dictionaries whose keys are candidates and values are the distance between the elector and the candidate
* **scale_size** (*int*) : number of areas into which to divide the position space, must be strictly greater than 1
* **threshold** (*float*) : rejection threshold
* **return** (*dict*) : keys are the candidates, values are lists indexed by the scores, from the better to the worst, elements of the lists are the number of voters giving this score to the candidate
*Notes*:
* If the position space is divided into N areas, there will be N-1 areas below the rejection threshold, and one above
* As the position space is divided in areas, it doesn't matter if it corresponds to notes or mentions
*Algorithm*:
```
for each candidate, for each elector
for i in areas
if the distance between the two is in this areas then
results for this candidate for this areas increments
if the distance is strictly greater than the rejection threshold then
results for this candidate for the threshold increments
return results
```
"""
results = {c: [0 for i in range(0, scale_size)] for c in distances[0]}
for c in distances[0]:
for e in distances:
for i in range(0, scale_size-1):
if i*threshold/(scale_size-1) < distances[e][c] <= (i+1)*threshold/(scale_size-1):
results[c][i] +=1
if distances[e][c] > threshold:
results[c][scale_size-1] += 1
return results
def approval (distances, threshold):
"""
Function used for **approval** voting method.
*Prototype*:
* **ranked** (*dict*) : keys are the electors, values are dictionaries whose keys are candidates and values are the distance between the elector and the candidate
* **threshold** (*float*) : rejection threshold
* **return** (*int*) : winning candidate
*Notes*:
* In some cases of a tie, there may not be a winner (*None* returned)
*Algorithm*:
```pseudo-code
results <- score_voting(distances, scale=2, threshold)
winner <- list of candidates with the most votes in the first grade
if there is only one winner then
return the winner
```
"""
scale = 2
results = score_voting(distances, scale, threshold)
winner = [c for c in distances[0] if results[c][0] == max([results[c][0] for c in distances[0]])]
if len(winner) == 1:
return winner[0]
def majority_judgement (distances, threshold):
"""
Function used for **majority judgement** voting method.
*Prototype*:
* **ranked** (*dict*) : keys are the electors, values are dictionaries whose keys are candidates and values are the distance between the elector and the candidate
* **threshold** (*float*) : rejection threshold
* **return** (*int*) : winning candidate
*Notes*:
* The number of mentions is 6, so there is no "middle mention"
* The tie-breaking is done by minimizing the number of opponents (i.e the number of electors that gave a lesser mention that the majority mention)
* The second tie-breaking, if necessary, is done by maximizing the number of supporters (i.e the number of electors that gave a better mention)
* In some cases of a tie, there may not be a winner (*None* returned)
* Because of the space of the positions, the majority mention of the best candidates can be the rejection
*Algorithm*:
```
results <- score_voting(distances, scale=6, threshold)
cumulative <- dictionary
// keys are the candidates, values are lists indexed by the mentions, from
// the better to the worst, elements of the lists are the number of voters
// giving at least this mention to the candidate
majority_mentions <- dictionary
// keys are the candidates, values are the majority mentions
winner <- list of candidates with the better majority mention
if the better majority mentions is better than rejection then
opponents <- dictionary
// keys are candidates, values are the number of opponents
winner <- filtered winner
// by weither if the candidate has the minimum of opponents
if there is only one winner then
return the winner
partisants <- dictionary
// keys are candidates, values are the number of partisants
winner <- filtered winner
// by weither if the candidate has the maximum of partisants
if there is only one winner then
return the winner
```
"""
scale = 6
results = score_voting(distances, scale, threshold)
cumulative = {c: [sum(results[c][:i+1]) for i in range(0, scale)] for c in results}
majority_mentions = {c: i for i in range(scale-1, -1, -1) for c in results if cumulative[c][i] > len(distances)/2}
winners = [c for c in majority_mentions if majority_mentions[c] == min([majority_mentions[c] for c in majority_mentions])]
if majority_mentions[winners[0]] != scale-1:
opponents = {c: sum(results[c][majority_mentions[c]+1:]) for c in winners}
winners = [c for c in winners if opponents[c] == min([opponents[c] for c in winners])]
if len(winners) == 1:
return winners[0]
partisants = {c: sum(results[c][:majority_mentions[c]]) for c in winners}
winners = [c for c in winners if partisants[c] == max([partisants[c] for c in winners])]
if len(winners) == 1:
return winners[0]
Functions
def N_rounds(ranked, turns)
-
Function used for plurality, two rounds, instant runoff, condorcet and borda voting methods.
Prototype:
- ranked (dict) : keys are the electors, values are lists of candidates sorted by preferences
- turns (int) : current turn (decreasing value)
- return (int) : winning candidate
Notes:
- N_rounds() is recursive, the final return type differs from the others, while there are remaining turns
Algorithm:
if turns = 0 then return the winner else results <- sorted dictionary // keys are the candidates and values are the number of voters for // whom this candidate is the favourite if the first candidate as more than half of the votes then return the winner else ranked <- dictionary // keys are the electors, values are lists of candidates // sorted by preferences, last candidate is removed return N_rounds(ranked, turns - 1)
Expand source code
def N_rounds (ranked, turns): """ Function used for **plurality**, **two rounds**, **instant runoff**, **condorcet** and **borda** voting methods. *Prototype*: * **ranked** (*dict*) : keys are the electors, values are lists of candidates sorted by preferences * **turns** (*int*) : current turn (decreasing value) * **return** (*int*) : winning candidate *Notes*: * **N_rounds()** is recursive, the final return type differs from the others, while there are remaining turns *Algorithm*: ``` if turns = 0 then return the winner else results <- sorted dictionary // keys are the candidates and values are the number of voters for // whom this candidate is the favourite if the first candidate as more than half of the votes then return the winner else ranked <- dictionary // keys are the electors, values are lists of candidates // sorted by preferences, last candidate is removed return N_rounds(ranked, turns - 1) ``` """ if turns == 0: return ranked[0][0] else: results = {candidate: len([elector for elector in ranked if ranked[elector][0] == candidate]) for candidate in ranked[0]} results = {candidate: electors for candidate, electors in sorted(results.items(), key=lambda item: item[1], reverse=True)} majors = [candidate for candidate in results.keys()][:turns] if (results[majors[0]] > len(ranked)/2): return majors[0] else: ranked = {elector: [candidate for candidate in ranked[elector] if candidate in majors] for elector in ranked} return N_rounds (ranked, turns-1)
def approval(distances, threshold)
-
Function used for approval voting method.
Prototype:
- ranked (dict) : keys are the electors, values are dictionaries whose keys are candidates and values are the distance between the elector and the candidate
- threshold (float) : rejection threshold
- return (int) : winning candidate
Notes:
- In some cases of a tie, there may not be a winner (None returned)
Algorithm:
results <- score_voting(distances, scale=2, threshold) winner <- list of candidates with the most votes in the first grade if there is only one winner then return the winner
Expand source code
def approval (distances, threshold): """ Function used for **approval** voting method. *Prototype*: * **ranked** (*dict*) : keys are the electors, values are dictionaries whose keys are candidates and values are the distance between the elector and the candidate * **threshold** (*float*) : rejection threshold * **return** (*int*) : winning candidate *Notes*: * In some cases of a tie, there may not be a winner (*None* returned) *Algorithm*: ```pseudo-code results <- score_voting(distances, scale=2, threshold) winner <- list of candidates with the most votes in the first grade if there is only one winner then return the winner ``` """ scale = 2 results = score_voting(distances, scale, threshold) winner = [c for c in distances[0] if results[c][0] == max([results[c][0] for c in distances[0]])] if len(winner) == 1: return winner[0]
def borda(ranked)
-
Function used for borda voting method.
Prototype:
- ranked (dict) : keys are the electors, values are lists of candidates sorted by preferences
- return (int) : winning candidate
Notes:
- In equality cases, condorcet method is called to decide
- In some cases of a tie, there may not be a winner (None returned)
Algorithm:
results <- sorted dictionary // keys are the candidates, values are the sum of the points given by the // voters for this candidate, electors give as many points as there are // candidates to their favorite, then one point less to the second favorite results <- filtered dictionary // by weither if the candidate has the more points or not if there is only one winner then return the winner else subset_ranked <- ranked filtered // by weither if the candidate is one of the winners or not return condorcet(subset_ranked)
Expand source code
def borda(ranked): """ Function used for **borda** voting method. *Prototype*: * **ranked** (*dict*) : keys are the electors, values are lists of candidates sorted by preferences * **return** (*int*) : winning candidate *Notes*: * In equality cases, **condorcet** method is called to decide * In some cases of a tie, there may not be a winner (*None* returned) *Algorithm*: ``` results <- sorted dictionary // keys are the candidates, values are the sum of the points given by the // voters for this candidate, electors give as many points as there are // candidates to their favorite, then one point less to the second favorite results <- filtered dictionary // by weither if the candidate has the more points or not if there is only one winner then return the winner else subset_ranked <- ranked filtered // by weither if the candidate is one of the winners or not return condorcet(subset_ranked) ``` """ results = {candidate: 0 for candidate in ranked[0]} number_of_candidates = len(ranked[0]) for elector in ranked: for i in range (0, number_of_candidates): results[ranked[elector][i]] += (number_of_candidates - i) results = {candidate: electors for candidate, electors in sorted(results.items(), key=lambda item: item[1], reverse=True)} results = {candidate: results[candidate] for candidate in results if results[candidate] == max([value for key, value in results.items()])} if len(results) == 1: return [candidate for candidate in results.keys()][0] else: subset_ranked = {elector: [candidate for candidate in ranked[elector] if candidate in [candidate for candidate in results.keys()]] for elector in ranked} return (condorcet(subset_ranked))
def condorcet(ranked)
-
Function used for condorcet and borda voting methods.
Prototype:
- ranked (dict) : keys are the electors, values are lists of candidates sorted by preferences
- return (int) : winning candidate
Notes:
- In equality cases, a recursive call is performed with the winners, as long as we can remove candidates
- In some cases of a tie, there may not be a winner (None returned)
Algorithm:
pairs <- list of pair of candidates score <- dictionary // keys are the candidates, values are the number of duels won // by this candidate score <- filtered dictionary, only the candidates with the most wins remain if there is only one winner then return the winner else subset_ranked <- ranked filtered // by weither if the candidate is one of the winners or not if there is less candidates in subset_ranked than in ranked then return condorcet(subset_ranked)
Expand source code
def condorcet(ranked): """ Function used for **condorcet** and **borda** voting methods. *Prototype*: * **ranked** (*dict*) : keys are the electors, values are lists of candidates sorted by preferences * **return** (*int*) : winning candidate *Notes*: * In equality cases, a recursive call is performed with the winners, as long as we can remove candidates * In some cases of a tie, there may not be a winner (*None* returned) *Algorithm*: ``` pairs <- list of pair of candidates score <- dictionary // keys are the candidates, values are the number of duels won // by this candidate score <- filtered dictionary, only the candidates with the most wins remain if there is only one winner then return the winner else subset_ranked <- ranked filtered // by weither if the candidate is one of the winners or not if there is less candidates in subset_ranked than in ranked then return condorcet(subset_ranked) ``` """ pairs = [(candidateA, candidateB) for candidateA in ranked[0] for candidateB in ranked[0] if candidateA < candidateB] victories = {} score = {candidate: 0 for candidate in ranked[0]} for pair in pairs: subset_ranked = {elector: [candidate for candidate in ranked[elector] if candidate in pair] for elector in ranked} victories[pair] = N_rounds(subset_ranked, 1) score[victories[pair]] += 1 score = {candidate: score[candidate] for candidate in score if score[candidate] == max([value for key, value in score.items()])} if len(score) == 1: return [candidate for candidate in score.keys()][0] subset_ranked = {elector: [candidate for candidate in ranked[elector] if candidate in [candidate for candidate in score.keys()]] for elector in ranked} if sorted(set(subset_ranked[0])) != sorted(set(ranked[0])): return (condorcet(subset_ranked))
def majority_judgement(distances, threshold)
-
Function used for majority judgement voting method.
Prototype:
- ranked (dict) : keys are the electors, values are dictionaries whose keys are candidates and values are the distance between the elector and the candidate
- threshold (float) : rejection threshold
- return (int) : winning candidate
Notes:
- The number of mentions is 6, so there is no "middle mention"
- The tie-breaking is done by minimizing the number of opponents (i.e the number of electors that gave a lesser mention that the majority mention)
- The second tie-breaking, if necessary, is done by maximizing the number of supporters (i.e the number of electors that gave a better mention)
- In some cases of a tie, there may not be a winner (None returned)
- Because of the space of the positions, the majority mention of the best candidates can be the rejection
Algorithm:
results <- score_voting(distances, scale=6, threshold) cumulative <- dictionary // keys are the candidates, values are lists indexed by the mentions, from // the better to the worst, elements of the lists are the number of voters // giving at least this mention to the candidate majority_mentions <- dictionary // keys are the candidates, values are the majority mentions winner <- list of candidates with the better majority mention if the better majority mentions is better than rejection then opponents <- dictionary // keys are candidates, values are the number of opponents winner <- filtered winner // by weither if the candidate has the minimum of opponents if there is only one winner then return the winner partisants <- dictionary // keys are candidates, values are the number of partisants winner <- filtered winner // by weither if the candidate has the maximum of partisants if there is only one winner then return the winner
Expand source code
def majority_judgement (distances, threshold): """ Function used for **majority judgement** voting method. *Prototype*: * **ranked** (*dict*) : keys are the electors, values are dictionaries whose keys are candidates and values are the distance between the elector and the candidate * **threshold** (*float*) : rejection threshold * **return** (*int*) : winning candidate *Notes*: * The number of mentions is 6, so there is no "middle mention" * The tie-breaking is done by minimizing the number of opponents (i.e the number of electors that gave a lesser mention that the majority mention) * The second tie-breaking, if necessary, is done by maximizing the number of supporters (i.e the number of electors that gave a better mention) * In some cases of a tie, there may not be a winner (*None* returned) * Because of the space of the positions, the majority mention of the best candidates can be the rejection *Algorithm*: ``` results <- score_voting(distances, scale=6, threshold) cumulative <- dictionary // keys are the candidates, values are lists indexed by the mentions, from // the better to the worst, elements of the lists are the number of voters // giving at least this mention to the candidate majority_mentions <- dictionary // keys are the candidates, values are the majority mentions winner <- list of candidates with the better majority mention if the better majority mentions is better than rejection then opponents <- dictionary // keys are candidates, values are the number of opponents winner <- filtered winner // by weither if the candidate has the minimum of opponents if there is only one winner then return the winner partisants <- dictionary // keys are candidates, values are the number of partisants winner <- filtered winner // by weither if the candidate has the maximum of partisants if there is only one winner then return the winner ``` """ scale = 6 results = score_voting(distances, scale, threshold) cumulative = {c: [sum(results[c][:i+1]) for i in range(0, scale)] for c in results} majority_mentions = {c: i for i in range(scale-1, -1, -1) for c in results if cumulative[c][i] > len(distances)/2} winners = [c for c in majority_mentions if majority_mentions[c] == min([majority_mentions[c] for c in majority_mentions])] if majority_mentions[winners[0]] != scale-1: opponents = {c: sum(results[c][majority_mentions[c]+1:]) for c in winners} winners = [c for c in winners if opponents[c] == min([opponents[c] for c in winners])] if len(winners) == 1: return winners[0] partisants = {c: sum(results[c][:majority_mentions[c]]) for c in winners} winners = [c for c in winners if partisants[c] == max([partisants[c] for c in winners])] if len(winners) == 1: return winners[0]
def score_voting(distances, scale_size, threshold)
-
Function used for approval and majority judgement voting methods.
Prototype:
- ranked (dict) : keys are the electors, values are dictionaries whose keys are candidates and values are the distance between the elector and the candidate
- scale_size (int) : number of areas into which to divide the position space, must be strictly greater than 1
- threshold (float) : rejection threshold
- return (dict) : keys are the candidates, values are lists indexed by the scores, from the better to the worst, elements of the lists are the number of voters giving this score to the candidate
Notes:
- If the position space is divided into N areas, there will be N-1 areas below the rejection threshold, and one above
- As the position space is divided in areas, it doesn't matter if it corresponds to notes or mentions
Algorithm:
for each candidate, for each elector for i in areas if the distance between the two is in this areas then results for this candidate for this areas increments if the distance is strictly greater than the rejection threshold then results for this candidate for the threshold increments return results
Expand source code
def score_voting(distances, scale_size, threshold): """ Function used for **approval** and **majority judgement** voting methods. *Prototype*: * **ranked** (*dict*) : keys are the electors, values are dictionaries whose keys are candidates and values are the distance between the elector and the candidate * **scale_size** (*int*) : number of areas into which to divide the position space, must be strictly greater than 1 * **threshold** (*float*) : rejection threshold * **return** (*dict*) : keys are the candidates, values are lists indexed by the scores, from the better to the worst, elements of the lists are the number of voters giving this score to the candidate *Notes*: * If the position space is divided into N areas, there will be N-1 areas below the rejection threshold, and one above * As the position space is divided in areas, it doesn't matter if it corresponds to notes or mentions *Algorithm*: ``` for each candidate, for each elector for i in areas if the distance between the two is in this areas then results for this candidate for this areas increments if the distance is strictly greater than the rejection threshold then results for this candidate for the threshold increments return results ``` """ results = {c: [0 for i in range(0, scale_size)] for c in distances[0]} for c in distances[0]: for e in distances: for i in range(0, scale_size-1): if i*threshold/(scale_size-1) < distances[e][c] <= (i+1)*threshold/(scale_size-1): results[c][i] +=1 if distances[e][c] > threshold: results[c][scale_size-1] += 1 return results