2011-04-23 7 views
1

처음에는 이것이 이것이이 질문에 적절한 장소인지 여부를 확신하지 못했지만 FAQ를 읽은 후에는 주제가 받아 들여질 수 있다고 확신했습니다. 이것은 특정 유형의 문제 (예 : 배낭 문제)로 분류 될 수 있으므로 제목이 다소 모호합니다. 나는 그 일에 대해 유감이다.목록 정렬/수정 문제

어쨌든. 파이썬 연습과 일반적인 프로그래밍 개념을 더 잘 이해하기위한 연습으로 간단한 Instant-Runoff Vote 시뮬레이션을 작성하기로 결정했습니다. 즉각적인 유출 투표에 대한 설명은 여기에서 확인할 수 있습니다.

기본적으로 유권자는 각 후보자에게 첫 번째 선택 사항 인 두 번째 숫자와 두 번째 선택 사항 인 숫자를 할당하여 순위를 매 깁니다. 투표 종료시 단 하나의 후보자가 과반수를 가지면 가장 작은 후보자가 제거되고 그들에게 주어진 투표는 유권자 후보자 후보자에게갑니다.

5 명의 후보자와 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 

는 그래서 그래, 꽤 많이 먹으 렴 ... 지금까지 프로그램입니다. 내 문제의 일부는 중첩 목록에있는 데이터를 표현하는 방식에 있다고 생각합니다. 누구라도 비판이나 제안이 있다면 공유하십시오!

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

당신은 당신이 예상 어떻게받을 수 있나요 당신이 당신의 루프에서 할 의도 D

감사

답변

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 모듈을 사용할 수도 있습니다.

타이가 자동으로 처리됩니다 (모든 묶여진 후보자가 승자로 선언됩니다).

가능한 최적화는 가능한 투표자보다 더 많은 투표자가있는 경우 투표 용지별로 투표자를 그룹화하고 전체 재검 표를 다시하는 대신 패자의 카운트를 재배포하여 카운트를 업데이트하는 것을 포함합니다. 위의 구현은 결과를 최적화 된 버전과 비교할 수있는 참조 구현을 제공합니다. :)

+0

오, 당신은 모든 것을 했어요 : D 덕분에 나는 정말로 노력을 기울였습니다. get_counnts 함수는 어떻게 작동합니까? – danem

+0

@ Pete :이 답변을 승인 해 주셔서 감사합니다. 투표 용지의 첫 번째 위치에있는 후보자 수를 계산하는'get_count()'에 Python <2.7 코드를 추가했습니다. 또한 후보자가 어떤 첫 번째 위치에도 출전 할 수없고 패자로 놓칠 수없는 드문 버그를 수정했습니다. – EOL

+0

분명히하기 위해, 이것은 유출 투표가 아닙니다. 투표 용지 ('바위', '종이', '가위', '가위', '종이', '암석')를 고려하십시오. 이 프로그램은 바위와 가위 사이의 매듭을 선포 할 것입니다. IRV에 따르면 정확한 우승자는 '종이'입니다. – dmd

1

되어가?

위의 코드는 먼저 가능한 투표 목록을 복사 한 다음 복사본을 임의로 섞습니다. 실제로 random.shuffle()은 인수로 주어진 목록을 "제자리에"수정합니다 (반환하지 않습니다). 희망이 도움이!

+0

나는 당신의 대답에 혼란 스럽다. D : 처음에 내가 게시 한 코드는 내가 말할 수있는 한 작동한다 ...이 수정은 어떻게 다르게 하는가? 솔루션과 광산 모두를 살펴본 결과, 내 생각에 볼 수있는 유일한 차이점은 중첩 된 목록의 결과입니다. 나는 대답이 자명하지 않기를 바란다. 나와 함께 곰! : D – danem

+0

@ Pete :'range (1)'대신에'range (2)'(또는 그 이상)을 반복하면 나의 답이 적용됩니다. 'range (1)'의 경우 실제로 코드와 동일한 것을 제공합니다. – EOL

1

귀하의 질문에 직접 대답하지 않지만 결과를 계산할 수있는 매우 간단한 프로그램을 작성했습니다. 내 프로그램과 단위 테스트는 github에 있습니다.유용 할 수도 있습니다.

+0

공유해 주셔서 감사합니다. 정말 감사. 나는 확실히 그것을 공부할 것이다. – danem

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보다 투표자가 더 선호하는 후보 X가있는 경우 후보 Y에게 투표 용지를 할당 할 수 없습니다. 따라서 유일하게 유효한 선택은 가장 선호되지 않는 비 제거 후보입니다.

계승 (N)에 관하여 : 5 명의 후보자가 있고 유효한 투표 용지가 숫자 1,2,3,4,5의 순열을 가져야한다면 5! 서로 다른 가능한 투표 용지가 있습니다. 첫 번째 후보는 4, 두 번째 후보는 ..., 다섯 번째는 1입니다. 5x4x3x2x1 == 5! == 120.

소포에 관하여 : 유효한 투표 용지 100,000 장과 후보자 5 명이 있다고 상상해보십시오. 카운터는 120 개의 휴지통을 만들고 각 휴지통을 해당 휴지통에 던지거나 가며 계산하거나 또는 각 휴지통의 무게를 재어 보았을 수도 있습니다 :-) 또는 OCR 각 투표 용지를 사용하고 collections.Counter을 사용하는 Python 스크립트를 사용할 수 있습니다. "소포"는 "그러한 빈의 내용"과 같습니다.

+0

내가하는 말을 이해하는지 확인하기 위해서. 각 투표마다 주어진 투표 라운드에서 단일 후보자에 대한 투표 만 효과적으로 수행한다는 사실은 다른 나열된 선호 사항에 대해 걱정할 필요가 없다는 것을 의미합니다. 귀하의 답변에 내가 매달려있는 것은 N 표를 집계하는 것에 대한 언급입니다! 소포. 제가 이해하지 못하는 것은 당신이 소포이고, 계승이 시작되는 곳입니다. 지금까지 고마워. 그것은 매우 도움이되는 것으로 증명됩니다. 이 문제는 처음 예상했던 것보다 훨씬 복잡합니다. 나는 그것이 아주 쉬울 것이라고 생각했지만. – danem