【问题标题】:List sorting/modify problem列表排序/修改问题
【发布时间】:2011-04-23 06:33:48
【问题描述】:

最初,我不确定这是否是这个问题的合适位置,但在阅读了常见问题解答后,我觉得这个主题是可以接受的......此外,我不确定这是否会被归类为特定类型的问题(例如背包问题),因此,标题相当模糊。对此我很抱歉。

无论如何。作为 Python 的练习和更好地理解一般编程概念的练习,我决定编写一个简单的 Instant-Runoff Vote 模拟。即时决选投票的描述可以在这里找到:http://en.wikipedia.org/wiki/Instant-runoff_voting

基本上,选民通过给每个候选人分配一个数字来对他们进行排名,一个是他们的第一选择,两个是他们的第二选择,等等......如果在投票结束时没有一个候选人拥有多数票,那么份额最小的候选人就会被淘汰投票给他们的选票将投给选民的第二选择候选人。

假设有 5 位候选人和 20 位选民,则需要投出 100 张选票 (5x20),并且每张选票都需要能够指向投出的选民,以及投给谁的票。

为了表示这一点,我选择使用嵌套列表,以便每个子列表代表一个选民(或选票),并且该子列表的每个索引代表一个候选人。

可视化:

[[1,3,2,5,4]...] 所以 ballot[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

谢谢

【问题讨论】:

    标签: python algorithm list


    【解决方案1】:

    以下代码使用蛮力方法(未优化),但相当健壮:

    #!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+ 中的集合模块,如注释中所示。

    自动处理平局(所有平局的候选人都被宣布为获胜者)。

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

    【讨论】:

    • 哦,哇,你完成了整个事情:D 谢谢我真的很感激付出的努力。get_counts 函数是如何工作的?
    • @Pete:感谢您批准此答案。我为 get_count() 添加了 Python
    • 明确地说,这不是即时决选投票。考虑选票 [['rock', 'paper', 'scissors'],['scissors', 'paper', 'rock']]。该程序将宣布石头和剪刀之间的平局。根据 IRV,正确的获胜者是“纸”。
    【解决方案2】:

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

    【讨论】:

    • 哦,谢谢分享!对此,我真的非常感激。我一定会学习的。
    【解决方案3】:

    你打算在循环中做的是

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

    你得到了你的期望吗?

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

    【讨论】:

    • 我对您的回答感到困惑... D:据我所知,我最初发布的代码有效...此修改有何不同之处?在检查了您的解决方案和我的解决方案之后,我看到的唯一区别是我的导致嵌套列表。我希望答案不是不言自明。忍受我! :D
    • @Pete:我的答案适用于循环range(2)(或更多)而不是range(1)(相当于没有循环);对于range(1),它确实提供了与您的代码相同的内容。
    【解决方案4】:

    在程序的任何阶段,选票均由未被淘汰且在其姓名上写有最小偏好编号的候选人“拥有”。

    因此,您无需对初始计票进行特殊处理,也无需模拟手动程序并在物理上重新分配被淘汰候选人的选票;只需在每个阶段进行蛮力总(重新)计数 - 逻辑要简单得多。对于一个真实的模拟,其中选票的数量远远大于可能的不同选票的数量(阶乘(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 absurdum 可以证明这是正确的。您不能将选票分配给已经被淘汰的候选人。如果有候选人 X 比候选人 Y 更受本次选票的青睐,则您不能将选票分配给候选人 Y。因此,唯一有效的选择是最受青睐的未淘汰候选人。

    关于阶乘(N):如果有 5 位候选人,并且一张有效选票必须有 1、2、3、4、5 的排列,那么有5! 不同的可能选票 -- 5 种选择第 1 个候选人,第 2 个候选人 4 个,……,第 5 个候选人 1 个。 5x4x3x2x1 == 5! == 120。

    关于包裹:假设有 100,000 张有效选票和 5 名候选人。计票员设置了 120 个垃圾箱,并将每张选票扔到适当的垃圾箱中,边走边计算,或者他们可能会对每个垃圾箱进行称重 :-),或者他们可能对每张选票进行 OCR 并使用使用collections.Counter 的 Python 脚本。 "parcel" 等于 "这样一个 bin 的内容"。

    【讨论】:

    • 只是为了确保我明白你在说什么。事实上,在任何给定的一轮投票中,每张选票只会有效地为单个候选人投票,这意味着我不需要担心任何其他列出的偏好。在您的回复中,我最感兴趣的是提到将投票计入 N!包裹。我不明白的是你的意思是包裹,以及阶乘在哪里发挥作用。顺便说一句,谢谢。事实证明这很有帮助。这个问题比我最初预期的要复杂得多。虽然我认为这很容易。
    猜你喜欢
    • 2019-04-07
    • 2015-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-20
    • 1970-01-01
    • 1970-01-01
    • 2017-03-07
    相关资源
    最近更新 更多