Подтвердить что ты не робот

Секретный алгоритм santa

Каждое Рождество мы рисуем имена для обмена подарками в моей семье. Обычно это связано с размытыми переливами, пока никто не вытащил их супруга. Итак, в этом году я закодировал собственное приложение для рисования имен, которое содержит кучу имен, кучу запрещенных пар и отправляет электронное письмо всем своим избранным giftee.

Сейчас алгоритм работает так (в псевдокоде):

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

Начиная с полного направленного графа и списка пар вершин. Для каждой пары вершин удалите ребро из первой вершины во вторую.

Цель состоит в том, чтобы получить граф, в котором каждая вершина имеет одно ребро, и одно ребро покидает.

4b9b3361

Ответ 1

Просто создайте граф с ребрами, соединяющими двух человек, если им разрешено делиться дарами, а затем использовать идеальный алгоритм совпадения. (Ищите "Пути, Деревья и Цветы" для (умного) алгоритма)

Ответ 2

Я бы не использовал запрещенные пары, так как это значительно увеличивает сложность проблемы. Просто введите все имя и адрес в список. Создайте копию списка и перетасуйте его, пока адреса в каждой позиции двух списков не совпадут. Это гарантирует, что никто не получит себя, или их супруга.

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

Есть еще больше решений этой проблемы на этой теме.

Ответ 3

Я просто делал это сам, в конечном итоге алгоритм, который я использовал, не точно моделирует имена рисунков из шляпы, но это довольно чертовски близко. В основном перетасовать список, а затем соединить каждого человека со следующим человеком в списке. Единственное отличие от рисования имен из шляпы заключается в том, что вы получаете один цикл вместо потенциального получения мини-подгрупп людей, которые только обмениваются подарками друг с другом. Если что-то может быть особенностью.

Реализация в Python:

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))

Ответ 4

Хммм. Я взял курс в теории графов, но проще просто просто переставить список, соединить каждую последовательную группу, а затем поменять любой элемент, который не разрешен другим. Поскольку в каждой данной паре нет запрещенного пользователя, обмен будет всегда успешным, если вы не разрешите свопы с выбранной группой. Ваш алгоритм слишком сложный.

Ответ 5

Создайте график, в котором каждое ребро является "податливостью". Вершины, которые представляют супругов, НЕ будут смежными. Выберите ребро в случайном порядке (это назначение подарка). Удалите все ребра, идущие от джойстика, и все ребра, идущие к приемнику, и повторите.

Ответ 6

В теории графов есть концепция, называемая гамильтоновой схемой которая описывает "цель" , которую вы описываете. Один совет для любого, кто находит это, - это указать пользователям, которые "семя" использовалось для генерации графика. Таким образом, если вам нужно сгенерировать график, вы можете. "Семя" также полезна, если вам нужно добавить или удалить человека. В этом случае просто выберите новое "семя" и создайте новый график, убедившись, что участники сообщают, что "семя" является текущим/последним.

Ответ 7

Я только что создал веб-приложение, которое будет делать именно это - http://www.secretsantaswap.com/

Мой алгоритм допускает подгруппы. Это не очень, но это работает.

Действует следующим образом:
1. назначить уникальный идентификатор всем участникам, помнить, в какую подгруппу они находятся
2. дублировать и перетасовывать этот список (цели)
3. создать массив из числа участников в каждой подгруппе
4. дублировать массив из [3] для целей
5. создать новый массив для проведения финальных матчей
6. перебирать участников, назначая первую цель, которая не соответствует ни одному из следующих критериев:
    A. участник == target
    B. участник. Субгруппа == target.Subgroup
    C. выбор целевой группы приведет к сбою подгруппы в будущем (например, подгруппа 1 должна иметь как минимум столько же целей, что и подгруппы 1, оставшихся участниками участников подгруппы 1)
    D. участник (n + 1) == target (n +1)
Если мы назначаем цель, мы также уменьшаем массивы из 3 и 4

Итак, не очень (вообще), но он работает. Надеюсь, это поможет,

Дэн Карлсон

Ответ 8

Здесь простая реализация в java для секретной проблемы 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));
    }
}

Ответ 9

Решение Python здесь.

Учитывая последовательность (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 [мужчина, компания1]',  'person2 [женщина, компания2]',  'person3 [мужчина, компания1]',  'жена2 [женщина, брак2, компания2]',   "муж1 [мужчина, брак1, компания2]",   "муж2 [мужчина, брак2, компания3]",  'жена1 [женщина, брак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 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))