2008-11-07 5 views
24

모든 크리스마스 우리 가족의 선물 교환에 대한 이름을 그립니다. 아무도 자신의 배우자를 끌어낼 때까지 이것은 일반적으로 여러 번 다시 그려집니다. 그래서 올해 저는 수많은 이름을 가진 제 이름을 그리는 앱을 만들었고, 허용되지 않는 쌍을 이루었습니다. 그리고 선택한 사람들과 함께 모든 사람에게 이메일을 보냅니다.비밀 산타 알고리즘

는 지금, 알고리즘 (의사 코드에서) 다음과 같이 작동

function DrawNames(list allPeople, map disallowedPairs) returns map 
    // Make a list of potential candidates 
    foreach person in allPeople 
     person.potentialGiftees = People 
     person.potentialGiftees.Remove(person) 
     foreach pair in disallowedPairs 
      if pair.first = person 
       person.Remove(pair.second) 

    // Loop through everyone and draw names 
    while allPeople.count > 0 
     currentPerson = allPeople.findPersonWithLeastPotentialGiftees 
     giftee = pickRandomPersonFrom(currentPerson.potentialGiftees) 
     matches[currentPerson] = giftee 
     allPeople.Remove(currentPerson) 
     foreach person in allPeople 
      person.RemoveIfExists(giftee) 

    return matches 

은 그래프 이론에 대한 자세한 내용을 알고있는 사람이 더 나은 여기서 일 것이다 알고리즘의 어떤 종류를 알고 있나요? 내 목적을 위해,이 작품은,하지만 난 호기심이야.

EDIT : 이메일이 얼마전에 나왔기 때문에, 나는 이것을 배우기를 희망하고 있습니다.이 것을 그래프 이론 질문으로 바꿔 볼 것입니다. 나는 예외가 모두 쌍인 특수한 경우에 관심이 없다. (배우자가 서로 마주 치지 않는 것처럼). 나는 어떤 해결책을 찾는 것이 어려운 부분이되는 충분한 배제가있는 경우에 더 관심이있다. 위의 알고리즘은 모든 경우에 성공할 수 있을지 모르는 단순한 욕심 많은 알고리즘 일뿐입니다.

완전 지향 그래프와 버텍스 쌍 목록으로 시작합니다. 각 정점 쌍에 대해 첫 번째 정점에서 두 번째 정점으로 가장자리를 제거합니다.

목표는 각 꼭지점에 하나의 가장자리가오고 하나의 가장자리가 떠나는 그래프를 얻는 것입니다.

답변

9

두 사람이 선물을 공유하고 완벽한 일치 알고리즘을 사용할 수있는 경우 가장자리를 연결하는 그래프를 만드십시오. (영리한) 알고리즘을위한 "경로, 나무 및 꽃"을 찾으십시오.

+0

그게 내가 찾고 있던거야! 감사! – Eclipse

+0

그렇지 않습니다. 그것은 지향적 인 그래프이며 반드시 완벽하게 일치하는 것을 원하지는 않습니다. 당신은 모든 정점이 indegree = outdegree = 1 * a * cycle cover *를 갖도록 서브 그래프를 원할뿐입니다. [완벽하게 일치하는 문제로 줄이는 방법이 있지만 직접 해결할 수있는 방법이 있습니다.] – ShreevatsaR

+0

@ShreevatsaR : 그래프를 그리지 않아도됩니다. 동전을 뒤집어서 각 사이클의 방향을 선택하십시오. (이것은 모든 블랙리스트에있는 쌍이 대칭이라고 가정합니다.) –

6

문제의 복잡성이 크게 증가하므로 허용되지 않는 쌍을 사용하지 않겠습니다. 모든 사람의 이름과 주소를 목록에 입력하기 만하면됩니다. 목록의 복사본을 만들고 두 목록의 각 위치에있는 주소가 일치하지 않을 때까지 목록을 유지하십시오. 이렇게하면 아무도 자신이나 배우자를 얻을 수 없습니다.

보너스로,이 비밀 투표 양식을 사용하려면 첫 번째 목록에서 봉투를 인쇄하고 두 번째 목록에서 이름을 인쇄하십시오. 봉투를 채우면서 살짝 보지 마십시오. (또는 모든 사람에게 이메일을 보내도록 자동화 할 수 있습니다.)

this thread에 대한 더 많은 해결책이 있습니다.

+0

그렇게 쉬울 것입니다! – Eclipse

+0

프로그램에서 모든 사람에게 전자 메일을 보냅니다. 따라서 비밀 문제는 문제가되지 않습니다. – Eclipse

+0

그건 제가 언급 한 옵션 중 하나였습니다. –

5

