http://catswhisker.xyz/ Atom Feed for 'programming' Features 2021-02-04T23:01:43Z A. Cynic http://catswhisker.xyz/about/ tag:catswhisker.xyz,2020-12-07:/log/2020/12/7/deranged_sinterklaas/ Deranged Sinterklaas: The Math and Algorithms of Secret Santa 2020-12-07T07:00:00Z 2021-02-04T23:01:43Z <div class="imageblock"> <div class="content"> <img src="/log/2020/12/7/deranged_sinterklaas/XmasStory.png" alt="XmasStory"> </div> </div> <div class="sect1"> <h2 id="_secret_santa">Secret Santa</h2> <div class="sectionbody"> <div class="paragraph"> <p><a href="https://en.wikipedia.org/wiki/Secret_Santa">Secret Santa</a> is a traditional Christmas gift exchanging scheme in which each member of a group is randomly and anonymously assigned another member to give a Christmas gift to (usually by drawing names from a container). It is not valid for a person to be assigned to themself (if someone were to draw their own name, for example, all the names should be returned to the jar and the drawing process restarted).</p> </div> <div class="paragraph"> <p>Given a group of a certain size, how many different ways are there to make valid assignments? What is the probability that at least one person will draw their own name? What is the probability that two people will draw each other&#8217;s names? What is a good way to have a computer make the assignments while guaranteeing they are generated with equal probability among all possible assignments?</p> </div> <div class="paragraph"> <p>It turns out that these questions about secret santa present good motivation for exploring some of the fundamental concepts in combinatorics (the math of counting). In the sections below we will take a look at a bit of that math and algorithms that allow us to answer the questions we posed above. The final section presents a simple command-line <a href="https://github.com/cristoper/sinterbot">program</a> that allows generating and anonymously sending secret santa assignments via email so that we no longer need to go through the tedious ordeal of drawing names from a hat.</p> </div> <div id="toc" class="toc"> <div id="toctitle" class="title">Table of Contents</div> <ul class="sectlevel1"> <li><a href="#_secret_santa">Secret Santa</a></li> <li><a href="#_math">Math</a> <ul class="sectlevel2"> <li><a href="#_permutations">Permutations</a></li> <li><a href="#_derangements">Derangements</a></li> </ul> </li> <li><a href="#_algorithms">Algorithms</a> <ul class="sectlevel2"> <li><a href="#_utilities">Utilities</a></li> <li><a href="#_how_not_to_generate_derangements">How not to generate derangements</a></li> <li><a href="#_how_to_generate_derangements">How to generate derangements</a></li> </ul> </li> <li><a href="#software">Sinterbot2020</a> <ul class="sectlevel2"> <li><a href="#_installation">Installation</a></li> <li><a href="#_usage">Usage</a></li> </ul> </li> </ul> </div> </div> </div> <div class="sect1"> <h2 id="_math">Math</h2> <div class="sectionbody"> <div class="sect2"> <h3 id="_permutations">Permutations</h3> <div class="paragraph"> <p>As an example let&#8217;s take a group of five friends who we will represent by the first initial of their names as a set: $$\{\mathrm{\,S, C, A, L, M\,}\}$$. The elements of this set of people, like the elements of any set, can be arranged in different orders. For example, the order of the elements as we just happened to write them $$(\mathrm{S\; C\; A\; L\; M})$$ is one arrangement, and we can also shuffle them around to get a different arrangement like $$(\mathrm{C\; A\; M\; L\; S})$$.</p> </div> <div class="paragraph"> <p>Each ordered arrangement is called a permutation of the set. How many permutations can be made from a set with $$n$$ elements? It is straight-forward to count. We can choose any element of the set to be in the first position of the permutation, so there are $$n$$ choices, which leaves $$n-1$$ choices for the second position, $$n-2$$ choices for the third position, and so on until for the $$n\text{th}$$ (and therefore last) position of the permutation there is only one element of the set remaining to choose from.</p> </div> <div class="paragraph"> <p>Multiplying the number of choices for each position of the permutation gives the total number of possible permutations: $$n(n-1)(n-2)\dots(1).$$ In other words, the product of all positive integers less than or equal to $$n$$. That product is known as the <a href="https://en.wikipedia.org/wiki/Factorial">factorial</a> of $$n$$ and is written $$n!$$:</p> </div> <div class="stemblock"> <div class="content"> \begin{align*} n! &amp;= n(n-1)(n-2)\cdots (n-(n-1))\\ &amp;= 1 \cdot 2 \cdots n\\ \end{align*} </div> </div> <div class="paragraph"> <p>So there are $$5! = 5 \cdot 4 \cdot 3 \cdot 2 \cdot 1 = 120$$ ways to permute a set of five friends. Writing any two of those permutations in <a href="http://groupprops.subwiki.org/wiki/Two-line_notation_for_permutations">two-line notation</a> with one above the other allows us to read off a secret santa assignment for the group:</p> </div> <div class="stemblock"> <div class="content"> $\begin{equation} \label{eq:perm} \begin{pmatrix} \mathrm{S} &amp; \mathrm{C} &amp; \mathrm{A} &amp; \mathrm{L} &amp; \mathrm{M} \\ \mathrm{C} &amp; \mathrm{A} &amp; \mathrm{M} &amp; \mathrm{L} &amp; \mathrm{S} \\ \end{pmatrix} \end{equation}$ </div> </div> <div class="paragraph"> <p>If we read the top line of as the list of gift givers and the bottom line as the gift recipients, then each santa is assigned to give a gift to the person in the bottom line directly beneath them. So $$\mathrm{S}$$ gives a gift to $$\mathrm{C}$$ who gives a gift to $$\mathrm{A}$$ and so forth.</p> </div> <div id="permgraph" class="imageblock"> <div class="content"> <a class="image" href="dotperm.svg"><img src="/log/2020/12/7/deranged_sinterklaas/dotperm.png" alt="TODO Figure &lt;&lt;permgraph&gt;&gt; illustrates the cycles."></a> </div> <div class="title">Figure 1. Graphical representation of the cycles of the permutation $$\eqref{eq:perm}$$. Arrows point from santas to gift recipients.</div> </div> <div class="paragraph"> <p>A permutation of santas (or anything else) can be represented as a directed graph, as in <a href="#permgraph">Figure 1</a>, or more compactly by listing its cycles: $$(\mathrm{S\; C\; A\; M})(\mathrm{L})$$. To see that the cycle notation is equivalent to the graph, read each cycle from left to right and insert the implied arrow from the last element back to the first: $$(\mathrm{S \to C \to A \to M \to S})(\mathrm{L \to L})$$. Note that any 1-cycles can be implied and are usually left out when writing a permutation in cycle notation, so an equivalent way to write our example permutation is simply as $$(\mathrm{S\; C\; A\; M})$$.</p> </div> <div class="paragraph"> <p>But note also that a permutation containing any 1-cycles defines an invalid secret santa assignment! The example permutation above has $$\mathrm{L}$$ giving a gift to herself, which is against the rules.</p> </div> </div> <div class="sect2"> <h3 id="_derangements">Derangements</h3> <div class="paragraph"> <p>A permutation with no 1-cycles&#8201;&#8212;&#8201;in other words, a permutation in which no element is left in its original position so that the entire set has been de-arranged&#8201;&#8212;&#8201;is called a <a href="https://en.wikipedia.org/wiki/Derangement">derangement</a>. One way to derange our example group of secret santas is</p> </div> <div class="stemblock"> <div class="content"> $\begin{equation} \label{eq:der} \begin{pmatrix} \mathrm{S} &amp; \mathrm{C} &amp; \mathrm{A} &amp; \mathrm{L} &amp; \mathrm{M} \\ \mathrm{C} &amp; \mathrm{A} &amp; \mathrm{S} &amp; \mathrm{M} &amp; \mathrm{L} \\ \end{pmatrix} \end{equation}$ </div> </div> <div class="paragraph"> <p>Or equivalently, decomposed into its cycles, $$(\mathrm{S\; C\; A})(\mathrm{M\; L})$$.</p> </div> <div class="paragraph"> <p>So the problem of generating a valid secret santa assignment is equivalent to generating a derangement. Some algorithms for uniformly generating random derangements are presented in the next section. But first we need a way to calculate $$D_n$$, the number of derangements that can be made from a set with $$n$$ elements.</p> </div> <div class="paragraph"> <p>Counting derangements is a trickier than counting unrestricted permutations. We proceed by counting the permutations with at least one 1-cycle, the <em>non</em>-derangements. First we&#8217;ll define the subsets $$H_p$$ to contain all of the permutations on $$n$$ with the $$p^{\text{th}}$$ element fixed in its original position (an element that stays in its original position in a permutation is called a &#8220;fixed point&#8221;). This gives $$n$$ such subsets $$\mathrm{(}H_1 \ldots H_n\mathrm{)}$$ each containing $$(n-1)!$$ permutations (because with one element fixed, there are $$(n-1)!$$ ways to permute the remaining elements).</p> </div> <div class="paragraph"> <p>We know that the subsets $$H_p$$ contain only non-derangements since every member has a fixed point. And since together the $$H_p$$ contain every non-derangement with the $$p^{\text{th}}$$ element fixed, we know that they contain <em>all</em> possible non-derangements. That means to find $$D_n$$ we just need to subtract the size of the union of all the $$H_p$$ subsets from the total number of permutations (which we know is $$n!$$):</p> </div> <div class="stemblock"> <div class="content"> $\begin{equation} \label{eq:up} D_n = n! - \left\lvert \bigcup H_p \right\rvert \\ \end{equation}$ </div> </div> <div class="paragraph"> <p>But finding the size of $$\cup H_p$$ is not straightforward. If we simply multiply the number of subsets by their size $$n\cdot(n-1)!$$ we over count because the subsets $$H_p$$ are not disjoint: some non-derangements belong to more than one subset. Specifically, every pair of subsets of $$H_p$$ share $$(n-2)!$$ permutations with at least two fixed points; every 3-tuple of subsets share $$(n-3)!$$ permutations with at least three fixed points; and so on.</p> </div> <div class="paragraph"> <p>To visualize this, it helps to draw out each $$H_p$$ for a small set. The table below shows each $$H_p$$ subset for the set $$\{\,a, b, c, d\,\}$$:</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 25%;"> <col style="width: 25%;"> <col style="width: 25%;"> <col style="width: 25%;"> </colgroup> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">$$H_1$$</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">$$H_2$$</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">$$H_3$$</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">$$H_4$$</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">abcd</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">abcd</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">abcd</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">abcd</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">abdc</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">abdc</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">adcb</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">acbd</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">acbd</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">cbad</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">bacd</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">bacd</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">acdb</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">cbda</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">bdca</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">bcad</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">adbc</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">dbac</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">dacb</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">cabd</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">adcb</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">dbca</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">dbca</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">cbad</p></td> </tr> </tbody> </table> <div class="paragraph"> <p>Notice that the first column, $$H_1$$, has "a" fixed in the first position; the second column has "b" fixed in the second position; etc. Note also that the $$H_1$$ and $$H_2$$ columns share every permutation with the first and second position fixed ("abcd" and "abdc").</p> </div> <div class="paragraph"> <p>To weed out the duplicates, we need to subtract the number of permutations with at least two fixed points multiplied by the number of pairs of $$H_p$$ subsets. But that will leave us with an <em>under</em> count because it will result in some permutations with three or more fixed points being excluded, so we must add those back in. We need to continue this <a href="https://en.wikipedia.org/wiki/Inclusion%E2%80%93exclusion_principle">inclusion-exclusion</a> process until we&#8217;ve considered the number of permutations which fix all $$n$$ elements in $$H_p$$ taken $$n$$ at a time:</p> </div> <div class="stemblock"> <div class="content"> $\begin{equation*} \label{eq:binom} \left\lvert \bigcup H_p \right\rvert = \binom{n}{1}(n-1)! - \binom{n}{2}(n-2)! + \binom{n}{3}(n-3)! - \cdots (-1)^n \binom{n}{n}(n-n)! \end{equation*}$ </div> </div> <div class="paragraph"> <p>where $$\binom{n}{k}$$ gives the <a href="https://en.wikipedia.org/wiki/Binomial_coefficient">binomial coefficients</a> which you may remember from math class can be interpreted as the number of ways to choose $$k$$ objects from a set of $$n$$ objects when order doesn&#8217;t matter. It can be written in terms of factorials:</p> </div> <div class="stemblock"> <div class="content"> $\begin{equation*} \binom{n}{k} = \frac{n!}{(n-k)!k!} \end{equation*}$ </div> </div> <div class="paragraph"> <p>No we can calculate the number of possible <em>non</em>-deranged permutations. To get $$D_n$$, we just subtract it from the total number of possible permutations. When we substitute the expression for $$\lvert \bigcup H_p \rvert$$ into equation \eqref{eq:up} then expand the binomial coefficients and factorials, this becomes:</p> </div> <div class="stemblock"> <div class="content"> $\begin{eqnarray} \notag D_n &amp; = &amp; n! - \frac{n!}{1!} + \frac{n!}{2!} - \frac{n!}{3!} + \cdots + (-1)^n \\ \notag &amp; = &amp; n!\left(1 - \frac{1}{1!} + \frac{1}{2!} - \frac{1}{3!} + \cdots + (-1)^n \frac{1}{n!}\right) \\ \label{eq:dn} &amp; = &amp; n!\sum_{k=0}^n(-1)^k\frac{1}{k!} \end{eqnarray}$ </div> </div> <div class="paragraph"> <p>And we&#8217;ve answered our first question: <strong>Given a group of size $$n$$, there are $$D_n =n!\sum_{k=0}^n(-1)^k\frac{1}{k!}$$ ways to make a valid secret santa assignment.</strong> To calculate the number of valid assignments between our five example friends, then, we have</p> </div> <div class="stemblock"> <div class="content"> $\begin{eqnarray*} D_5 &amp; = &amp; 5!\left(1 - \frac{1}{1!} + \frac{1}{2!} - \frac{1}{3!} + \frac{1}{4!} - \frac{1}{5!}\right) \\ &amp; = &amp; 120 \left(1 - 1 + \frac{1}{2} - \frac{1}{6} + \frac{1}{24} - \frac{1}{120}\right) \\ &amp; = &amp; 120 \left(\frac{44}{120}\right) \\ &amp; = &amp; 44 \end{eqnarray*}$ </div> </div> <div class="paragraph"> <p>The first nine values of $$D_n$$ are listed in the table below.</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 10%;"> <col style="width: 10%;"> <col style="width: 10%;"> <col style="width: 10%;"> <col style="width: 10%;"> <col style="width: 10%;"> <col style="width: 10%;"> <col style="width: 10%;"> <col style="width: 10%;"> <col style="width: 10%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">$$n$$</th> <th class="tableblock halign-left valign-top">1</th> <th class="tableblock halign-left valign-top">2</th> <th class="tableblock halign-left valign-top">3</th> <th class="tableblock halign-left valign-top">4</th> <th class="tableblock halign-left valign-top">5</th> <th class="tableblock halign-left valign-top">6</th> <th class="tableblock halign-left valign-top">7</th> <th class="tableblock halign-left valign-top">8</th> <th class="tableblock halign-left valign-top">9</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><strong>$$D_n$$</strong></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">0</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">1</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">2</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">9</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">44</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">265</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">1,854</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">14,833</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">133,496</p></td> </tr> </tbody> </table> <div class="paragraph"> <p>This is OEIS sequence <a href="https://oeis.org/A000166">A000166</a>. The number $$D_n$$ is known as the <a href="http://mathworld.wolfram.com/Subfactorial.html">subfactorial</a> of $$n$$ (usually written $$!n$$). It is also a special case of the <a href="https://en.wikipedia.org/wiki/Rencontres_numbers">rencontres numbers</a> which enumerate partial derangements (derangements with specified numbers of 1-cycles).</p> </div> <div class="paragraph"> <p>Notice that the summation in equation $$\eqref{eq:dn}$$ is the $$n^{\text{th}}$$ partial <a href="https://en.wikipedia.org/wiki/Taylor_series">Maclaurin expansion</a> of $$e^{-1}$$, so that</p> </div> <div class="stemblock"> <div class="content"> $\begin{equation*} \lim_{n \to \infty} D_n = \frac{n!}{e} \end{equation*}$ </div> </div> <div class="paragraph"> <p>Because the series converges rather quickly, $$\frac{n!}{e}$$ is a good approximation even for even small values of $$n$$.</p> </div> <div class="paragraph"> <p>The probability that a permutation is a derangement is $$D_n$$ divided by the number of all possible permutations $$n!$$. This answers the second question asked in the introduction: <strong>The probability that at least one secret santa participant will draw their own name is</strong> $$1 - \frac{D_n}{n!} \approx 1 - \frac{1}{e} \approx 63\%$$. That may seem high, but the mean of the <a href="https://en.wikipedia.org/wiki/Geometric_distribution">geometric distribution</a> is $$e$$ so you can expect to draw a valid derangement after 2 or 3 attempts (restarting each time someone draws their own name). There is a nearly 99% chance of having drawn a derangement after 10 attempts $$(1 - (1 - \frac{1}{e})^{10} \approx 98.9\%)$$.</p> </div> <div class="paragraph"> <p>Beyond counting mere derangements there are more elaborate constraints and questions we could consider, the sorts of things investigated by <a href="https://en.wikipedia.org/wiki/Random_permutation_statistics">statistics of random permutations</a> and <a href="https://en.wikipedia.org/wiki/Generating_function">generating functions</a>. We haven&#8217;t even answered the third question from the introduction yet (&#8220;What is the probability that two people will draw each other&#8217;s names?&#8221;). I hope to return to this article in the future when I have more time and a better grasp of combinatoric tools to look into some of those questions.</p> </div> <div class="paragraph"> <p>If my explanation above of how to derive $$D_n$$ was not clear, don&#8217;t worry. Counting derangements is frequently used as an example application of the inclusion-exclusion principle, so better explanations can be found on the web and in almost any introductory combinatorics textbook. See, for example, Professor Howard Haber&#8217;s <a href="http://scipp.ucsc.edu/~haber/ph116C/InclusionExclusion.pdf">handout on the inclusion-exclusion principle [PDF]</a>. There are also several other methods for deriving and proving the formula for $$D_n$$, including those that first derive the recurrence relation $$D_n = (n-1)(D_{n-1} + D_{n-2})$$ and then solve it by iteration or by the method of generating functions. For a solution via generating functions see Jean Pierre Mutanguha&#8217;s <a href="http://euler.genepeer.com/the-power-of-generating-functions/">&#8220;The Power of Generating Functions&#8221;</a>.</p> </div> </div> </div> </div> <div class="sect1"> <h2 id="_algorithms">Algorithms</h2> <div class="sectionbody"> <div class="quoteblock"> <blockquote> [A]lmost as many algorithms have been published for unsorting as for sorting! </blockquote> <div class="attribution"> &#8212; Donald Knuth </div> </div> <div class="paragraph"> <p>We&#8217;ll use Python to explore some algorithms for generating random derangements. The functions given below are sometimes simplified to get the main ideas across; the complete versions can be found in <a href="https://github.com/cristoper/sinterbot/blob/master/sinterbot/algorithms.py">alogrithms.py in the github repository</a>. To keep things simple, all of the functions operate only on permutations of the set of integers from 0 to n-1. If we&#8217;d like to permute a set of some other n objects (like santas) we can then use those integers as indexes into the list of our other objects.</p> </div> <div class="sect2"> <h3 id="_utilities">Utilities</h3> <div class="paragraph"> <p>There are a few utility functions that we might want while exploring and debugging our algorithms. First of all, the ability to calculate $$D_n$$, the number of derangements in a set of size $$n$$. Here is a straightforward translation of $$\eqref{eq:dn}$$ to Python:</p> </div> <div class="listingblock"> <div class="title">Dn()</div> <div class="content"> <pre class="CodeRay highlight"><code data-lang="python"><span class="keyword">import</span> <span class="include">math</span> <span class="keyword">from</span> <span class="include">decimal</span> <span class="keyword">import</span> <span class="include">Decimal</span> <span class="keyword">def</span> <span class="function">Dn</span>(n: <span class="predefined">int</span>): <span class="comment"># Use Decimal to handle large n accurately</span> <span class="comment"># (by large, I mean n&gt;13 or so...</span> <span class="comment"># factorials get big fast!)</span> s = <span class="integer">0</span> <span class="keyword">for</span> k <span class="keyword">in</span> <span class="predefined">range</span>(n+<span class="integer">1</span>): s += (-<span class="integer">1</span>)**k/Decimal(math.factorial(k)) result = math.factorial(n) * s <span class="keyword">return</span> Decimal.to_integral_exact(result) [<span class="predefined">int</span>(Dn(i)) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="predefined">range</span>(<span class="integer">9</span>)] &gt;&gt; [<span class="integer">1</span>, <span class="integer">0</span>, <span class="integer">1</span>, <span class="integer">2</span>, <span class="integer">9</span>, <span class="integer">44</span>, <span class="integer">265</span>, <span class="integer">1854</span>, <span class="integer">14833</span>]</code></pre> </div> </div> <div class="paragraph"> <p>Next up is a way to generate all $$n!$$ permutations of a set. Several algorithms for generating permutations are well-known. Most classic is an algorithm which produces permutations in lexicographical order described by Knuth in <a href="http://www.cs.utsa.edu/~wagner/knuth/fasc2b.pdf">7.2.1.2 Algorithm L</a>. Other techniques produce all permutations by only swapping one pair of elements at a time (see the <a href="https://en.wikipedia.org/wiki/Steinhaus%E2%80%93Johnson%E2%80%93Trotter_algorithm">Steinhaus-Johnson-Trotter algorithm</a> which Knuth gives as Algorithm P).</p> </div> <div class="paragraph"> <p>But in our case the Python standard library provides a function for generating permutations (in lexicographical order) so we&#8217;ll just use that:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="python"><span class="keyword">import</span> <span class="include">itertools</span> <span class="predefined">list</span>(itertools.permutations([<span class="integer">0</span>,<span class="integer">1</span>,<span class="integer">2</span>])) &gt;&gt; [(<span class="integer">0</span>, <span class="integer">1</span>, <span class="integer">2</span>), (<span class="integer">0</span>, <span class="integer">2</span>, <span class="integer">1</span>), (<span class="integer">1</span>, <span class="integer">0</span>, <span class="integer">2</span>), (<span class="integer">1</span>, <span class="integer">2</span>, <span class="integer">0</span>), (<span class="integer">2</span>, <span class="integer">0</span>, <span class="integer">1</span>), (<span class="integer">2</span>, <span class="integer">1</span>, <span class="integer">0</span>)]</code></pre> </div> </div> <div class="paragraph"> <p>Another helpful function would be a way to decompose permutations into their cycles to make them easier to visualize (taking $$(0, 1, 2,\cdots, n-1)$$ to be the identity permutation). To find the cycles in a permutation, start with the first element and then visit the element it points to (the element in its position in the identity permutation), and then visit the element that one points to and so on until we get back to the first element. That completes a cycle containing each of the elements visited, add it to a list. Now start over with the first unvisited element. Repeat until there are no more unvisited elements.</p> </div> <div class="paragraph"> <p>Below is an implementation of that algorithm. It requires storage for the list of cycles (the <code>cycles</code> variable), a way to keep track of unvisited elements (the <code>unvisited</code> variable, which starts as a copy of the input but has elements removed as they are visited), and a way to keep track of the first element in a cycle so that we know when we&#8217;ve returned to it (the variable called <code>first</code> below):</p> </div> <div class="listingblock"> <div class="title">decompose()</div> <div class="content"> <pre class="CodeRay highlight"><code data-lang="python"><span class="keyword">def</span> <span class="function">decompose</span>(perm): cycles = [] unvisited = <span class="predefined">list</span>(perm) <span class="keyword">while</span> <span class="predefined">len</span>(unvisited): first = unvisited.pop(<span class="integer">0</span>) cur = [first] nextval = perm[first] <span class="keyword">while</span> nextval != first: cur.append(nextval) <span class="comment"># Remove each element from unvisited</span> <span class="comment"># once we visit it</span> unvisited.pop(unvisited.index(nextval)) nextval = perm[nextval] cycles.append(cur) <span class="keyword">return</span> cycles</code></pre> </div> </div> <div class="paragraph"> <p>As an example, let&#8217;s see the cycles in $$\{1, 2, 4, 3, 0\}\mathrm{:}$$</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="python">decompose([<span class="integer">1</span>,<span class="integer">2</span>,<span class="integer">4</span>,<span class="integer">3</span>,<span class="integer">0</span>]) &gt;&gt; [[<span class="integer">1</span>, <span class="integer">2</span>, <span class="integer">4</span>, <span class="integer">0</span>], [<span class="integer">3</span>]]</code></pre> </div> </div> <div class="paragraph"> <p>Notice this agrees with the same cycles we found way back in our first example of a permutation (where $$\mathrm{S}=0, \mathrm{C}=1, \mathrm{A}=2, \mathrm{L}=3, \mathrm{M}=4$$) $$\eqref{eq:perm}$$.</p> </div> <div class="paragraph"> <p>Finally, it will be handy to have a function that can test whether a permutation is a derangement or not. One way to do that would be to call <code>decompose()</code> on the permutation and then check if there are any 1-cycles in the decomposition. The nice thing about that method is that it generalizes so we could use it to check if the permutation contains any cycles $$\leq m$$ for any $$m$$.</p> </div> <div class="paragraph"> <p>But if we only care about derangements (the case where $$m=1$$), it is simpler (and faster) to just iterate over the elements of the permutation and check if they are in their original position. If any are, we can immediately return <code>False</code>, the permutation is not a derangement.</p> </div> <div class="listingblock"> <div class="title">check_deranged()</div> <div class="content"> <pre class="CodeRay highlight"><code data-lang="python"><span class="keyword">def</span> <span class="function">check_deranged</span>(perm): <span class="keyword">for</span> i, el <span class="keyword">in</span> <span class="predefined">enumerate</span>(perm): <span class="keyword">if</span> el == i: <span class="keyword">return</span> <span class="predefined-constant">False</span> <span class="keyword">return</span> <span class="predefined-constant">True</span> check_deranged([<span class="integer">1</span>,<span class="integer">2</span>,<span class="integer">4</span>,<span class="integer">3</span>,<span class="integer">0</span>]) &gt;&gt; <span class="predefined-constant">False</span> decompose([<span class="integer">1</span>,<span class="integer">3</span>,<span class="integer">4</span>,<span class="integer">2</span>,<span class="integer">0</span>]) &gt;&gt; [[<span class="integer">1</span>, <span class="integer">3</span>, <span class="integer">2</span>, <span class="integer">4</span>, <span class="integer">0</span>]] <span class="comment"># Notice no 1-cycles</span> check_deranged([<span class="integer">1</span>,<span class="integer">3</span>,<span class="integer">4</span>,<span class="integer">2</span>,<span class="integer">0</span>]) &gt;&gt; <span class="predefined-constant">True</span></code></pre> </div> </div> </div> <div class="sect2"> <h3 id="_how_not_to_generate_derangements">How not to generate derangements</h3> <div class="paragraph"> <p>The first time I sat down to write a secret santa algorithm, my instinct was to try a <a href="https://en.wikipedia.org/wiki/Backtracking">backtracking</a> approach and ended up with something like the code below. The idea behind the backtracker is to iteratively build a derangement by randomly selecting an element from the identity arrangement, and then checking if the resulting partial permutation is a derangement. If it is not a derangement, undo (backtrack) the last choice and try again. If it is a derangement, randomly choose one of the remaining elements and check again. Repeat until you&#8217;ve deranged all $$n$$ elements:</p> </div> <div class="listingblock"> <div class="title">backtracker()</div> <div class="content"> <pre class="CodeRay highlight"><code data-lang="python"><span class="keyword">import</span> <span class="include">random</span> <span class="keyword">def</span> <span class="function">backtracker</span>(n): <span class="keyword">if</span> n == <span class="integer">0</span>: <span class="keyword">return</span> [] remaining = <span class="predefined">list</span>(<span class="predefined">range</span>(n)) perm = [] <span class="comment"># backtrack until solution</span> <span class="keyword">while</span> <span class="predefined">len</span>(perm) &lt; n: perm.append(random.choice(remaining)) <span class="keyword">if</span> <span class="keyword">not</span> check_deranged(perm): <span class="keyword">if</span> <span class="predefined">len</span>(remaining) == <span class="integer">1</span>: <span class="comment"># we're down to the last two elements</span> <span class="comment"># just swap them to get a derangement</span> perm[-<span class="integer">1</span>], perm[-<span class="integer">2</span>] = perm[-<span class="integer">2</span>], perm[-<span class="integer">1</span>] <span class="keyword">return</span> perm <span class="comment"># undo last choice</span> perm.pop(-<span class="integer">1</span>) <span class="keyword">else</span>: remaining.remove(perm[-<span class="integer">1</span>]) <span class="keyword">return</span> perm <span class="comment"># Use it to generate a derangement and view the cycles:</span> perm = backtracker(<span class="integer">5</span>) perm, decompose(perm) &gt;&gt; ([<span class="integer">1</span>, <span class="integer">2</span>, <span class="integer">0</span>, <span class="integer">4</span>, <span class="integer">3</span>], [[<span class="integer">1</span>, <span class="integer">2</span>, <span class="integer">0</span>], [<span class="integer">4</span>, <span class="integer">3</span>]])</code></pre> </div> </div> <div class="paragraph"> <p>As written, <code>backtracker()</code> is fast and will produce any possible derangement (and in only ~20 lines of Python), so it <em>could</em> be used for secret santa. However, as the decades fly by your friends might begin to suspect that the same assignments seem to be &#8216;randomly&#8217; generated fairly often. They would be right: <code>backtracker()</code> does not produce derangements with uniform probability. Even though each element in the derangement is chosen from the remaining elements of the input set with uniform probability, the number of possible derangements is dependant on which numbers happen to have been chosen first.</p> </div> <div class="paragraph"> <p>For example, let&#8217;s look at the probability that <code>backtracker()</code> will produce $$(5, 0, 1, 2, 3, 4).$$ The first number can be anything but 0, so there are $$6-1=5$$ ways to choose that. The second number can be any of the remaining 5 numbers except 1, so there are 4 ways to choose that. The third number can be any of the remaining 4 numbers except 2 which leaves 3 possibilities. The fourth element can be any of the remaining numbers except for 2 which leaves 2 possibilities. The fifth element can not be 4, so there is only one way to derange the last two elements. If we take the product of those probabilities we get $$\frac{1}{5} \cdot \frac{1}{4} \cdot \frac{1}{3} \cdot \frac{1}{2} = \frac{1}{120}.$$</p> </div> <div class="paragraph"> <p>If you do a similar calculation for the probability that <code>backtracker()</code> would produce $$(2, 3, 5, 4, 0, 1)$$ you should get $$\frac{1}{360}.$$ Not only are the probabilities for generating those two derangements significantly different from each other but they also both differ from the expected probability of $$\frac{1}{265}$$ if every one of the $$D_6$$ derangements had an equal probability of being generated. I generated 10,000 derangements of length 6 with <code>backtracker()</code>, and sure enough $$(5, 0, 1, 2, 3, 4)$$ was generated 94 times while $$(2, 3, 5, 4, 0, 1)$$ was generated only 20 times. The graph below shows a plot of counts for every 6-derangement over the 10,000 runs:</p> </div> <div class="imageblock"> <div class="content"> <img src="/log/2020/12/7/deranged_sinterklaas/generate_backtrack.png" alt="A bar chart showing the frequency of each derangement produced in 10,000 trials. The backtracker algorithm is clearly not choosing derangements uniformly."> </div> <div class="title">Figure 2. Count of each derangement produced after running backtracker() 10,000 times with $$n = 6$$. It is clearly not choosing derangements uniformly. The grey line shows the expected count if each derangement were generated with uniform probability ($$1/D_6\cdot 10000 \approx 37.7$$)</div> </div> <div class="paragraph"> <p>Instead of building derangements by randomly selecting elements and checking if the result is a derangement, we could simply generate all possible permutations, filter out the non-derangements, and then randomly select one of the derangements to return. The nice thing about that approach is that we could enforce any other constraints we want in the filter step (maybe we want a minimum cycle length or have a &#8220;blacklist&#8221; of people who should not be assigned to each other) and we can still be confident we would select a valid secret santa assignment with uniform probability (since we have generated all of them it is easy to select one at random).</p> </div> <div class="listingblock"> <div class="title">generate_all()</div> <div class="content"> <pre class="CodeRay highlight"><code data-lang="python"><span class="keyword">import</span> <span class="include">random</span> <span class="keyword">def</span> <span class="function">generate_all</span>(n): potential = [] perms = itertools.permutations(<span class="predefined">range</span>(n)) <span class="keyword">for</span> p <span class="keyword">in</span> perms: <span class="keyword">if</span> check_constraints(p, m, bl): potential.append(p) <span class="keyword">return</span> random.choice(potential)</code></pre> </div> </div> <div class="paragraph"> <p>Below you can see a bar graph of the counts after producing 10,000 derangements of length 6 with <code>generate_all()</code>. It <em>looks</em> much more uniform than <code>backtracker()</code> at least. One tool we can use to gauge how closely our counts match what we should expect from a uniform distribution is the <a href="https://en.wikipedia.org/wiki/Chi-squared_test">chi-squared statistic</a>:</p> </div> <div class="stemblock"> <div class="content"> $\begin{equation*} \chi^2 = \sum\frac{(O_i - E_i)^2}{E_i} \end{equation*}$ </div> </div> <div class="paragraph"> <p>where $$O_i$$ are our observed counts and $$E_i$$ are the expected counts for each derangement (which in our case is $$1/D_6\cdot 10000 \approx 37.7$$). For my data I calculated $$\chi^2 \approx 261.75$$. If we check that against the chi-squared cumulative distribution function with k-1 degrees of freedom (where k is the number of data points, 265 in this case), we get a p-value of about 0.53. The p-value is the probability that our $$\chi^2$$ value would be least 261.75 if our counts were uniformly distributed. Usually if p&lt;0.05 it would be prudent to question whether the data fits a uniform distribution. On the other hand if p&gt;0.99 or so we could be confident it is uniform, but we might question whether it is random. A p-value of 0.53 should leave us confident that <code>generate_all()</code> randomly generates derangements with uniform or very nearly uniform probability.</p> </div> <div class="imageblock"> <div class="content"> <img src="/log/2020/12/7/deranged_sinterklaas/generate_all.png" alt="A bar chart showing the frequency of each derangement produced in 10,000 trials. The generate_all algorithm appears to be uniform"> </div> <div class="title">Figure 3. Count of each derangement produced after running generate_all() 10,000 times with $$n = 6$$. $$\chi^2 \approx 261.75$$ and the chi-squared test p-value ≈ 0.53. The grey line shows the expected count if each derangement were generated with uniform probability ($$\approx 37.7$$)</div> </div> <div class="paragraph"> <p>But there are two major problems with <code>generate_all()</code>: it is slow (because we have to generate all $$n!$$ permutations), and it uses a lot of memory (because we have to store all $$D_n$$ derangements). $$D_{12} = 176,214,841$$, for example, so even if we implemented our permutations in some memory efficient way (say an array of one byte per element), we would need over 1GB of memory just to store all of the derangements before returning one. Running <code>generate_all()</code> with $$n&gt;11$$ runs my desktop out of RAM after about a minute and crashes the Python interpreter. And in the grand scheme of things 12 is not such a huge number.</p> </div> </div> <div class="sect2"> <h3 id="_how_to_generate_derangements">How to generate derangements</h3> <div class="paragraph"> <p>We can do better than the <code>backtracker</code> and <code>generate_all</code> algorithms above by combining the best aspects of each: generate a single random permutation and check if it is a derangement. If it is, return it; otherwise, try again by generating another random permutation. That should be much more efficient than generating and storing all possible derangements, and as long as we can generate permutations with uniform probability we will also generate derangements with uniform probability.</p> </div> <div class="paragraph"> <p>A well-known algorithm for creating a random permutation by shuffling a given arrangement is to simply select one of the elements at random, set that as the leftmost element of the permutation, and then repeat by selecting one of the remaining elements at random until all of the elements have been selected. This is known as the <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates shuffle</a> named for two statisticians who described a paper-and-pen method for shuffling a sequence in 1938. The computer version of the algorithm&#8201;&#8212;&#8201;popularized in Chapter 3 of Knuth&#8217;s <em>The Art of Computer Programming</em> (&#8220;Algorithm P (Shuffling)&#8221;)&#8201;&#8212;&#8201;usually shuffles an array in place. It does this by iterating through the array from left to right swapping the element at the index with a random element to the right of the index. Once an element has been swapped left it is in its position in the generated permutation. Repeat to the end. For a good visualization of Fisher-Yates (and how it compares to less efficient algorithms) see Mike Bostock&#8217;s <a href="https://bost.ocks.org/mike/shuffle/">Fisher-Yates Shuffle</a>.</p> </div> <div class="paragraph"> <p>The <code>shuffle_rejection()</code> algorithm below repeatedly shuffles a list using Fisher-Yates until the resulting permutation is a derangement:</p> </div> <div class="listingblock"> <div class="title">shuffle_rejection()</div> <div class="content"> <pre class="CodeRay highlight"><code data-lang="python"><span class="keyword">def</span> <span class="function">shuffle_rejection</span>(n): perm = <span class="predefined">list</span>(<span class="predefined">range</span>(n)) <span class="keyword">while</span> <span class="keyword">not</span> check_deranged(perm): <span class="comment"># Fisher-Yates shuffle:</span> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="predefined">range</span>(n): k = random.randrange(n-i)+i <span class="comment"># i &lt;= k &lt; n</span> perm[i], perm[k] = perm[k], perm[i] <span class="keyword">return</span> perm</code></pre> </div> </div> <div class="paragraph"> <p>Notice that in the Fisher-Yates algorithm the range of the random index <code>k</code> includes the index of the current element <code>i</code>. In other words, elements can swap with themselves creating a 1-cycle. That is necessary, of course, to generate all possible permutations. If the algorithm is changed so that <code>k</code> ranges only from $$i &lt; k &lt; n$$ so that it does not include <code>i</code>, then the algorithm will produce only permutations with a single n-cycle. This is known as <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Sattolo&#8217;s_algorithm">Sattolo&#8217;s algorithm</a>, and it generates n-cycles with uniform probability.</p> </div> <div class="paragraph"> <p>The bar graph above summarizes the results of generating 10,000 derangements of size 6 with <code>shuffle_rejection()</code>. It appears to be uniform as expected. It is also fast and simple, which makes it perfectly suitable for generating secret santa assignments (and is, in fact, what I use in <a href="#_software">sinterbot</a>, my secret santa tool).</p> </div> <div class="imageblock"> <div class="content"> <img src="/log/2020/12/7/deranged_sinterklaas/generate_rejection.png" alt="A bar chart showing the frequency of each derangement produced in 10,000 trials. shuffle_rejection algorithm appears to be uniform"> </div> <div class="title">Figure 4. Count of each derangement produced after running shuffle_rejection() 10,000 times with $$n = 6$$. $$\chi^2 \approx 278.9$$ and the chi-squared test p-value ≈ 0.25.</div> </div> <div class="paragraph"> <p>One inelegance of <code>shuffle_rejection()</code> is that it generates any number of random permutations just to throw them away. It is possible that it would never actually generate a derangement and just keep generating and rejecting permutations all day. In reality derangements are common enough that that is not a practical concern (it finds close to 20,000 derangements of length 6 per second on my old desktop). But is there a way to directly generate derangements with uniform probability without needing to backtrack or reject non-deranged permutations?</p> </div> <div class="paragraph"> <p>Yes. In 2008 Martínez et al. published one such algorithm (<a href="https://doc.lagout.org/science/0_Computer%20Science/2_Algorithms/Proceedings%20of%20the%20Tenth%20Workshop%20on%20Algorithm%20Engineering%20and%20Experiments%20and%20the%20Fifth%20Workshop%20on%20Analytic%20Algorithmics%20and%20Combinatorics%20%5BMunro%20et%20al.%202008-05-30%5D.pdf">&#8220;Generating Random Derangements,&#8221;</a> 234-240). It is similar to Sattolo&#8217;s algorithm, but instead of joining every element into a single n-cycle, it will randomly close cycles at a specific probability which ensures a uniform generation of derangements. <a href="https://www.cs.upc.edu/~conrado/research/talks/analco08.pdf">Here is a nice set of slides</a> that goes through their algorithm step by step.</p> </div> <div class="paragraph"> <p>Jörg Arndt provides an easier to follow (in my opinion) version of the algorithm in his 2010 thesis <a href="https://maths-people.anu.edu.au/~brent/pd/Arndt-thesis.pdf"><em>Generating Random Permutations</em></a>. (It&#8217;s a short book that includes several useful algorithms.) This Python implementation more closely follows his version:</p> </div> <div class="listingblock"> <div class="title">rand_derangement()</div> <div class="content"> <pre class="CodeRay highlight"><code data-lang="python"><span class="keyword">def</span> <span class="function">rand_derangement</span>(n): perm = <span class="predefined">list</span>(<span class="predefined">range</span>(n)) remaining = <span class="predefined">list</span>(perm) <span class="keyword">while</span> (<span class="predefined">len</span>(remaining)&gt;<span class="integer">1</span>): <span class="comment"># random index &lt; last:</span> rand_i = random.randrange(<span class="predefined">len</span>(remaining)-<span class="integer">1</span>) rand = remaining[rand_i] last = remaining[-<span class="integer">1</span>] <span class="comment"># swap to join cycles</span> perm[last], perm[rand] = perm[rand], perm[last] <span class="comment"># remove last from remaining</span> remaining.pop(-<span class="integer">1</span>) p = random.random() <span class="comment"># uniform [0, 1)</span> r = <span class="predefined">len</span>(remaining) prob = r * Dn(r-<span class="integer">1</span>)/Dn(r+<span class="integer">1</span>) <span class="keyword">if</span> p &lt; prob: <span class="comment"># Close the cycle</span> remaining.pop(rand_i) <span class="keyword">return</span> perm</code></pre> </div> </div> <div class="imageblock"> <div class="content"> <img src="/log/2020/12/7/deranged_sinterklaas/rand_derangement.png" alt="A bar chart showing the frequency of each derangement produced in 10,000 trials. The rand_derangement() algorithm appears to be uniform."> </div> <div class="title">Figure 5. Count of each derangement produced after running rand_derangement() 10,000 times with $$n = 6$$. $$\chi^2 \approx 261.1$$ and the chi-squared test p-value ≈ 0.54.</div> </div> <div class="paragraph"> <p>Arndt&#8217;s implementation is in C with a precomputed lookup table for the ratio calculated on the <code>prob = r * Dn(r-1)/Dn(r+1)</code> line. Even then he reports it is only slightly faster than the rejection method. This Python implementation is actually about twice as slow as the rejection method in my tests.</p> </div> <div class="paragraph"> <p>But one advantage of generating derangements directly as in <code>rand_derangement()</code> is that it can be generalized to generate derangements with minimum cycle lengths. Arndt shows how that can be done in his thesis.</p> </div> <div class="paragraph"> <p>There are other ways to generate random derangements that I&#8217;ve not covered in this post. Earlier this year J. Ricardo G. Mendonça published two new algorithms for [almost-]uniformly generating random derangements: <a href="https://arxiv.org/pdf/1809.04571.pdf">&#8220;Efficient generation of random derangements with the expected distribution of cycle lengths,&#8221;</a> <em>Computational and Applied Mathematics</em> 39, no. 3 (2020): 1-15.</p> </div> </div> </div> </div> <div class="sect1"> <h2 id="software">Sinterbot2020</h2> <div class="sectionbody"> <div class="paragraph"> <p><code>sinterbot</code> is a little command line program (Python 3.5+) that helps to manage secret santa assignments. With <code>sinterbot</code> you can generate a valid secret santa assignment for a list of people and email each person their assigned gift recipient without ever revealing to anybody (including the operator of <code>sinterbot</code>) the full secret list of assignments.</p> </div> <div class="paragraph"> <p>Source code and more usage instructions: <a href="https://github.com/cristoper/sinterbot" class="bare">https://github.com/cristoper/sinterbot</a></p> </div> <div class="paragraph"> <p><code>sinterbot</code> allows specifying some extra constraints such as minimum cycle length or a blacklist of people who should not be assigned to each other.</p> </div> <div class="sect2"> <h3 id="_installation">Installation</h3> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="sh">pip install sinterbot</code></pre> </div> </div> </div> <div class="sect2"> <h3 id="_usage">Usage</h3> <div class="paragraph"> <p>First create a config file with a list of participants' names and email addresses. The config file may also specify constraints for minimum cycle length and a blacklist. See <a href="https://github.com/cristoper/sinterbot/blob/master/sample.conf">sample.conf</a> for a full example:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="xmas2020.conf"># xmas2020.conf Santa A: user1@email.tld Santa B: user2@email.tld Santa C: user3@email.tld Santa D: user4@email.tld Santa E: user5@email.tld</code></pre> </div> </div> <div class="paragraph"> <p>The format is <code>Name: emailaddress</code>. Only the email addresses need to be unique.</p> </div> <div class="paragraph"> <p>Then run <code>sinterbot derange</code> to compute a valid assignment and save it to the config file:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="sh">$sinterbot derange xmas2020.conf Derangement info successfully added to config file. Use sinterbot send sample.conf -c smtp.conf to send emails!</code></pre> </div> </div> <div class="paragraph"> <p><code>sinterbot</code> will not allow you to re-derange a config file without passing the <code>--force</code> flag.</p> </div> <div class="paragraph"> <p>Now if you want you can view the secret santa assignments with <code>sinterbot view xmas2020.conf</code>. However, if you&#8217;re a participant that would ruin the suprise for you! Instead you can email each person their assignment without ever seeing them yourself.</p> </div> <div class="paragraph"> <p>First create a file to specify your SMTP credentials:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="sh"># smtp.conf SMTPEmail: yourname@gmail.com SMTPPass: yourgmailpassword SMTPServer: smtp.gmail.com SMTPPort: 587</code></pre> </div> </div> <div class="paragraph"> <p>(If you do not know what SMTP server to use but you have a gmail account, you can <a href="https://www.digitalocean.com/community/tutorials/how-to-use-google-s-smtp-server">use gmail&#8217;s SMTP server</a>) using values like those exemplified above.)</p> </div> <div class="paragraph"> <p>Then run the <code>sinterbot send</code> command, giving it the smtp credentials file with the <code>-c</code> option, to send the emails:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="sh">$ sinterbot send xmas2020.conf -c smtp.conf Send message to user1@email.tld! Send message to user2@email.tld! Send message to user3@email.tld! Send message to user4@email.tld! Send message to user5@email.tld!</code></pre> </div> </div> </div> </div> </div> How to generate uniformly random derangement for secret santa purposes.