Вначале я не был уверен, было ли это подходящим местом для этого вопроса, но, прочитав FAQ, я уверен, что тема приемлема.... Кроме того, я не был уверен, будет ли это классифицировано как конкретный тип проблемы (например, проблема с рюкзаком), таким образом, название довольно расплывчато. Прошу прощения за это.
В любом случае. Как и практика для python, так и упражнение для лучшего понимания общих концепций программирования, я решил написать простейшее имитирование Instant-Runoff Vote. Описание моментального голосования в Runoff можно найти здесь: http://en.wikipedia.org/wiki/Instant-runoff_voting
В основном избиратель оценивает каждого кандидата, присваивая им номер, один из которых является их первым выбором, два являются их вторым выбором и т.д.... Если по окончанию голосования ни один кандидат не имеет большинства, кандидат с наименьшей долей исключается и голоса, которые пошли к ним, идут к кандидату на второй выбор избирателей.
Предполагая, что есть пять кандидатов и 20 избирателей, 100 голосов (5x20) должны быть отнесены, и каждый голос должен быть способен указать на избирателя, который его подал, и за кого голосование.
Чтобы представить это, я решил использовать вложенный список, чтобы каждый подсписок представлял собой один избиратель (или бюллетень), и каждый индекс этого подписок представлял собой единственный кандидат.
Визуализация:
[[1,3,2,5,4]...] Таким образом, бюллетень [0] [0] является избирателем 1 голос за кандидата 1
Хотя я считаю, что это довольно простой и эффективный способ справиться с этим (насколько я могу судить), я сталкиваюсь с трудностями при попытке:
a) Ранжировать кандидатов на основе количества голосов "1", которые они получают.
b) Распространять голоса после того, как кандидат был исключен
Я полагаю, что с достаточным количеством свернутых вложенных циклов и достаточных переменных я мог бы достичь обоих из них, но не без того, чтобы программа становилась излишне сложной и запутанной.
Вот программа до сих пор...
#!usr/bin/python
#Alt Voter Solution 3
import random
ballots = []
results = [0,0,0,0,0]
#Generate 20 ballots. Since each ballot has 5 seperate
#unique numbers, I felt it would be best if I just
#shuffled a list and appended it 20 times
for voters in range(20):
possible = [1,2,3,4,5]
for x in range(1):
shufvote = random.shuffle(possible)
ballots.append(possible)
for cand in range(5):
for voter in ballots:
if voter[cand] == 1:
results[cand] +=1
Так что да, это в значительной степени. Я думаю, что часть моей проблемы заключается в том, как я предпочитаю представлять данные (во вложенных списках). Если у кого есть какие-либо критические замечания или предложения, пожалуйста, поделитесь ими!: D
Спасибо
В следующем коде используется подход грубой силы (он не оптимизирован), но достаточно прочен:
#!usr/bin/env python
import random
import collections
# Candidates:
candidates = ['John', 'Max', 'Philip', 'Eric', 'Jane']
def simul_ballots(num_voters):
"""
Returns the (random) ballots of num_voters voters.
"""
ballots = []
choice = candidates[:]
for _ in range(num_voters):
random.shuffle(choice)
ballots.append(choice[:]) # Copy
return ballots
def get_counts(ballots):
"""
Returns the number of votes for each candidate placed first in the
ballots.
Candidates present in the ballots but found in any first ballot
places are given a count of zero.
"""
counts = dict()
for ballot in ballots:
vote = ballot[0]
if vote in counts:
counts[vote] += 1
else:
counts[vote] = 1
# Python 2.7+ replacement for the above code:
# counts = collections.Counter(ballot[0] for ballot in ballots)
candidates = set()
for ballot in ballots:
candidates.update(ballot)
for not_represented in set(candidates)-set(counts):
counts[not_represented] = 0
return counts
def get_winners(ballots):
"""
Returns the winners in the given ballots (lists of candidates), or
[] if there is no winner.
A winner is a candidate with 50 % or more of the votes, or a
candidate with as many votes as all the other candidates.
"""
counts = get_counts(ballots)
max_count = max(counts.values())
num_counts = sum(counts.values())
potential_winners = [candidate for (candidate, count) in counts.items()
if count == max_count]
if max_count >= num_counts/2. or len(potential_winners) == len(counts):
return potential_winners
else:
return []
def get_losers(ballots):
"""
Returns the loser(s) of the ballots, i.e. the candidate(s) with the
fewest voters.
Returns [] if all candidates have the same number of votes.
"""
counts = get_counts(ballots)
min_count = min(counts.values())
potential_losers = [candidate for (candidate, count) in counts.items()
if count == min_count]
if len(potential_losers) == len(counts):
return []
else:
return potential_losers
def remove_candidate(ballots, candidate):
"""
Removes the given candidate from the ballots.
"""
for ballot in ballots:
ballot.remove(candidate)
if __name__ == '__main__':
ballots = simul_ballots(20)
while True:
print "* Votes:"
for ballot in ballots:
print '-', ballot
print "=> Counts:", get_counts(ballots)
winners = get_winners(ballots)
if winners:
break
# The losers are removed:
losers = get_losers(ballots)
print '=> Losers:', losers
for loser in losers:
remove_candidate(ballots, loser)
print "Winners: ", winners
Выход идет следующим образом (с 4 кандидатами):
* Votes:
- ['Max', 'John', 'Eric', 'Philip']
- ['Philip', 'Max', 'Eric', 'John']
- ['Eric', 'Philip', 'John', 'Max']
- ['Philip', 'John', 'Max', 'Eric']
- ['Eric', 'Max', 'Philip', 'John']
- ['Max', 'Philip', 'John', 'Eric']
- ['Max', 'John', 'Eric', 'Philip']
- ['Eric', 'Philip', 'Max', 'John']
- ['Max', 'Eric', 'Philip', 'John']
- ['Philip', 'Max', 'Eric', 'John']
- ['John', 'Eric', 'Max', 'Philip']
- ['Philip', 'Eric', 'Max', 'John']
- ['Max', 'Philip', 'John', 'Eric']
- ['Philip', 'Max', 'John', 'Eric']
- ['Philip', 'Eric', 'Max', 'John']
- ['John', 'Philip', 'Eric', 'Max']
- ['John', 'Max', 'Philip', 'Eric']
- ['Eric', 'Philip', 'John', 'Max']
- ['John', 'Eric', 'Philip', 'Max']
- ['Philip', 'John', 'Max', 'Eric']
=> Counts: Counter({'Philip': 7, 'Max': 5, 'John': 4, 'Eric': 4})
=> Losers: ['John', 'Eric']
* Votes:
- ['Max', 'Philip']
- ['Philip', 'Max']
- ['Philip', 'Max']
- ['Philip', 'Max']
- ['Max', 'Philip']
- ['Max', 'Philip']
- ['Max', 'Philip']
- ['Philip', 'Max']
- ['Max', 'Philip']
- ['Philip', 'Max']
- ['Max', 'Philip']
- ['Philip', 'Max']
- ['Max', 'Philip']
- ['Philip', 'Max']
- ['Philip', 'Max']
- ['Philip', 'Max']
- ['Max', 'Philip']
- ['Philip', 'Max']
- ['Philip', 'Max']
- ['Philip', 'Max']
=> Counts: Counter({'Philip': 12, 'Max': 8})
Winners: ['Philip']
Этот код также может использовать модуль коллекций из Python 2.7+, как указано в комментарии.
Связи автоматически обрабатываются (все назначенные кандидаты объявляются победителями).
Возможные оптимизации включают группировку избирателей бюллетенями (если число избирателей превышает количество возможных избирательных бюллетеней), а также обновление подсчетов путем перераспределения счетов от неудачников (вместо повторного пересчета). Вышеупомянутая реализация обеспечивает ссылочную реализацию, результаты которой можно сравнить с оптимизированными версиями.:)
На любом этапе процедуры избирательный бюллетень "принадлежит" кандидатом, который не был исключен, и который имеет наименьший предпочтительный номер, написанный против их имени.
Следовательно, вам не нужно делать особый случай с начальным счетом, и вам не нужно эмулировать ручную процедуру и физически перераспределять голоса исключенных кандидатов; просто сделайте общее (re) количество грубой силы на каждом этапе - логика намного проще. Для реалистичного моделирования, где количество избирательных бюллетеней намного больше, чем количество возможных различных бюллетеней (факториал (N = число_кодов)), вы можете захотеть подсчитать голоса в N! посылки, прежде чем вы начнете.
псевдокод:
eliminated = set()
For each round:
(1) calculate "results" by tallying the ballots as specified above
(2) iterate over "results" to determine the possible winner [tie-break rule?]
(3) if the possible winner has a majority, declare the result and exit the loop.
(4) iterate over results to determine the ejectee [tie-break rule?]
(5) eliminated.add(ejected_candidate)
Очень сильный намек: не затрудняйте количество кандидатов и количество избирательных бюллетеней; сделайте их переменными входами для вашего script.
Обновление в ответ на комментарий
Вы писали:
Тот факт, что каждый бюллетень, при любом если только раунд голосования подает голос за одного кандидата означает, что мне не нужно беспокоиться о любые другие перечисленные настройки.
Я не уверен, что вы подразумеваете под словом "не беспокойтесь". Вам нужно изучить все N предпочтений, проигнорировать кандидаты, которые уже были устранены, и выбирая наиболее предпочтительного кандидата из остатка. Тогда, конечно, вы игнорируете остальных; все, что вам нужно сделать для кандидата "владелец", results[owner] += 1
.
Если это правило определения владельца, о котором вы беспокоитесь: это может быть показано как значение reductio ad adsurdum
. Вы не можете выделить бюллетень для уже исключенного кандидата. Вы не можете распределить баллы кандидату Y, если есть кандидат X, который является более предпочтительным в этом бюллетене, чем кандидат Y. Следовательно, единственным допустимым выбором является наиболее предпочтительный невыбранный кандидат.
О факториале (N): если есть 5 кандидатов, а действительная избирательная бумага должна иметь перестановку цифр 1,2,3,4,5, тогда существуют 5!
различные возможные избирательные бюллетени - 5 вариантов для 1-й кандидат, 4 для второго,..., 1 для 5-го. 5x4x3x2x1 == 5! == 120.
О посылках: представьте, что существует 100 000 действующих бюллетеней и 5 кандидатов. Счетчики устанавливают 120 ящиков и бросают каждую избирательную бумагу в соответствующий ящик, считая, когда они идут, или, может быть, они взвешивают каждый бит:-), или, может быть, они OCR каждый бюллетень и используют Python script, который использует collections.Counter
. "посылка" равна "содержимому такого бункера".
Он не отвечает на ваш вопрос напрямую, но я написал очень простую программу для вычисления результатов. Вы можете найти мою программу и unit test на github. Это может быть полезно.
То, что вы намеревались сделать в своем цикле, -
shufvote = possible[:]
random.shuffle(shufvote)
ballots.append(shufvote)
Получаете ли вы, что вы ожидали от этого?
Вышеприведенный код сначала копирует список возможных голосов, затем перетасовывает копию. Фактически, random.shuffle()
изменяет "на месте" список, который он задает как аргумент (он его не возвращает). Надеюсь, это поможет!
range(2)
(или более) вместо range(1)
(что эквивалентно отсутствию цикла); в случае range(1)
он действительно дает то же самое, что и ваш код.
get_count()
, который подсчитывает кандидатов на первой позиции в бюллетенях. Я также исправил редкую ошибку, из-за которой кандидат не мог появиться ни на одной из первых позиций и не мог быть проигравшим.