흠. 나는 그래프 이론에서 과정을 밟았지만 간단히 말해서 목록을 무작위로 순열하고 각 연속적인 그룹을 쌍으로 만든 다음 허용되지 않는 요소를 다른 그룹으로 바꾼다. 특정 쌍에 허용되지 않은 사람이 없기 때문에 선택한 그룹과의 스왑을 허용하지 않으면 스왑이 항상 성공합니다. 알고리즘이 너무 복잡합니다.

+0

그 사람과 그 배우자는 둘 다 허용되지 않으므로 스왑이 작동하지 않을 수 있습니다. –

+0

선택한 그룹에 해당 사람과 배우자가 모두 있기 때문에 (사실 그렇지 않은 경우 스왑 필요 없음) 사실이 아닙니다. – Brian

6

나는 알고리즘을 사용하여 결국 모자에서 그림 이름을 모델링하지 않지만 결국이 작업을 수행하고있었습니다. 꽤 가까이에. 기본적으로 목록을 섞은 다음 각 사람과 목록의 다음 사람을 짝 지어보십시오. 모자에서 이름을 그리는 것의 유일한 차이점은 잠재적으로 서로 선물을 교환하는 사람들의 미니 하위 그룹을 얻는 대신 한 사이클을 얻는 것입니다. 어떤 것이 있다면 그것은 기능 일 것입니다. 파이썬에서

구현 : - http://www.secretsantaswap.com/

내 알고리즘은 하위 그룹을 허용

import random 
from collections import deque 
def pairup(people): 
    """ Given a list of people, assign each one a secret santa partner 
    from the list and return the pairings as a dict. Implemented to always 
    create a perfect cycle""" 
    random.shuffle(people) 
    partners = deque(people) 
    partners.rotate() 
    return dict(zip(people,partners)) 
1

난 그냥 바로이 작업을 수행하는 웹 응용 프로그램을 만들었습니다. 그것은 예쁘지 않지만 작동합니다.다음과 같이

가 작동 : 그들은
2. 중복에있어 그 목록 (대상)을 셔플 서브 그룹의 모든 참가자들에게 고유 식별자가 기억
1. 할당
3. 숫자의 배열을 만들 각 서브 그룹의 참여자
4. 중복 배열 [3] 타겟
5. 최종 일치를 다음과 일치하지 않는 제 목표 할당 참가자 통해
6. 반복 처리를 보유 할 수있는 새로운 배열을 만들 기준 :
          A. 참가자 == 대상
          B. participant.Subgroup == target.Subgroup
          C. 미래에 실패 할 하위 그룹의 원인이됩니다 대상을 선택 (예 하위 그룹 일해야 항상이 적어도 참가자로, 나머지 1 개 목표는 1 명, 나머지 참가자)
          D. 참가자 (N + 1) == 대상 (N 서브 그룹이 아닌 하위 그룹 등 많은 +1)
우리가 할당하는 경우 우리는 또한 3과 4에서 배열을 감소시킵니다.

그래서, 전혀 효과가 없습니다. 댄 칼슨

2

각 모서리가 배우자를 대표하는 "giftability"정점은 인접 않을 것입니다 그래프 만들기, 희망이 도움이. 무작위로 가장자리를 선택하십시오 (선물 할당 임). 지퍼에서 오는 모든 가장자리와 수신기로가는 모든 가장자리를 삭제하고 반복하십시오.

+0

이것이 불완전한 결과를 생성하지 않습니까? 어떤 gifter가 선호하는 giftee를 가지고 있다면? –

2

설명하는 "목표"를 설명하는 Hamiltonian Circuit이라고하는 그래프 이론의 개념이 있습니다. 이를 발견 한 사람을위한 팁 중 하나는 그래프를 생성하는 데 "시드"가 사용 된 사용자를 알리는 것입니다. 그래프를 다시 생성해야한다면이 방법을 사용할 수 있습니다. "씨앗"은 사람을 추가하거나 제거해야하는 경우에도 유용합니다. 이 경우 간단하게 새로운 "시드"를 선택하고 새로운 그래프를 생성하여 참가자에게 "시드"가 현재/최신인지를 알려줍니다.

1

여기 비밀 santa 문제에 대한 자바의 간단한 구현.

public static void main(String[] args) { 
    ArrayList<String> donor = new ArrayList<String>(); 
    donor.add("Micha"); 
    donor.add("Christoph"); 
    donor.add("Benj"); 
    donor.add("Andi"); 
    donor.add("Test"); 
    ArrayList<String> receiver = (ArrayList<String>) donor.clone(); 

    Collections.shuffle(donor); 
    for (int i = 0; i < donor.size(); i++) { 
     Collections.shuffle(receiver); 
     int target = 0; 
     if(receiver.get(target).equals(donor.get(i))){    
      target++; 
     }   
     System.out.println(donor.get(i) + " => " + receiver.get(target)); 
     receiver.remove(receiver.get(target)); 
    } 
} 
0

