implement single transferable vote
This commit is contained in:
parent
b1dfabd515
commit
705df55ef9
|
@ -0,0 +1,2 @@
|
|||
secrets.env
|
||||
venv
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
echo "BEARER=???" > secrets.env
|
||||
echo "API_VOTE=https://voting.2021.egoi.ch/api/votes/{question_id}" >> secrets.env
|
|
@ -0,0 +1,2 @@
|
|||
python-dotenv
|
||||
requests
|
|
@ -0,0 +1,128 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import requests
|
||||
import os
|
||||
|
||||
from io import StringIO
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
def get_votes(ids):
|
||||
votes = {}
|
||||
for id in ids:
|
||||
res = requests.get(os.environ['API_VOTE'].format(question_id=id), headers={'Authorization': 'Bearer ' + os.environ['BEARER']})
|
||||
if not res.ok:
|
||||
print('Could not get votes for id', id)
|
||||
continue
|
||||
f = StringIO(res.text)
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
votes.setdefault(row['Delegation'], []).append(row['Option'])
|
||||
return list(votes.values())
|
||||
|
||||
def get_candidates_from_votes(votes):
|
||||
candidates = set()
|
||||
for vote in votes:
|
||||
for option in vote:
|
||||
candidates.add(option)
|
||||
return candidates
|
||||
|
||||
def determine_winners(people, votes):
|
||||
"""Single Transferable Vote (https://en.wikipedia.org/wiki/Single_transferable_vote)"""
|
||||
elected = []
|
||||
elected_set = set()
|
||||
candidates = get_candidates_from_votes(votes)
|
||||
surplus_votes = {x: 0 for x in candidates}
|
||||
quota = len(votes) / people + 1
|
||||
|
||||
print('Votes:', len(votes))
|
||||
print('Quota:', quota)
|
||||
print('Candidates:', len(candidates))
|
||||
|
||||
def one_round():
|
||||
print('-----------')
|
||||
print('Start Round')
|
||||
print('-----------')
|
||||
round_votes = {x: 0 for x in candidates}
|
||||
for vote in votes:
|
||||
vote_weight = 1
|
||||
for option in vote:
|
||||
if option in elected_set:
|
||||
vote_weight *= surplus_votes[option]
|
||||
if option in candidates and option not in elected_set:
|
||||
round_votes[option] += vote_weight
|
||||
break
|
||||
vote_counts = []
|
||||
for candidate in candidates:
|
||||
if candidate in elected_set:
|
||||
continue
|
||||
vote_counts.append((round_votes[candidate], candidate))
|
||||
vote_counts.sort()
|
||||
print('Vote counts:')
|
||||
for (count, candidate) in reversed(vote_counts):
|
||||
print(' {}: {}'.format(candidate, count))
|
||||
if vote_counts[-1][0] >= quota or len(candidates) == people:
|
||||
elected.append((vote_counts[-1][1], vote_counts[-1][0]))
|
||||
elected_set.add(vote_counts[-1][1])
|
||||
|
||||
surplus = vote_counts[-1][0] - quota
|
||||
if surplus > 0:
|
||||
surplus_votes[vote_counts[-1][1]] = surplus / vote_counts[-1][0]
|
||||
|
||||
print('Elect', vote_counts[-1][1])
|
||||
elif len(candidates) > people:
|
||||
candidates.remove(vote_counts[0][1])
|
||||
print('Remove', vote_counts[0][1])
|
||||
|
||||
while len(elected_set) < people:
|
||||
one_round()
|
||||
|
||||
return elected
|
||||
|
||||
def test_votes():
|
||||
return [
|
||||
['O'],
|
||||
['O'],
|
||||
['O'],
|
||||
['O'],
|
||||
['P', 'O'],
|
||||
['P', 'O'],
|
||||
['C', 'S'],
|
||||
['C', 'S'],
|
||||
['C', 'S'],
|
||||
['C', 'S'],
|
||||
['C', 'S'],
|
||||
['C', 'S'],
|
||||
['C', 'S'],
|
||||
['C', 'S'],
|
||||
['C', 'H'],
|
||||
['C', 'H'],
|
||||
['C', 'H'],
|
||||
['C', 'H'],
|
||||
['S'],
|
||||
['H'],
|
||||
]
|
||||
|
||||
def main():
|
||||
load_dotenv('secrets.env')
|
||||
parser = argparse.ArgumentParser(description='Determine voted persons from list of votes.')
|
||||
parser.add_argument('--test', action='store_true', help='Use test values for the votes')
|
||||
parser.add_argument('people', type=int, help='Number of people who need to be voted.')
|
||||
parser.add_argument('questions', type=int, nargs='+', help='IDs of the questions.')
|
||||
args = parser.parse_args()
|
||||
if args.test:
|
||||
votes = test_votes()
|
||||
else:
|
||||
votes = get_votes(args.questions)
|
||||
winners = determine_winners(args.people, votes)
|
||||
|
||||
print('-------------')
|
||||
print('Final results')
|
||||
print('-------------')
|
||||
for i, (winner, votes) in enumerate(winners):
|
||||
print('{:2} with {:2} votes: {}'.format(i+1, int(votes), winner))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue