Derangement extra postage

I left something out of last night’s post on derangements: the Python program that generated the table of values I used to search the OEIS for the sequences associated with one, two, three, etc. fixed points. It was a deliberate omission, as I thought the post was long enough as it was. Consider this post the equivalent of Numberphile’s “extra footage.”

Recall that the table looked like this (you may have to scroll left to see it all)

                                   m
           0      1      2      3      4      5      6      7      8
     ----------------------------------------------------------------
  2 |      1      0      1
  3 |      2      3      0      1
  4 |      9      8      6      0      1
n 5 |     44     45     20     10      0      1
  6 |    265    264    135     40     15      0      1
  7 |   1854   1855    924    315     70     21      0      1
  8 |  14833  14832   7420   2464    630    112     28      0      1

where \(n\) is the number of cards we’re shuffling and \(m\) is the number of fixed points, that is, the number of cards that are in their original position after the shuffling.

Shuffling cards is a permuting operation, and the itertools module in Python’s standard library has a permutations function that returns all the permutations of the list (or other iterable) passed to it. The program I wrote does nothing more than call that function to generate the permutations and then search through them, counting up the number of fixed points in each. It does that for \(n = 2 \ldots 8\).

Here it is:

python:
 1:  #!/usr/bin env python
 2:  
 3:  from itertools import permutations
 4:  
 5:  def countMatches(o, p):
 6:    count = 0
 7:    for i in range(len(o)):
 8:      if p[i] == o[i]:
 9:        count += 1
10:    return count
11:  
12:  # Table header
13:  print(' '*3, end='')
14:  for m in range(9):
15:    print(f'{m:7d}', end='')
16:  print()
17:  print(' '*3 + '-'*64)
18:  
19:  # Table body
20:  for n in range(2, 9):
21:    cards = [chr(i+65) for i in range(n)]
22:    counts = [0]*(n+1)
23:  
24:    for a in permutations(cards):
25:      matches = countMatches(a, cards)
26:      counts[matches] += 1
27:    
28:    print(f'{n} |', end='')
29:    for i in range(n+1):
30:      print(f'{counts[i]:7d}', end='')
31:    print()

The key to the script is the countMatches function in Lines 5–10. When given the original list and a permutation, it marches through each and returns the number of fixed points. I’m sure there’s a more clever way of doing this, but I wasn’t interested in spending the time to be clever. Since the program as a whole is a brute force approach to the problem, I didn’t care if there was a brute force subroutine lurking within it.

Lines 12–17 just set up the table’s header, with the values of \(m\) and a row of hyphens. The rest of the program loops through all the values of \(n\) to create the row labels and body of the table.

Those of you who know your ASCII will recognize that the list of cards created in Line 21 consists of n capital letters in alphabetical order, starting with A. Line 22 initializes the counts list to a bunch of zeros.

The loop in Lines 24–26 goes through all the permutations and uses the countMatches function to increment the appropriate member of counts. For each item in counts, the index is the number of fixed points, \(m\), and the value is the number of permutations with that many fixed points.

Finally, Lines 28–31 print out the row of the table associated with the current value of n.

The “m” label above the top header row and the “n” label to the left of the row labels were added by hand. It was easier than programming them in.

You’ll note that some of the printing is done with f-strings, so this will only run in Python 3.6 and above. But you could rewrite those lines to use the format method if you’re really stuck with an older version of Python.

If you ever run into a problem that requires rearranging or combining lists, keep itertools in mind.