파이썬 솔루션은 여기에 있습니다.

태그가 자체 문자열 (아마도 비어있는) 문자열 인 경우 (person, tags)의 시퀀스가 ​​주어지면 내 알고리즘은 각 사람이 체인의 다음 사람에게 선물을주는 일련의 사람을 제안합니다 (마지막 사람은 분명히 첫번째).

태그는 사람이 그룹화 될 수 있고 다음 사람이 가장 최근에 선택한 사람과 가장 관계가없는 그룹에서 선택 될 때마다 존재합니다. 초기 사람은 빈 태그 집합에 의해 선택되므로 가장 긴 그룹에서 선택됩니다.

example_sequence= [ 
    ("person1", ("male", "company1")), 
    ("person2", ("female", "company2")), 
    ("person3", ("male", "company1")), 
    ("husband1", ("male", "company2", "marriage1")), 
    ("wife1", ("female", "company1", "marriage1")), 
    ("husband2", ("male", "company3", "marriage2")), 
    ("wife2", ("female", "company2", "marriage2")), 
] 

제안은 : 입력 시퀀스의 주어진 그래서

,

[ 'PERSON1 [남성 company1]'[여성 company2] PERSON2 ' , 'person3 [남성, 회사 1] ', 'wife2 [여성, 결혼 2, 회사 2] ', '남편 1 [남성, 결혼 1, 회사 2] ', '남편 2 [남성, 결혼 2, 회사 3] ', 'wife1 [여성, 결혼 1 , 회사 1] ']

물론 모든 사람에게 태그가없는 경우 (예 : 빈 튜플 인 경우) 선택할 수있는 그룹은 하나뿐입니다.

항상 최적의 해결책은 아니지만 (10 명의 여성과 2 명의 남성의 입력 순서를 생각해보십시오. 장르는 주어진 유일한 태그입니다.) 가능한 한 최선을 다합니다.

Py2/3 호환.

import random, collections 

class Statistics(object): 
    def __init__(self): 
     self.tags = collections.defaultdict(int) 

    def account(self, tags): 
     for tag in tags: 
      self.tags[tag] += 1 

    def tags_value(self, tags): 
     return sum(1./self.tags[tag] for tag in tags) 

    def most_disjoined(self, tags, groups): 
     return max(
      groups.items(), 
      key=lambda kv: (
       -self.tags_value(kv[0] & tags), 
       len(kv[1]), 
       self.tags_value(tags - kv[0]) - self.tags_value(kv[0] - tags), 
      ) 
     ) 

def secret_santa(people_and_their_tags): 
    """Secret santa algorithm. 

    The lottery function expects a sequence of: 
    (name, tags) 

    For example: 

    [ 
     ("person1", ("male", "company1")), 
     ("person2", ("female", "company2")), 
     ("person3", ("male", "company1")), 
     ("husband1", ("male", "company2", "marriage1")), 
     ("wife1", ("female", "company1", "marriage1")), 
     ("husband2", ("male", "company3", "marriage2")), 
     ("wife2", ("female", "company2", "marriage2")), 
    ] 

    husband1 is married to wife1 as seen by the common marriage1 tag 
    person1, person3 and wife1 work at the same company. 
    … 

    The algorithm will try to match people with the least common characteristics 
    between them, to maximize entrop— ehm, mingling! 

    Have fun.""" 

    # let's split the persons into groups 

    groups = collections.defaultdict(list) 
    stats = Statistics() 

    for person, tags in people_and_their_tags: 
     tags = frozenset(tag.lower() for tag in tags) 
     stats.account(tags) 
     person= "%s [%s]" % (person, ",".join(tags)) 
     groups[tags].append(person) 

    # shuffle all lists 
    for group in groups.values(): 
     random.shuffle(group) 

    output_chain = [] 
    prev_tags = frozenset() 
    while 1: 
     next_tags, next_group = stats.most_disjoined(prev_tags, groups) 
     output_chain.append(next_group.pop()) 
     if not next_group: # it just got empty 
      del groups[next_tags] 
      if not groups: break 
     prev_tags = next_tags 

    return output_chain 

if __name__ == "__main__": 
    example_sequence = [ 
     ("person1", ("male", "company1")), 
     ("person2", ("female", "company2")), 
     ("person3", ("male", "company1")), 
     ("husband1", ("male", "company2", "marriage1")), 
     ("wife1", ("female", "company1", "marriage1")), 
     ("husband2", ("male", "company3", "marriage2")), 
     ("wife2", ("female", "company2", "marriage2")), 
    ] 
    print("suggested chain (each person gives present to next person)") 
    import pprint 
    pprint.pprint(secret_santa(example_sequence)) 
관련 문제