mathematicskit.combinatorics#
Permutation/combination counting via scipy.special.perm/comb
(sequence generation via itertools); binomial/multinomial
coefficients, with a hand-rolled Pascal’s-triangle build kept as a
pedagogical illustration; integer partitions and Young/Ferrers diagrams;
the inclusion-exclusion principle and derangements; Stirling numbers,
Catalan numbers, and Bell numbers.
mathematicskit.combinatorics: combinatorial mathematics.
Permutation and combination counting via scipy.special.perm/comb
(sequence generation, which scipy doesn’t provide, is mathematicskit’s own
thin wrapper around itertools); binomial and multinomial
coefficients via scipy.special.comb, with a hand-rolled Pascal’s-
triangle build kept only as a pedagogical illustration of the
recurrence; integer partitions (the partition function via Euler’s
pentagonal-number recurrence, and full enumeration) and Young/Ferrers
diagrams; the inclusion-exclusion principle and the classic derangement-
counting application; and Stirling numbers (first and second kind),
Catalan numbers, and Bell numbers with their combinatorial
interpretations; Fibonacci and Bernoulli numbers, power sums, and Gray
codes; Latin squares; Prüfer codes for labeled trees; Ramsey’s
R(3,3) and Erdős-Szekeres monotone subsequences; Hall’s marriage
theorem; and Pólya necklace counting – all hand-rolled, with no
scipy/numpy equivalent.
- class mathematicskit.combinatorics.HallResult(satisfied, violating_subset=frozenset({}), matching=<factory>)[source]#
Bases:
objectContainer for a Hall’s-condition check on a bipartite graph.
- class mathematicskit.combinatorics.YoungDiagram(parts=<factory>)[source]#
Bases:
objectA Young diagram for an integer partition \(\lambda = (\lambda_1 \geq \lambda_2 \geq \dots)\).
Rows are left-justified, of non-increasing length – the standard combinatorial picture of a partition, e.g. \(\lambda=(4,2,1)\) of 7 is drawn as three rows of 4, 2, and 1 boxes. See Andrews & Eriksson, Integer Partitions, 2nd ed., Ch. 1.
- Parameters:
parts (
list) – Non-increasing positive integers summing to the partitioned number.
Examples
>>> diagram = YoungDiagram([4, 2, 1]) >>> print(diagram.ferrers_diagram()) **** ** * >>> diagram.conjugate().parts [3, 2, 1, 1]
- conjugate()[source]#
The conjugate (transpose) partition: reflect the diagram across its main diagonal.
\(\lambda'_k\) = the number of parts of \(\lambda\) that are \(\geq k\).
- Return type:
- Returns:
YoungDiagram
- mathematicskit.combinatorics.are_orthogonal(a, b)[source]#
Whether two Latin squares are orthogonal: superimposed, every ordered pair of symbols appears exactly once.
- Parameters:
- Return type:
- Returns:
bool
Examples
>>> are_orthogonal(cyclic_latin_square(3, 1), cyclic_latin_square(3, 2)) True
- mathematicskit.combinatorics.bell_number(n)[source]#
The
n-th Bell number: the total number of ways to partitionnlabeled elements into any number of non-empty unlabeled blocks.\(B_n = \sum_{k=0}^{n} \left\{{n \atop k}\right\}\), summing
stirling_second_kind()over every possible number of blocks. See Graham, Knuth & Patashnik, Concrete Mathematics, 2nd ed., Sec. 6.1, exercise 6.5.Examples
>>> [bell_number(n) for n in range(6)] [1, 1, 2, 5, 15, 52]
- mathematicskit.combinatorics.bernoulli_numbers(n)[source]#
The Bernoulli numbers \(B_0, \dots, B_n\) as exact fractions, with \(B_1 = +\tfrac12\).
From the recurrence \(\sum_{j=0}^{m} \binom{m+1}{j} B_j = m+1\), which fixes the convention \(B_1 = +\tfrac12\) used by Jacob Bernoulli in Ars Conjectandi (1713).
Examples
>>> [str(b) for b in bernoulli_numbers(8)] ['1', '1/2', '1/6', '0', '-1/30', '0', '1/42', '0', '-1/30']
- mathematicskit.combinatorics.catalan_number(n)[source]#
The
n-th Catalan number, via its defining recurrence.\(C_0 = 1\), \(C_{n+1} = \sum_{i=0}^{n} C_i C_{n-i}\) – counts, among many equivalent combinatorial objects: balanced strings of
npairs of parentheses, binary trees withninternal nodes, triangulations of a convex \((n+2)\)-gon, and monotonic lattice paths from \((0,0)\) to \((n,n)\) that never cross above the diagonal (Dyck paths). Equals the closed form \(\binom{2n}{n}/(n+1)\), cross-checked in this module’s tests. See Graham, Knuth & Patashnik, Concrete Mathematics, 2nd ed., Sec. 7.5.Examples
>>> [catalan_number(n) for n in range(6)] [1, 1, 2, 5, 14, 42]
- mathematicskit.combinatorics.combinations_count(n, k)[source]#
Number of ways to choose
kofndistinct items, unordered: \(\binom{n}{k}\).Via
scipy.special.comb(). See Graham, Knuth & Patashnik, Concrete Mathematics, 2nd ed., Sec. 5.1.Examples
>>> combinations_count(5, 2) 10 >>> combinations_count(52, 5) # 5-card poker hands 2598960
- mathematicskit.combinatorics.count_bracelets(n, k)[source]#
Number of bracelets of
nbeads inkcolors, up to rotation and reflection.Examples
>>> [count_bracelets(n, 2) for n in range(1, 9)] [2, 3, 4, 6, 8, 13, 18, 30]
- mathematicskit.combinatorics.count_labeled_trees(n)[source]#
Cayley’s formula: \(n^{n-2}\) labeled trees on
nvertices.Examples
>>> [count_labeled_trees(n) for n in range(1, 7)] [1, 1, 3, 16, 125, 1296]
- mathematicskit.combinatorics.count_necklaces(n, k)[source]#
Number of necklaces of
nbeads inkcolors, up to rotation.- Parameters:
- Return type:
- Returns:
int
Examples
>>> [count_necklaces(n, 2) for n in range(1, 9)] [2, 3, 4, 6, 8, 14, 20, 36]
- mathematicskit.combinatorics.count_triangle_free_colorings(n)[source]#
The number of red/blue edge colorings of \(K_n\) with no monochromatic triangle.
Exhaustive over all \(2^{\binom{n}{2}}\) colorings, so feasible only for \(n \le 6\). It is zero for \(n = 6\), which proves \(R(3,3) \le 6\): among any six people, three are mutual friends or three are mutual strangers.
Examples
>>> count_triangle_free_colorings(5), count_triangle_free_colorings(6) (12, 0)
- mathematicskit.combinatorics.cyclic_latin_square(n, multiplier=1)[source]#
The Latin square \(L_{ij} = (a\,i + j) \bmod n\) for a multiplier \(a\) coprime to
n.- Parameters:
- Return type:
- Returns:
ndarray, shape (n, n), int
Examples
>>> cyclic_latin_square(3) array([[0, 1, 2], [1, 2, 0], [2, 0, 1]])
- mathematicskit.combinatorics.derangement_count(n)[source]#
Number of derangements \(D_n\): permutations of
nitems with no fixed points.The classic inclusion-exclusion application (the “hat-check problem”): starting from all \(n!\) permutations, subtract those fixing each point, add back those fixing each pair (double- subtracted), etc., giving \(D_n = n!\sum_{k=0}^n \dfrac{(-1)^k}{k!}\). See Graham, Knuth & Patashnik, Concrete Mathematics, 2nd ed., Sec. 8.3, and Cormen et al., Introduction to Algorithms, 3rd ed., Ch. C.4.
Examples
>>> derangement_count(0) 1 >>> derangement_count(1) 0 >>> derangement_count(4) 9 >>> import itertools >>> brute_force = sum(1 for p in itertools.permutations(range(4)) if all(p[i] != i for i in range(4))) >>> derangement_count(4) == brute_force True
- mathematicskit.combinatorics.domino_tilings(n)[source]#
Number of ways to tile a \(2 \times n\) strip with \(1 \times 2\) dominoes, \(F_{n+1}\).
Equivalently, the number of ways to write \(n\) as an ordered sum of 1s and 2s – the counting problem behind the Sanskrit prosodists’ study of long and short syllables, centuries before Fibonacci.
Examples
>>> domino_tilings(4) # 1111, 112, 121, 211, 22 5
- mathematicskit.combinatorics.fibonacci(n)[source]#
The Fibonacci number \(F_n\), with \(F_0 = 0\), \(F_1 = 1\), \(F_{n} = F_{n-1} + F_{n-2}\).
Examples
>>> [fibonacci(n) for n in range(10)] [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
- mathematicskit.combinatorics.generate_combinations(items, r)[source]#
Generate every
r-combination of items, in lexicographic order.Thin wrapper around
itertools.combinations().Examples
>>> generate_combinations([1, 2, 3, 4], r=2) [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
- mathematicskit.combinatorics.generate_permutations(items, r=None)[source]#
Generate every
r-permutation of items, in lexicographic order of position.Thin wrapper around
itertools.permutations()(whichscipy.special.permdoesn’t provide – it only counts).- Parameters:
- Return type:
- Returns:
list of tuple
Examples
>>> generate_permutations([1, 2, 3], r=2) [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
- mathematicskit.combinatorics.gray_code(n)[source]#
The reflected binary Gray code on
nbits, as integers \(g_k = k \oplus (k \gg 1)\).Consecutive codes differ in exactly one bit, including the wrap from the last code back to the first. Frank Gray’s 1953 patent used the code in analog-to-digital converters. See Knuth, The Art of Computer Programming, Vol. 4A, Sec. 7.2.1.1.
Examples
>>> [format(g, "03b") for g in gray_code(3)] ['000', '001', '011', '010', '110', '111', '101', '100']
- mathematicskit.combinatorics.hall_condition(adjacency)[source]#
Check Hall’s condition \(|N(S)| \ge |S|\) for every subset \(S\) of the left side.
Checks all \(2^n\) subsets, so it is meant for small illustrations. Hall’s theorem says the condition holds exactly when
maximum_matching()covers every left vertex; this function returns both, so the two can be compared.- Parameters:
adjacency (
dict) –{left_vertex: iterable of right vertices}.- Return type:
- Returns:
HallResult
Examples
>>> hall_condition({"a": [1], "b": [1], "c": [2]}).violating_subset == frozenset({"a", "b"}) True
- mathematicskit.combinatorics.has_monochromatic_triangle(n, coloring)[source]#
Whether a 2-coloring of the edges of \(K_n\) contains a triangle with all three edges the same color.
- Parameters:
- Return type:
- Returns:
bool
Examples
>>> pentagon = {(i, j): int((j - i) % 5 in (1, 4)) for i, j in combinations(range(5), 2)} >>> has_monochromatic_triangle(5, pentagon) # K_5 can avoid them: R(3,3) > 5 False
- mathematicskit.combinatorics.integer_partitions(n)[source]#
Enumerate every integer partition of
n, each as a non-increasing list of parts.Generated recursively: every partition of
nwith largest part \(\leq m\) either has largest part exactlym(prependmto a partition ofn-mwith parts \(\leq m\)) or largest part \(< m\) (a partition ofnwith parts \(\leq m-1\)). See Andrews & Eriksson, Integer Partitions, 2nd ed., Ch. 1.- Parameters:
n (
int) –n >= 0.- Return type:
- Returns:
list of list of int – In descending-largest-part order;
len(...) == partition_function(n).
Examples
>>> integer_partitions(4) [[4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]] >>> len(integer_partitions(10)) == partition_function(10) True
- mathematicskit.combinatorics.is_latin_square(square)[source]#
Whether every row and every column of an \(n \times n\) array is a permutation of
0..n-1.Examples
>>> is_latin_square([[0, 1], [1, 0]]), is_latin_square([[0, 1], [0, 1]]) (True, False)
- mathematicskit.combinatorics.longest_decreasing_subsequence(sequence)[source]#
A longest strictly decreasing subsequence; see
longest_increasing_subsequence().- Parameters:
sequence (sequence of numbers)
- Return type:
- Returns:
list
Examples
>>> longest_decreasing_subsequence([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]) [9, 6, 5, 3]
- mathematicskit.combinatorics.longest_increasing_subsequence(sequence)[source]#
A longest strictly increasing subsequence, by patience sorting in \(O(n \log n)\).
The Erdős-Szekeres theorem guarantees that any sequence of \((r-1)(s-1)+1\) distinct numbers has an increasing subsequence of length \(r\) or a decreasing one of length \(s\).
- Parameters:
sequence (sequence of comparable values)
- Return type:
- Returns:
list
Examples
>>> longest_increasing_subsequence([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]) [1, 2, 3, 5]
- mathematicskit.combinatorics.maximum_matching(adjacency)[source]#
A maximum matching of a bipartite graph, by repeated augmenting-path search.
- Parameters:
adjacency (
dict) –{left_vertex: iterable of right vertices}.- Return type:
- Returns:
dict –
{left: right}for every matched left vertex.
Examples
>>> sorted(maximum_matching({"a": [1, 2], "b": [1], "c": [2, 3]}).items()) [('a', 2), ('b', 1), ('c', 3)]
- mathematicskit.combinatorics.multinomial_coefficient(n, ks)[source]#
Multinomial coefficient \(\dbinom{n}{k_1,\dots,k_m} = \dfrac{n!}{k_1!\cdots k_m!}\),
sum(ks) = n.Computed as a product of ordinary binomial coefficients (each via
scipy.special.comb()): choose \(k_1\) ofn, then \(k_2\) of the remaining \(n-k_1\), and so on – equivalent to the factorial-ratio definition but built entirely from library calls. See Graham, Knuth & Patashnik, Concrete Mathematics, 2nd ed., Sec. 5.5.Examples
>>> multinomial_coefficient(10, [2, 3, 5]) 2520
- mathematicskit.combinatorics.orthogonal_latin_square_pair(n)[source]#
A pair of orthogonal Latin squares of odd order
n: \(L_{ij} = i + j\) and \(M_{ij} = 2i + j \pmod n\).Euler conjectured in 1782 that no pair exists when \(n \equiv 2 \pmod 4\). Gaston Tarry confirmed it for \(n = 6\) in 1900, but R. C. Bose, S. S. Shrikhande, and E. T. Parker disproved the conjecture for every other such \(n > 6\) in 1959-1960. This construction covers odd \(n\), where 2 is invertible modulo \(n\).
- Parameters:
n (
int) – Odd,n >= 3.- Returns:
tuple of ndarray
Examples
>>> a, b = orthogonal_latin_square_pair(5) >>> are_orthogonal(a, b) True
- mathematicskit.combinatorics.partition_function(n)[source]#
The partition function \(p(n)\): the number of ways to write
nas a sum of positive integers (order irrelevant).Computed via Euler’s pentagonal number theorem recurrence,
\[p(n) = \sum_{k \geq 1} (-1)^{k+1}\left[p\!\left(n - \tfrac{k(3k-1)}{2}\right) + p\!\left(n - \tfrac{k(3k+1)}{2}\right)\right]\]which is far faster than enumerating every partition (see
integer_partitions()) just to count them. See Hardy & Wright, An Introduction to the Theory of Numbers, 6th ed., Sec. 19.10-19.11.Examples
>>> partition_function(0) 1 >>> partition_function(5) 7 >>> partition_function(10) 42
- mathematicskit.combinatorics.pascals_triangle(n_rows)[source]#
Build the first n_rows rows of Pascal’s triangle via the addition recurrence.
Row
n(0-indexed) holds \(\binom{n}{0}, \dots, \binom{n}{n}\), each computed as the sum of the two entries above it in the previous row (with implicit zeros outside the triangle) – never callingcombinations_count()at all. See Graham, Knuth & Patashnik, Concrete Mathematics, 2nd ed., Sec. 5.1.Examples
>>> pascals_triangle(5) [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]
- mathematicskit.combinatorics.permutations_count(n, k=None)[source]#
Number of ways to arrange
kofndistinct items in order: \(P(n,k) = n!/(n-k)!\).Via
scipy.special.perm().k=None(the default) counts full permutations, \(P(n,n) = n!\). See Graham, Knuth & Patashnik, Concrete Mathematics, 2nd ed., Sec. 5.1.Examples
>>> permutations_count(5, 2) # 5*4 20 >>> permutations_count(5) # 5! 120
- mathematicskit.combinatorics.prufer_decode(sequence)[source]#
The labeled tree with the given Prüfer sequence, as a sorted edge list.
Inverts
prufer_encode(): a sequence of length \(n - 2\) over0..n-1determines a unique tree onnvertices.Examples
>>> prufer_decode([3, 3, 3]) [(0, 3), (1, 3), (2, 3), (3, 4)]
- mathematicskit.combinatorics.prufer_encode(edges, n)[source]#
The Prüfer sequence of a labeled tree on vertices
0..n-1.Repeatedly removes the smallest-labeled leaf and records its neighbor, until two vertices remain. The result has length \(n - 2\).
- Parameters:
- Return type:
- Returns:
list of int
Examples
>>> prufer_encode([(0, 3), (1, 3), (2, 3), (3, 4)], 5) # a star around 3, plus 4 [3, 3, 3]
- mathematicskit.combinatorics.stirling_first_kind(n, k, signed=False)[source]#
Stirling numbers of the first kind: permutations of
nelements with exactlykcycles.Unsigned, \(\left[{n \atop k}\right]\), via the recurrence \(\left[{n \atop k}\right] = (n-1)\left[{n-1 \atop k}\right] + \left[{n-1 \atop k-1}\right]\) (inserting element
neither into an existing cycle in \((n-1)\) ways, or as its own new cycle).signed=Truereturns the signed version \(s(n,k) = (-1)^{n-k}\left[{n \atop k}\right]\) (the coefficients of \(x(x-1)\cdots(x-n+1)\) in powers ofx). See Graham, Knuth & Patashnik, Concrete Mathematics, 2nd ed., Sec. 6.1.Examples
>>> stirling_first_kind(4, 2) # permutations of 4 elements with exactly 2 cycles 11 >>> sum(stirling_first_kind(4, k) for k in range(5)) == 24 # sum over k = n! True
- mathematicskit.combinatorics.stirling_second_kind(n, k)[source]#
Stirling numbers of the second kind: ways to partition
nlabeled elements into exactlyknon-empty unlabeled blocks.\(\left\{{n \atop k}\right\}\), via the recurrence \(\left\{{n \atop k}\right\} = k\left\{{n-1 \atop k}\right\} + \left\{{n-1 \atop k-1}\right\}\) (element
njoins one of thekexisting blocks, or starts a new one). See Graham, Knuth & Patashnik, Concrete Mathematics, 2nd ed., Sec. 6.1.Examples
>>> stirling_second_kind(4, 2) # ways to split 4 labeled items into 2 non-empty groups 7 >>> stirling_second_kind(5, 1) # only one way: everything in one block 1 >>> stirling_second_kind(5, 5) # only one way: every element its own block 1
- mathematicskit.combinatorics.sum_of_powers(n, p)[source]#
The power sum \(1^p + 2^p + \cdots + n^p\), by Faulhaber’s formula.
Jacob Bernoulli’s formula expresses the sum as a polynomial in \(n\) whose coefficients involve the Bernoulli numbers:
\[\sum_{k=1}^{n} k^p = \frac{1}{p+1} \sum_{j=0}^{p} \binom{p+1}{j} B_j\, n^{p+1-j}.\]Examples
>>> sum_of_powers(1000, 10) # Bernoulli's own boast: computed "in half of a quarter of an hour" 91409924241424243424241924242500
- mathematicskit.combinatorics.union_size_inclusion_exclusion(sets)[source]#
Exact size of the union of several (possibly overlapping) sets.
\(\left|\bigcup_i A_i\right| = \sum_i |A_i| - \sum_{i<j}|A_i \cap A_j| + \sum_{i<j<k}|A_i \cap A_j \cap A_k| - \dots\), summed over every nonempty subset of the given sets, alternating sign by subset size. See Graham, Knuth & Patashnik, Concrete Mathematics, 2nd ed., Sec. 8.3, eq. (8.63).
Examples
>>> a = {1, 2, 3, 4} >>> b = {3, 4, 5, 6} >>> c = {4, 5, 6, 7} >>> union_size_inclusion_exclusion([a, b, c]) == len(a | b | c) True