Skip to content

Instantly share code, notes, and snippets.

@edvardm
Last active April 14, 2019 12:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edvardm/53e581515ddd195fd558bbe1adb37e7d to your computer and use it in GitHub Desktop.
Save edvardm/53e581515ddd195fd558bbe1adb37e7d to your computer and use it in GitHub Desktop.
Toy program for playing with D'Hondt system
import argparse
import logging
import random
import string
from itertools import groupby
from operator import itemgetter
PARTIES = ["Kesk", "Kok", "SDP", "Sin", "PS", "Vihr", "Vas", "RKP", "KD", "TL", "EOP", "FP", "IPU", "KP", "KTP", "Lib",
"PP", "SKE", "SKP"]
logging.basicConfig(level=logging.INFO)
def main():
def split_at(lst, index):
return lst[:index + 1], lst[index + 1:]
def chunks(l, n):
for i in range(0, len(l), n):
yield l[i:i + n]
def print_votes(lst, max_rows=None):
for i, chunk in enumerate(chunks(lst, 5), start=1):
if max_rows and i > max_rows:
break
for party, idx, votes in chunk:
print('{}{}: {:6d}'.format(party, idx, int(votes)), end="\t")
print()
print()
def rank_order(elect_count, votes):
def gen_weighted_votes(label, votes, max_count):
return [(label, i + 1, votes / float(i + 1)) for i in range(max_count)]
weighted_votes = []
for k, v in votes.items():
weighted_votes.extend(gen_weighted_votes(k, v, 10 * elect_count))
return weighted_votes
parser = argparse.ArgumentParser(description='Show D\'Hondt vote distribution')
parser.add_argument('votes', metavar='N', type=int, nargs='*',
help='Votes a party gets. First for A, second for B, third for C etc')
parser.add_argument('--seats', '-s', type=int, help='Number of seats to elect in total')
parser.add_argument('--party-count', '-p', type=int, default=4)
parser.add_argument('--num-voters', '-v', type=int, default=int(3E6), help='Total number of voters (use with -f)')
parser.add_argument('--max-party-votes', '-m', type=float, default=30.0,
help='Max number of votes for party (percentage)')
parser.add_argument('--fun-mode', '-f', action='store_true',
help='Map abstract party symbols to real parties, implies -c 19')
args = parser.parse_args()
zipper = lambda i: dict(zip(range(len(i)), i))
pos_to_party = zipper(string.ascii_uppercase)
if args.fun_mode:
logging.info("Funny mode enabled")
args.party_count = len(PARTIES)
args.seats = args.seats or 200
random.shuffle(PARTIES)
pos_to_party = zipper(PARTIES)
else:
args.seats = args.seats or 3
vote_list = args.votes
if not vote_list:
print(f'Using randomized votes for {args.party_count} parties')
vote_list = []
voters_left = args.num_voters
for _ in range(args.party_count):
vote_max_limit = min((args.max_party_votes / 100.0) * args.num_voters, voters_left)
votes = random.randint(0, vote_max_limit)
vote_list.append(votes)
voters_left -= votes
vote_list = sorted(vote_list, reverse=True)
for i, chunk in enumerate(chunks(vote_list, 5)):
print(', '.join(['{}: {}'.format(pos_to_party[5 * i + j], v) for j, v in enumerate(chunk)]))
vote_map = {}
for i, num_votes in enumerate(vote_list):
label = pos_to_party[i]
vote_map[label] = num_votes
total_votes = sum(vote_list)
elected, rest = split_at(sorted(rank_order(args.seats, vote_map), key=lambda x: -x[-1]), args.seats - 1)
print(f'Total votes: {total_votes}')
print()
print(f'Elected {args.seats} people, ordered by relative votes:')
print_votes(elected)
sorted_by_party = sorted(elected, key=itemgetter(0))
print('Seats and proportionate seats:')
by_seats = []
for party, entries in groupby(sorted_by_party, key=itemgetter(0)):
values = list(entries)
seats = len(values)
top_seats = values[0][-1]
prop = args.seats * top_seats / total_votes
by_seats.append((seats, party, prop))
for seats, party, prop in sorted(by_seats, reverse=True):
print('{:>6s}: {:<6d} / {:.2f}'.format(party, seats, prop))
print()
print("Top 3 rows for not chosen, ordered by relative votes:")
print_votes(rest, max_rows=3)
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment