Проблема сортировки / изменения списка

1

Вначале я не был уверен, было ли это подходящим местом для этого вопроса, но, прочитав 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

Спасибо

Теги:
list
algorithm

4 ответа

2
Лучший ответ

В следующем коде используется подход грубой силы (он не оптимизирован), но достаточно прочен:

#!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+, как указано в комментарии.

Связи автоматически обрабатываются (все назначенные кандидаты объявляются победителями).

Возможные оптимизации включают группировку избирателей бюллетенями (если число избирателей превышает количество возможных избирательных бюллетеней), а также обновление подсчетов путем перераспределения счетов от неудачников (вместо повторного пересчета). Вышеупомянутая реализация обеспечивает ссылочную реализацию, результаты которой можно сравнить с оптимизированными версиями.:)

  • 0
    Ого, ты сделал все это: D Спасибо, я действительно ценю приложенные усилия. Как работает функция get_counnts?
  • 0
    @Pete: Спасибо, что одобрили этот ответ. Я добавил код Python <2.7 для get_count() , который подсчитывает кандидатов на первой позиции в бюллетенях. Я также исправил редкую ошибку, из-за которой кандидат не мог появиться ни на одной из первых позиций и не мог быть проигравшим.
Показать ещё 1 комментарий
1

На любом этапе процедуры избирательный бюллетень "принадлежит" кандидатом, который не был исключен, и который имеет наименьший предпочтительный номер, написанный против их имени.

Следовательно, вам не нужно делать особый случай с начальным счетом, и вам не нужно эмулировать ручную процедуру и физически перераспределять голоса исключенных кандидатов; просто сделайте общее (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. "посылка" равна "содержимому такого бункера".

  • 0
    Просто чтобы убедиться, что я понимаю, что вы говорите. Тот факт, что в каждом туре голосования в любом данном раунде голосования фактически голосуется только за одного кандидата, означает, что мне не нужно беспокоиться о каких-либо других перечисленных предпочтениях. В вашем ответе меня зациклило упоминание о подсчете голосов в N! посылок. То, что я не понимаю, это то, что вы имеете в виду под посылками, и где факториал вступает в игру. Спасибо пока кстати. Это оказывается очень полезным. Эта проблема значительно сложнее, чем я изначально предполагал. Хотя я думал, что это будет очень легко.
1

Он не отвечает на ваш вопрос напрямую, но я написал очень простую программу для вычисления результатов. Вы можете найти мою программу и unit test на github. Это может быть полезно.

  • 0
    О, спасибо, что поделился! Я очень ценю это. Я обязательно изучу это.
1

То, что вы намеревались сделать в своем цикле, -

shufvote = possible[:]
random.shuffle(shufvote)
ballots.append(shufvote)

Получаете ли вы, что вы ожидали от этого?

Вышеприведенный код сначала копирует список возможных голосов, затем перетасовывает копию. Фактически, random.shuffle() изменяет "на месте" список, который он задает как аргумент (он его не возвращает). Надеюсь, это поможет!

  • 0
    Я смущен вашим ответом ... D: Код, который я первоначально разместил, работает, насколько я могу судить ... Что эта модификация делает по-другому? После рассмотрения вашего и моего решения, единственное различие, которое я вижу, - мое, приводящее к вложенному списку. Я надеюсь, что ответ не очевиден. Потерпите меня! : D
  • 0
    @Pete: Мой ответ применим, если вы зациклились на range(2) (или более) вместо range(1) (что эквивалентно отсутствию цикла); в случае range(1) он действительно дает то же самое, что и ваш код.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню