现在,算法的工作原理如下(伪代码):
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
有没有更多了解图论的人知道某种算法会更好吗?就我的目的而言,这很有效,但我很好奇。
编辑:由于电子邮件不久前发布了,我只是希望学习一些东西,我将其重新描述为图论问题。我对排除所有成对的特殊情况不太感兴趣(如配偶没有相互获得)。我对那些有足够排除条件的案例更感兴趣,因为找到任何解决方案都会成为困难的部分。我上面的算法只是一个简单的贪婪算法,我不确定在所有情况下都会成功。从完整的有向图和顶点对列表开始。对于每个顶点对,删除第一个顶点到第二个顶点的边缘。
目标是得到一个图形,其中每个顶点有一条边进入,一条边离开。
答案 0 :(得分:9)
如果允许他们分享礼物然后使用完美的匹配算法,只需创建一个边缘连接两个人的图表。 (为(聪明的)算法寻找“路径,树和花”)
答案 1 :(得分:6)
我不会使用不允许的配对,因为这会大大增加问题的复杂性。只需将每个人的姓名和地址输入一个列表即可。创建列表的副本并保持混洗,直到两个列表的每个位置中的地址不匹配。这将确保没有人得到自己或他们的配偶。
作为奖励,如果你想做这个秘密选票风格,请打印第一个列表中的信封和第二个列表中的名称。塞满信封时不要偷看。 (或者你可以自动通过电子邮件发送给所有人。)
this thread还有更多解决方案可以解决这个问题。
答案 2 :(得分:6)
我自己就是这样做的,最后我使用的算法并没有用帽子精确地模拟绘图名称,但它非常接近。基本上将列表洗牌,然后将每个人与列表中的下一个人配对。与帽子名称的唯一区别在于,您获得一个周期,而不是可能获得仅与彼此交换礼物的迷你子组。如果有什么可能是一个功能。
在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))
答案 3 :(得分:5)
嗯。我参加了图论的课程,但更简单的是随机地置换你的列表,对每个连续的组配对,然后交换任何不允许的元素与另一个。由于任何给定对中没有不允许的人,如果您不允许与所选组进行交换,则交换将始终成功。你的算法太复杂了。
答案 4 :(得分:2)
创建一个图形,其中每条边都是“giftability”代表配偶的顶点不会相邻。随机选择边缘(即礼品分配)。删除来自礼品夹的所有边缘以及所有到达接收器的边缘并重复。
答案 5 :(得分:2)
图论中有一个名为Hamiltonian Circuit的概念描述了你描述的“目标”。找到这个的任何人的一个提示是告诉用户使用哪个“种子”来生成图表。这样,如果你必须重新生成图表。如果您必须添加或删除某人,“种子”也很有用。在这种情况下,只需选择一个新的“种子”并生成一个新的图表,确保告诉参与者哪个“种子”是当前/最新的种子。
答案 6 :(得分:1)
我刚创建了一个可以完成此操作的网络应用 - http://www.secretsantaswap.com/
我的算法允许子组。它不漂亮,但它有效。
操作如下:
1。为所有参与者分配唯一标识符,记住他们所在的子组
2。复制并随机播放该列表(目标)
3。创建每个子组中参与者数量的数组
4。来自[3]的重复数组用于目标
5。创建一个新数组来保存最终匹配
6。迭代参与者分配不符合以下任何条件的第一个目标:
A.参与者==目标
B.参与者。子组== target.Subgroup
C.选择目标将导致子组在未来失败(例如,子组1必须始终至少有剩余的非子组1目标作为参与者子组1参与者剩余)
D.参与者(n + 1)==目标(n + 1)
如果我们分配目标,我们也将数组从3和4递减
所以,不是很漂亮(根本没有),但它确实有效。希望它有所帮助,
丹·卡尔森答案 7 :(得分:1)
这是java中用于秘密圣诞老人问题的简单实现。
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));
}
}
答案 8 :(得分:0)
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 [female,company2]', 'person3 [男,公司1]', 'wife2 [female,marriage2,company2]', 'husband1 [男,婚姻1,公司2]', 'husband2 [男,婚姻2,公司3]', 'wife1 [female,marriage1,company1]']
当然,如果所有人都没有标签(例如空元组),那么只有一个组可供选择。
并不总是有一个最佳解决方案(想想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))