Code That Counts

Combinatorial calculations with combinator calculus.

Several explanations in Enumerative Combinatorics Volume 1 by Richard P. Stanley are outlines of algorithms. We follow along parts of the first chapter, but replace descriptions of algorithms with real code.

Sets take center stage in mathematics, but sets are troublesome for computation because we must work to decide whether one representation of a set is equivalent to another. For example, we only know \(\{1,2,3\} = \{3,1,2\}\) by confirming one is a permutation of the other. In addition, at some point we must check for duplicates.

We therefore focus on lists instead of sets. Translating results back to the sets is easy enough, and our choice turns out to have an advantage when we consider multisets and ordered sets.

Subsets

From Data.List, we have:

isSubsequenceOf :: (Eq a) => [a] -> [a] -> Bool
isSubsequenceOf []    _                    = True
isSubsequenceOf _     []                   = False
isSubsequenceOf a@(x:a') (y:b) | x == y    = isSubsequenceOf a' b
                               | otherwise = isSubsequenceOf a b
-- Examples:
and
  [ [] `isSubsequenceOf` [1..5]
  , [2,4] `isSubsequenceOf` [1..5]
  , [1..5] `isSubsequenceOf` [1..5]
  , not $ reverse [1..5] `isSubsequenceOf` [1..5]
  , not $ [6] `isSubsequenceOf` [1..5]
  ]

We represent a set \(U\) with a list u of distinct elements. A subset or combination of \(U\) is represented by a list s satisfying isSubsequenceOf s u. In other words:

  1. The empty set, represented by the empty list, is a subset of every set.

  2. The empty set has no non-empty subsets.

  3. There are two kinds of non-empty subsets of a non-empty set: those that begin with its head element x, and those that do not. In both subcases, the remaining subset elements are to be found among a, the tail of the list representing the set.

Subsets of size \(k\) are called k-subsets or k-combinations. To generate them, we closely follow the above cases:

subsets :: [a] -> Int -> [[a]]
subsets _     0 = [[]]
subsets []    _ = []
subsets (x:a) k = map (x:) (subsets a (k - 1)) ++ subsets a k

subsets "abcde" 3

Write \({n \choose k}\) for the number of \(k\)-subsets of a set of size \(n\).

We read this as "n choose k".

The first case of subsets implies for all \(n \ge 0\):

\[{n \choose 0} = 1\]

The second case implies that for any \(k \gt 0\):

\[{0 \choose k} = 0\]

The third case implies:

\[{n \choose k} = {n - 1 \choose k - 1} + {n - 1 \choose k }\]

where \(n, k \gt 0\). The left-hand term of the sum counts all subsets starting with x, while the other side counts all subsets not containing x. In the latter, we know x must be absent because of the uniqueness of each item of the list representing u.

These numbers are called the binomial coefficients, because the coefficient of \(x^k\) in \((1 + x)^n\) is \({n \choose k}\). We can prove via induction that the coefficients satisfy the same recurrence relations.

Pascal’s triangle brings these equations to life:

pascal = iterate (\p -> zipWith (+) ([0] ++ p) (p ++ [0])) [1]
showPascal n = unlines $ take n $ unwords . map show <$> pascal

putStr $ showPascal 16

By induction, we can show:

\[ {n \choose k} = \frac{n^\underline{k}}{k!} \]

where we adopt Knuth’s underline notation for the falling factorial:

\[ n^\underline{k} = n(n-1)..(n-k+1) \]

Stanley writes \( (n)_k \), but we prefer Knuth because it naturally leads to overline notation for the rising factorial:

\[ n^\overline{k} = n(n+1)..(n+k-1) \]

(Thus \( k! = k^\underline{k} = 1^{\overline{k}} \).)

Although there exists a much faster algorithm for computing binomial coefficients, we use falling factorials to compute binomial coefficients for simplicity:

falling n k = product $ take k $ iterate pred n
choose n k = falling n k `div` product [1..k]

choose 10 5

We can view this formula from another perspective. There are \(n\) ways to pick an element from a set of size \(n\):

picks = \case
  [] -> []
  x:xs -> (x, xs):(second (x:) <$> picks xs)

picks [1..5]

Iterating this \(k\) times yields \(n(n-1)…​(n-k+1)\) ways of picking a sequence of size \(k\) consisting of distinct elements of the set:

orderedSets xs n = map fst $ iterate (concatMap step) [([], xs)] !! n
  where step (ys, zs) = first (:ys) <$> picks zs

orderedSets "abcde" 3

In the case \(n = k\), we have that the number of permutations of \(k\) objects is \(k\) factorial.

Now, there are \({n \choose k}\) distinct \(k\)-subsets, and from each we can produce \(k!\) sequences of size \(k\). By equating two different ways of counting the same thing:

\[ n^\underline{k} = {n\choose k} k! \]

Polynomials

Recall:

\[ (1+x)^n = \sum_k {n\choose k} x^k \]

To translate this to code, we represent polynomials with lists of integers, with \([a_0, a_1, …​]\) representing \(a_0 + a_1 x + …​\). Then addition and multiplication of polynomials are given by:

p .+. q = zipWith (+) (pad p) (pad q)
  where pad = take (length p `max` length q) . (++ repeat 0)
p      .*. [] = []
[]     .*. q  = []
(a:as) .*. q  = map (a *) q .+. (0:(as .*. q))

take 5 $ iterate ([1, 1] .*.) [1]

It turns out our representation works well the other way, that is, we can represent a sequence of numbers as a polynomial; in this scheme, an infinite sequence corresponds to a formal power series. This gives us an algebra for manipulating sequences, known as generating functions.

By redefining \({n\choose k}\) to be the coefficient of \(x^k\) in \((1 + x)^n\) written as a power series, we generalize binomial coefficients beyond positive integers \(n\). When \(n\) is a negative integer, we could derive a formula via calculus and Taylor series expansions, or using the recurrences and induction, but we’ll soon see a simpler approach.

A composition of \(n\) is an ordered list of positive integers that sum to \(n\). A k-composition of \(n\) is a composition of \(n\) into \(k\) parts. For example, a 4-composition of 7 is:

\[ 1 + 3 + 2 + 1 = 7 \]

We obtain related definitions by allowing zero. A weak composition of \(n\) is an ordered list of nonnegative integers that sum to \(n\). A weak k-composition of \(n\) is a weak composition of \(n\) into \(k\) parts. For example, a weak 4-composition of 7 is:

\[ 3 + 0 + 2 + 2 = 7 \]

How can we generate all \(k\)-compositions and weak \(k\)-compositions of \(n\)?

Try answering this before studying the following:

compositions n k = ungap <$> subsets [1..n-1] (k-1)
  where ungap xs = zipWith (-) (xs ++ [n]) ([0] ++ xs)

weakCompositions n k = map pred <$> compositions (n + k) k

putStrLn "4-compositions of 7:"
print $ compositions 7 4

putStrLn "weak 3-compositions of 5:"
print $ weakCompositions 5 3

Thus the number of \(k\)-compositions of \(n\) is \({n-1\choose k-1}\), and the number of weak \(k\)-compositions of \(n\) is \({n+k-1\choose k-1}\).

Power Sets

The power set of a set \(S\) is the set of all subsets of \(S\). We can compute it with the standard library subsequences function, or bust out a well-known mind-blowing trick:

powerset = filterM $ const [False, True]

powerset "abc"

The power set of an \(n\)-set has size \(2^n\), as suggested by the length of the list [False, True]. In brief, the list monad is syntax sugar for concatMap. Applying each of \(x\) functions to each of \(y\) items in a list and concatenating everything leads to a list of size \(xy\), so iterating this procedure on \(n\) lists of size 2 leads to a list of size \(2^n\).

Less abstrusely, for each item, we can decide to include it or leave it out, leading to \(2^n\) total different decision paths.

Accordingly, we denote the power set of \(S\) by \(2^S\).

Multisets

Informally, a multiset is a set where elements may appear more than once. More rigorously, a finite multiset \(M\) on a set \(S\) is a function \(v:S\to\Bbb{N}\) with \(\sum_{s\in S} v(s) < \infty\). We’re back to ordinary subsets when \(v(s) \le 1\).

A k-combination with repetition of a set \(S\) is a multiset on \(S\) of size \(k\). The number of \(k\)-combinations with repetition of an \(n\)-set is denoted by \(\left({n \choose k}\right)\).

We can generate all \(k\)-combinations with repetition of \([1..n]\) as follows:

multisets n k = zipWith (flip (-)) [0..] <$> subsets [1..n + k - 1] k

which implies \(\left({n \choose k}\right) = {n + k - 1 \choose k}\):

multichoose n k = choose (n + k - 1) k

We also have:

\[ (1 + x + x^2 + …​)^n = \sum x^{|M|} [M \text{ is a multiset on } [1..n]] = \sum_{k\ge 0} \left({n \choose k}\right) x^k \]

Thus:

\[ (1 + x + x^2 +…​ )^n = (1 - x)^{-n} = \sum_{k\ge 0} {-n \choose k}(-1)^k x^k \]

and therefore:

\[\left({n\choose k}\right) = (-1)^k {-n\choose k} = {n + k - 1\choose k}\]

This is an easy way to compute binomial coefficients for negative \(n\). The first equation is an example of a combinatorial reciprocity theorem.

We generalize binomial cofficients to multinomial coefficients: if \(a_1,…​,a_m\) are nonnegative integers that sum to \(n\), that is, a weak \(m\)-composition of \(n\), then define \({n\choose a_1,…​,a_m}\) to be number of ways to divide \([1..n]\) into \(m\) different categories. We can also view this as the number of permutations of the multiset where there are \(a_i\) copies of the \(i\)th element. Additionally, we have:

\[ (x_1 + ... + x_m)^n = \sum {n\choose a_1,...,a_m} x_1^{a_1}...x_m^{a_m} \]

When \(m = 2\), we have binomial coefficients. Conventionally, we write \({n\choose k}\) instead of \({n\choose k,n-k}\). The latter notation does have advantages: we immediately have \({n\choose k} = {n\choose n-k}\), and we’re mindful that choosing \(k\) elements out of an \(n\)-set is the same as choosing to omit \(n-k\) elements. The recurrence of Pascal’s triangle also becomes symmetric:

\[{n\choose a,b} = {n-1\choose a-1, b} + {n-1\choose a,b-1}\]

Multinomials can be computed by:

\[ {n\choose a_1,…​,a_m} = \frac{n!}{a_1!…​a_m!} \]

and satisfy the recurrence:

\[ {n\choose a_1,…​,a_m} = {n\choose a_1-1,…​,a_m} + …​ + {n\choose a_1,…​,a_m-1} \]

The following is a multiset version of our picks function. We rely on a helper function to keep out degenerate empty lists. We expect identical elements in the input list to be grouped together, for instance, ["b", "aaa", "nn"] instead of "banana".

multipicks = \case
  [] -> []
  a@(x:a'):rest -> (x, consNonEmpty a' rest) : (second (a:) <$> multipicks rest)
  where
  consNonEmpty = \case
    [] -> id
    a  -> (a:)

multipicks ["b", "aaa", "nn"]

Permutations

We’ve already exhibited code to enumerate all permutations of a list xs. Simply evaluate orderedSets xs (length xs).

In Algorithm Design with Haskell, Richard Bird and Jeremy Gibbons describe a more direct approach:

permsPicks :: [a] -> [[a]]
permsPicks = \case
  [] -> [[]]
  xs -> concatMap (\(x, ys) -> (x:) <$> permsPicks ys) $ picks xs

permsPicks "abc"

They also describe another function for generating all permutations, one that is inductive as opposed to recursive:

perms :: [a] -> [[a]]
perms = foldr (concatMap . inserts) [[]] where
  inserts x = \case
    [] -> [[x]]
    y:ys -> (x:y:ys) : ((y:) <$> inserts x ys)

perms "abc"

They discuss important features of these algorithms, which we regrettably ignore for now because we’re focusing on combinatorics.

We can use the multiset version of picks to enumerate all permutations of elements of a multiset:

permsMultipicks :: [[a]] -> [[a]]
permsMultipicks = \case
  [] -> [[]]
  xs -> concatMap (\(x, ys) -> (x:) <$> permsMultipicks ys) $ multipicks xs

permsMultipicks ["b", "aaa", "n"]

A permutation of a set can be represented as a word. In particular, we can represent the permutation \(\pi\) on \([1..n]\) defined by \(\pi(i) = a_i\) as the word \(a_1 …​ a_n\).

Given a permutation \(\pi\) on a finite set \(S\), for any \(x\in S\) the sequence \(x, \pi(x), \pi(\pi(x)), …​\) must eventually repeat, thus permutations can be written as a product of cycles:

cycles xs = f [] [1..n] where
  f [] [] = []
  f [] (i:is) = f [i] is
  f cs is | head cs == next = cs : f [] is
          | otherwise       = f (cs ++ [next]) $ filter (/= next) is
          where next = xs!!(last cs - 1)
  n = length xs

-- Seven cycles of length 1:
cycles [1..7]

-- One cycle of length 7:
cycles $ 7:[1..6]

-- Should be [[1,4],[2],[3,7,5],[6]]:
cycles [4,2,7,1,3,6,5]

We define a standard representation for writing products of cycles. We sort cycles in increasing order by their maximum elements, and each cycle is written starting with its maximum element. We write \(\hat\pi\) for the standard representation of \(\pi\).

Such a representation is unique, and furthermore, we can remove all bracketing: a new cycle begins precisely when an element exceeds the previous maximum, namely, on all the left-to-right maxima. Thus the standard representation defines a bijection on permutations.

maximum = foldr1 max
minimum = foldr1 min
partition p xs = foldr (select p) ([],[]) xs
select p x (ts,fs) | p x       = (x:ts,fs)
                   | otherwise = (ts, x:fs)
sort = \case
  [] -> []
  x:xt -> sort a ++ [x] ++ sort b where (a, b) = partition (<= x) xt

sortOn f xs = snd <$> sort (zip (f <$> xs) xs)

standardize :: Ord a => [[a]] -> [a]
standardize = concatMap (\c -> uncurry (flip (++)) $ span (< maximum c) c) .
  sortOn maximum

cyclesFromStandard = \case
  [] -> []
  x:xs -> (x:a) : cyclesFromStandard b where (a, b) = span (< x) xs

standardize $ cycles [4,2,7,1,3,6,5]  -- Expect [2,4,1,6,7,5,3]

Given a permutation \(\pi\), let \(c_i(\pi)\) be the number of cycles of length \(i\). Then the type of a permutation \(\pi\) is the sequence \( (c_1(\pi),…​,c_n(\pi)) \), and the total number of cycles of \(\pi\) is the sum of this sequence and denoted \(c(\pi)\).

cyclesOfLength xs k = length $ filter (== k) $ length <$> cycles xs

permType xs = cyclesOfLength xs <$> [1..length xs]

numCycles = length . cycles

Exercise: Show the number of permutations of type \((c_1,…​,c_n)\) is:

\[ \frac{n!}{1^{c_1}c_1!...n^{c_n}c_n!} \]

Solution: Suppose we write \([1..n]\) in every possible order, and for each such ordering, from left to right we divide the elements into \(c_1\) cycles of length 1, then \(c_2\) cycles of length 2, and so on up to \(c_n\) cycles of length \(n\). We have produced all permutations of the desired type, but we have counted each permutation multiple times because we can write a cycle of length \(k\) in \(k\) different ways and for each \(i\) we can permute \(c_i\) cycles amongst themselves without changing the product.

Following Knuth (who credits Jovan Karamata), we write:

\[{n \brack k}\]

for the number of permutations of \([1..n]\) with exactly \(k\) cycles. (Stanley writes \(c(n, k)\).) Thanks to standard representations, this is also the number of permutations of \([1..n]\) with \(k\) left-to-right maxima. These are known as the signless Stirling numbers of the first kind.

The following enumerates them all:

kCyclePerms :: Int -> Int -> [[[Int]]]
kCyclePerms 0 0 = [[]]
kCyclePerms _ 0 = []
kCyclePerms 0 _ = []
kCyclePerms n k = concatMap grows (kCyclePerms (n-1) k)
  ++ map ([n]:) (kCyclePerms (n-1) (k-1))
  where
  grows = \case
    [] -> []
    xs:xst -> (:xst) <$> ins n xs <|> (xs:) <$> grows xst
  ins n = \case
    [] -> []
    x:xt -> (x:n:xt) : ((x:) <$> ins n xt)

putStrLn "Permutations of [1..4] consisting of 2 cycles:"
kCyclePerms 4 2

The code implies the recursion:

\[ {n \brack k} = (n-1) {n-1 \brack k} + {n-1 \brack k-1} \]

stirling1 0 0 = 1
stirling1 0 k = 0
stirling1 n 0 = 0
stirling1 n k = (n - 1) * stirling1 (n - 1) k + stirling1 (n - 1) (k - 1)

putStr $ unlines $ take 8
  [unwords [show $ stirling1 n k | k <- [0..n]] | n <- [0..]]

We can prove:

\[ \sum_{k=0}^n {n\brack k} x^k = x^\overline{n} = x(x+1)...(x+n-1) \]

by showing the coefficients satisfy the same recursive relations.

Another proof: the coefficient of \(x^k\) on the right-hand side is:

\[ \sum_{1\le a_1,…​,a_{n-k}\le n-1} a_1 …​ a_{n-k} \]

which can viewed as the count of tuples \((S, f)\) where \(S\) is a \((n-k)\)-subset of \([1..n-1]\) and \(f : S → [1..n - 1]\) is a function satisfying \(f(i) \le i\).

We show each such tuple corresponds to a \(k\)-cycle permutation, which can be written as a permutation of \([1..n]\) with \(k\) left-to-right maxima.

Subtract each element of \(S\) from \(n\) to get \(b_1 > …​ > b_{n-k}\). The \(k\) remaining elements of \([1..n\) are our left-to-right maxima, so we start by writing them in ascending order. Then we insert the \(b_i\) values so that exactly \(f(b_i)\) numbers larger than \(b_i\) appear to the left of \(b_i\).

permFromSF n s fs = foldl ins ([1..n] \\ bs) $ zip bs fs where
  bs = (n -) <$> s
  ins xs (b, f) = uncurry (++) $ second (b:) $ splitBigsN f xs where
    splitBigsN 0 xs     = ([], xs)
    splitBigsN r (x:xt) = first (x:) $ splitBigsN (r - fromEnum (x > b)) xt

permFromSF 9 [1,3,4,6,8] [1,2,1,3,6]  -- Expect (2)(4)(753)(9168)

Yet another proof: we show the polynomials in question agree for all positive integers \(x\). For such an \(x\), the left-hand side is the number of \(k\)-cycle permutations multiplied by the number of functions from \(k\) objects to \([1..x]\), while the right-hand side is the number of integer sequences \((a_1,…​,a_n)\) satisfying \(0 \le a_i \le x - 1 + n - i\). (The \(a_i\) are "backwards" and zero-indexed for historical reasons; our code will undo this.)

We can produce a permutation and function from such a sequence as follows:

permFunctionFromSequence n x as = foldl f ([], []) $ zip bs [n, n-1..] where
  f (ps, fs) (b, n) | b <= x    = ([n]:ps, b:fs)
                    | otherwise = (ins n (b - x) ps, fs)
  bs = (+1) <$> reverse as
  ins n k (p:ps) | length p >= k = (p0 ++ n:p1) : ps
                 | otherwise     = p : ins n (k - length p) ps
                 where (p0, p1) = splitAt k p

Setting \(x = 1\) shows for all positive integers \(n, k\), the number of sequences \((a_1, …​, a_n)\) with \(0 \le a_i \le n - i\) containing exactly \(k\) zeroes is \({n\brack k}\). Or in code:

prop_137 n k =
  stirling1 n k == length (filter ((k ==) . length . filter (0 ==)) $
    sequence $ enumFromTo 0 . (n -) <$> [1..n])

We can build a permutation \(\pi\) in word form more directly from such a sequence:

permFromInversionTable n as = foldl f [] $ zip (reverse as) [n,n-1..] where
  f p (a, n) = p0 ++ n:p1 where (p0, p1) = splitAt a p

In the sequence, \(a_i\) is the number of entries \(j\) in \(\pi\) to the left of \(i\) such that \(j > i\). As hinted by our function name, if \(\pi = b_1 …​ b_n\) then \((b_i, b_j)\) is an inversion if \(i < j\) and \(b_i > b_j\), and the sequence \(a_i\) is called the inversion table of the permutation \(\pi\), and written \(I(\pi)\). An inversion table is another way to represent a permutation.

inversionTable p = (\a -> length $ filter (> a) $ takeWhile (/= a) p) <$> p

If \(i(\pi)\) denotes the number of inversions in the permutation \(\pi\), we see:

\[ \sum_{\pi\in S_n} q^{i(\pi)} = (1 + q)(1 + q + q^2)…​(1 + q + …​ + q^{n-1}) \]

The descent set \(D(\pi)\) of a permutation \(\pi = a_1 …​ a_n\) is defined by:

descentSet p = map fst $ filter snd $ zip [1..] $ zipWith (>) p $ tail p
-- List comprehension variant.
descentSet' p = [i | i <- [1..length p - 1], p!!(i-1) > p!!i]

For a subset \(S\) of \([1..n-1]\), define \(\alpha(S)\) and \(\beta(S)\) as follows:

alpha n s = length [p | p <- perms [1..n], descentSet p `isSubsequenceOf` s]
beta n s  = length [p | p <- perms [1..n], descentSet p == s]

Thus an alternative definition of \(\alpha(S)\) is:

alpha' n s = sum $ beta n <$> powerset s

If we write \(S\) as an increasing sequence \(s_1, …​, s_k\), then \[ \alpha(S) = {n \choose {s_1, s_2 - s_1, …​, n - s_k}} \]

For a permutation \(\pi\), write \(des(\pi)\) for the number of descents \(|D(\pi)|\). Then

\[ A_n(x) = \sum_{\pi\in S_n} x^{1+des(\pi)} \]

is an Eulerian polynomial. We write \(A(n, k)\) for the coefficient of \(x^k\) in \(A_n(x)\), and we call it an Eulerian number. Hence:

eulerian n k = length $ filter (((k - 1) ==) . length . descentSet) $ perms [1..n]

eulerianPolys = [[eulerian n k | k <- [1..n]] | n <- [1..]]

The definition of the standard representation of a permutation implies:

\[ n - des(\hat\pi) = \#\{i \in [1..n] | \pi(i) \ge i\} \]

A number \(i\) is called a weak excedance of \(\pi\) if \(\pi(i)\ge i\), and an excedance if \(\pi(i) > i\):

weakExcedances  p = map fst $ filter (uncurry (>=)) $ zip p [1..]
excedances      p = map fst $ filter (uncurry (>))  $ zip p [1..]

From:

prop_weakVsStrictExcedances p =
  length (weakExcedances p) == n - length (excedances $ (n + 1 -) <$> reverse p)
  where n = length p
prop_descentCounts p = null p ||
  length (descentSet p) == n - 1 - length (descentSet $ reverse p)
  where n = length p

it follows that the Eulerian number \(A(n, k+1)\) is equal to the number of permutations of \(S_n\) with \(k\) excedances and also the number of permutations of \(S_n\) with \(k+1\) weak excedances.

The sum of the descent set of a permutation \(\pi\) is called the greater index and is denoted \(\iota(\pi)\). It is also called the major index and denoted \(maj(\pi)\). It turns out:

prop_remarkable n =
  (sort $ sum . inversionTable <$> perms [1..n]) ==
  (sort $ sum . descentSet     <$> perms [1..n])

Trees

A binary tree of numbers has the heap property if each child node exceeds its parent node. We represent a permutation as a heap as follows:

data Tree a = Node a [Tree a] deriving Show

heap [] = Node Nothing  []
heap p  = Node (Just m) [heap p0, heap p1]
  where (p0, m:p1) = span (/= minimum p) p

Define an element \(w_i\) of a word \(w_1…​w_n\) to be

a double rise or double ascent

if \(w_{i-1} < w_i < w_{i+1} \)

a double fall or double descent

if \(w_{i-1} > w_i > w_{i+1} \)

a peak

if \(w_{i-1} < w_i > w_{i+1} \)

a valley

if \(w_{i-1} > w_i < w_{i+1} \)

where we set the sentinels \(w_0 = w_{n+1} = 0\). These four classes of terrain determine whether a node has no children, a left child, a right child, or both.

From this bijection, we see:

  • The number of binary trees with nodes \([1..n]\) with the heap property is \(n!\).

  • The number of such trees with \(k\) nodes with left children is \(A(n, k + 1).\)

  • The number of such trees with \(k\) leaf nodes is equal to the number of such trees with \(k\) nodes with two children.

  • The number of such trees that are full (that is, each node has zero or two children) with \(2n + 1\) nodes is equal to the number of alternating permutations of \(S_{2n+1}\):

    \[ a_1 > a_2 < a_3 > …​ < a_{2n+1} \]

We can also represent a permutation \(\pi\) as an unoriented heap. We prepend the sentinel value of 0 to our permutation, and start with root node of 0. Then the children of the node \(n\) are all nodes \(a\) that appear to the right of \(n\) in \(\pi\) satisfying \(n < a\) and \(b > a\) for all numbers \(b\) that appear between \(n\) and \(a\) in \(\pi\).

unorientedHeap p = f (0:p) 0 where
  f p n = Node n $ f p <$>
    filter (\a -> n < a && head (dropWhile (> a) q) == a) q
      where q = tail $ dropWhile (/= n) p

Thus:

  • The number of unoriented heaps with \(n+1\) nodes is \(n!\).

  • The number of such trees where the root has \(k\) children is \({n\brack k}\).

  • The number of such trees with \(k\) leaf nodes is \(A(n, k)\).

Partitions

A partition of a natural number \(n\) is a non-increasing sequence of integers that sums to \(n\). We ignore terminating 0s when deciding equality of partitions. A partition is like a composition, except the order of the summands is unimportant.

Write \(p(n)\) for the number of partitions of \(n\), \(p_k(n)\) for the number of partitions of \(n\) into \(k\) parts, and \(p(j, k, n)\) for the number of partitions of \(n\) into \(k\) parts with largest part at most \(j\).

We can generate all partitions with:

intPartitions 0 0 = [[]]
intPartitions n k
  | k <= 0 || n <= 0 = []
  | otherwise = ((++ [1]) <$> intPartitions (n - 1) (k - 1)) ++
    (map (+1) <$> intPartitions (n - k) k)

intPartitions 7 4

which gives the recurrence:

\[ p_k(n) = p_{k-1}(n - 1) + p_k(n - k) \]

-- Awkward name to avoid defining `p` at top-level.
pPar 0 0 = 1
pPar n k
  | k <= 0 || n <= 0 = 0
  | otherwise       = pPar (n - 1) (k - 1) + pPar (n - k) k

The following code produces the Ferrers diagram or Ferrers graph of a partition:

ferrers = unlines . map (`replicate` '*')

If we had drawn squares instead, then we would have a Young diagram.

A partition of a finite set \(N\) is a set of disjoint subsets \(\{B_1,…​,B_k\}\) of \(N\) whose union is \(N\). A subset \(B_i\) is called a block.

Roughly speaking, partitions of a set are like partitions of an integer, except the elements of a set are distinguishable. We can generate them with:

partitions [] 0 = [[]]
partitions [] _ = []
partitions _  0 = []
partitions (x:xs) k =
  (ins x =<< partitions xs k) ++ (([x]:) <$> partitions xs (k - 1))
  where
  ins x = \case  -- Insert x in each block.
    [] -> []
    ys:yss -> ((x:ys):yss) : map (ys:) (ins x yss)

partitions "abcd" 2

Define \({n \brace k}\) to be the number of ways to partition \(N\) into \(k\) blocks. These are called the Stirling numbers of the second kind. (Again, we adopt Knuth’s recommendation instead of Stanley’s \(S(n, k)\).)

Our partitions function implies:

stirling2 0 0 = 1
stirling2 _ 0 = 0
stirling2 0 _ = 0
stirling2 n k = k * stirling2 (n - 1) k + stirling2 (n - 1) (k - 1)

stirling2 7 4

By induction:

\[ x^n = \sum_k {n\brace k} x^\underline{k} \]

The total number of partitions of an \(n\)-set is called a Bell number and is denoted \(B(n)\).

bell n = sum $ stirling2 n <$> [0..n]

bell <$> [0..10]  -- [1,1,2,5,15,52,203,877,4140,21147,115975]

To recap:

\[ {0 \choose 0} = {0 \brack 0} = {0 \brace 0} = 1 \]

For \(k > 0\): \[ {0 \choose k} = {0 \brack k} = {0 \brace k} = 0 \]

For \(n > 0\): \[ {n \choose 0} = 1 \]

\[ {n \brack 0} = {n \brace 0} = 0 \]

For \(n, k > 0\):

\[ \begin{align} {n \choose k} &=& &{n-1 \choose k} + {n-1 \choose k-1} \\ {n \brack k} &=& (n-1) &{n-1 \brack k} + {n-1 \brack k-1} \\ {n \brace k} &=& k &{n-1 \brace k} + {n-1 \brace k-1} \end{align} \]

Summations: \[ \begin{align} \sum_k {n \choose k} &= 2^n \\ \sum_k {n \brack k} &= n! \\ \sum_k {n \brace k} &= B(n) \end{align} \]

The Twelvefold Way

Two functions \(f, g : N \to X\) are equivalent with \(N\) indistinguishable if there exists a bijection \(\pi : N \to N\) such that \(f \circ \pi = g\).

They are equivalent with \(X\) indistinguishable if there exists a bijection \(\sigma : X \to X\) such that \(\sigma \circ f = g\).

They are equivalent with \(N\) and \(X\) indistinguishable if there exists bijections \(\pi : N \to N\) and \(\sigma : X \to X\) such that \(\sigma \circ f \circ \pi = g\).

Given \(n, x\) and two booleans representing the distinguishability of an \(n\)-set \(N\) and and \(x\)-set \(X\), the following function returns a list containing the number of functions, injections, and surjections from \(N\) to \(X\).

numFunctions n x nDist xDist = case (nDist, xDist) of
  (True , True)  -> [x^n, product [x-n+1..x], product [1..x] * s n x]
  (False, True)  -> [multichoose x n, choose x n, multichoose x $ n - x]
  (True , False) -> [sum $ s n <$> [1..x], fromEnum $ n <= x, s n x]
  (False, False) -> [sum $ p n <$> [1..x], fromEnum $ n <= x, p n x]
  where
    p = pPar
    s = stirling2

Ben Lynn blynn@cs.stanford.edu 💡