列出排序/修改问题

时间:2011-04-23 06:33:48

标签: python algorithm list

最初,我不确定这是否适合这个问题,但在阅读完FAQ之后,我觉得这个话题是可以接受的......我还不确定这是否属于特定类型的问题(例如,背包问题),因此标题相当模糊。对不起,我很抱歉。

反正。作为python的练习和更好地理解一般编程概念的练习,我决定编写一个简单的Instant-Runoff Vote模拟。可以在此处找到即时径流投票的说明: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

由于

4 个答案:

答案 0 :(得分: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+中的collections模块,如注释中所示。

自动处理领带(所有并列候选人都被宣布为获胜者)。

可能的优化包括通过选票对选民进行分组(如果选民多于可能的选票),并通过从失败者重新分配计数来更新计数(而不是重新完成重新计票)。上述实现提供了参考实现,其结果可以与优化版本进行比较。 :)

答案 1 :(得分:1)

你打算在你的循环中做什么

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

你对此有何看法?

上面的代码首先复制可能的投票列表,然后将副本洗牌。事实上,random.shuffle()修改了“就地”它作为参数给出的列表(它不会返回它)。希望这有帮助!

答案 2 :(得分:1)

它没有直接回答你的问题,但我写了一个非常简单的程序来计算结果。您可以在github找到我的程序和单元测试。这可能有用。

答案 3 :(得分:1)

在程序的任何阶段,选票“拥有”未被淘汰的候选人以及根据其姓名写出最小偏好号码的候选人。

因此,您不需要特殊情况下的初始计数,您不需要模拟手动程序并在物理上重新分配已淘汰候选人的投票;只需在每个阶段进行蛮力总计(重新)计数 - 逻辑就更简单了。对于真实的模拟,其中选票的数量远远大于可能的不同选票的数量(阶乘(N = number_of_candidates)),您可能希望将投票数量计入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)

非常强烈的暗示:不要硬编码候选人数和选票数;使它们成为脚本的可变输入。

更新以回复评论

您写道:

  

事实上每次投票,无论如何   只有有效地进行一轮投票   投票给一个候选人   意味着我不必担心   任何其他列出的偏好。

我不确定你的意思是“不要担心”。您需要检查所有N个首选项,忽略已经被淘汰的候选项,并从剩余部分中选择最优选的候选项。当然你忽略了其他人;您需要为“所有者”候选人做的只有results[owner] += 1

如果您担心的是所有者确定规则:reductio ad adsurdum可以证明这一点。您无法将选票分配给已经被淘汰的候选人。如果候选人X比候选人Y更优先选择候选人X,则不能将选票分配给候选人Y.因此,唯一有效的选择是最优先未被淘汰的候选人。

关于阶乘(N):如果有5个候选人,并且有效选票必须具有1,2,3,4,5的排列,那么有5!个不同的选票--5第一个候选人的选择,第二个的选择,......,第五个的选择。 5x4x3x2x1 == 5! == 120。

关于地块:想象一下,有100,000张有效选票和5名候选人。这些柜台设置了120个箱子并将每张选票投入适当的箱子,按照它们的计数,或者他们称重每个箱子:-),或者他们可能每次选票都是OCR并使用使用collections.Counter的Python脚本。 “parcel”等于“这样一个箱子的内容”。