我的朋友邀请我去玩“暗恋者”游戏,我们需要抽签并扮演一个朋友的“圣诞老人”的角色。我们把我们所有人的名字写下来,然后随机抽取一个名字,如果有人抽到自己的名字,我们就要重新洗牌再次抽签。我们有七个人参加,我认为最终的“圣诞老人分配”是将(1:7)的排列映射到自身的某些限制条件下。我希望能够提出各种想法,看如何使用Mathematica或任何编程语言或甚至算法来:列出/打印出所有“有效”的“圣诞老人分配”,并且随着参与“暗恋者”游戏的朋友数量增加而具有可伸缩性。
f[s_List] := Pick[#, Inner[SameQ, #, s, Nor]] & @ Permutations@s
f @ Range @ 4
{{2, 1, 4, 3}, {2, 3, 4, 1}, {2, 4, 1, 3}, {3, 1, 4, 2}, {3, 4, 1, 2}, {3, 4, 2, 1}, {4, 1, 2, 3}, {4, 3, 1, 2}, {4, 3, 2, 1}}
此代码比Heike的函数要快得多。
f @ Range @ 9; //Timing
secretSanta[9]; //Timing
{0.483, 空值}
{1.482, 空值}
忽略代码的透明度,这可以使其运行速度再次提高几倍:
f2[n_Integer] := With[{s = Range@n},
# ~Extract~
SparseArray[Times@@BitXor[s, #] & /@ #]["NonzeroPositions"] & @ Permutations@s
]
f2[9]; //Timing
{0.162, Null}
Subfactorial
函数。 - telefunkenvf14将没有元素映射到其自身的排列称为derangement。随着n的增加,无序排列的比例趋近于常数1/e。因此,如果随机选择排列,则平均需要e次尝试才能获得一个无序排列。
维基百科文章包括计算小n的明确值的表达式。
在Mathematica中,你可以这样做:
secretSanta[n_] :=
DeleteCases[Permutations[Range[n]], a_ /; Count[a - Range[n], 0] > 0]
其中n
是游泳池中的人数。例如,secretSanta[4]
返回
{{2, 1, 4, 3}, {2, 3, 4, 1}, {2, 4, 1, 3}, {3, 1, 4, 2}, {3, 4, 1, 2},
{3, 4, 2, 1}, {4, 1, 2, 3}, {4, 3, 1, 2}, {4, 3, 2, 1}}
编辑
看起来Mathematica中的Combinatorica
包实际上有一个 Derangements
函数,所以你也可以这样做:
Needs["Combinatorica`"]
Derangements[Range[n]]
尽管在我的系统上,Derangements[Range[n]]
函数比上述函数慢了约2倍。
secretSanta[n_] := Cases[Permutations@Range@n, a_ /; FreeQ[a - Range[n], 0]]
。 - Mr.Wizard我在文档中发现了内置的Subfactorial
函数,并修改了其中一个示例以产生:
Remove[teleSecretSanta];
teleSecretSanta[dims_Integer] :=
With[{spec = Range[dims]},
With[{
perms = Permutations[spec],
casesToDelete = DiagonalMatrix[spec] /. {0 -> _}},
DeleteCases[perms, Alternatives @@ casesToDelete]
]
]
可以使用Subfactorial
来检查函数。
Length[teleSecretSanta[4]] == Subfactorial[4]
就像Mr.Wizard的回答一样,我怀疑通过SparseArray可以优化teleSecretSanta
。然而,我现在太醉了,无法尝试这种花招。(开玩笑...实际上我太懒和太蠢了。)