Python Math Module – 5 Combinatorial Functions Ever Coder Ought to Know

This is the second article discussing the math module from the Python Standard Library. You can find the first about four basic numerical functions here. The articles are organized thematically; functions that are closely related to each other are discussed in the same article.

In this article, we will explore two themes: combinatorics and elementary number theory.

For our purposes, combinatorics is the study of counting the number of ways to rearrange objects. We look at three important ideas:

  • combinations,
  • permutations, and
  • factorials.

Number theory is a subfield of mathematics concerning the properties of integers and rational numbers. Much of elementary number theory studies the divisibility of integers. In this article, we explore two important concepts:

  • greatest common divisors, and
  • least common multiples.

Combinatorial Functions

The Combination Function math.comb()

math.comb(int n, int k)

The combination function (a.k.a. the binomial coefficient) gives the number of ways of choosing k objects from a collection of n distinct objects, not accounting for different rearrangements of the k objects. For more details on binomial coefficients, see this blog post.

To calculate the number of ways to choose 4 letters from the collection {a,b,c,d,e,f,g,h}, we can use:

import math
math.comb(8, 4)
# 70

As we see above, the math.comb() function accepts two nonnegative integers parameters. The first parameter is the number of objects in the collection (in the above example, the letters a through h), and the second parameter is the number of objects we choose from the collection.)

The Permutation Function math.perm()

math.perm(int n, int k=None)

A permutation of n elements is the number of ways to rearrange n distinct objects.

For instance, let’s consider the permutation of the letters {a,b,c,d,e}. We will think about putting the five objects in one row.

As a starter, let’s think of how many ways of putting the letters in the pattern edc. In the fourth spot, we can put either a or b. Let’s say we choose a. Then there is only one choice left b in the fifth spot. Thus, there are 2 ways of achieving this pattern.

Let’s try something slightly harder. How many ways can we get the pattern ed_? Well, the third spot has the three options {a,b,c}. Let’s say we choose c. Then there are 2 ways of getting edc from what we said earlier. From the same idea, there are 2 ways of getting eda and edb respectively. Therefore, there are 3*2 = 6 ways of getting the pattern ed_.

How about the pattern e? From the same argument as above, we get 4*3*2 = 24 ways.

Finally, the number of ways of rearranging all five letters is 5*4*3*2 = 120. This is the permutation of five letters.

We can implement this in Python using the following syntax:

import math
math.perm(5)
# 120

We can do a little more with the math.perm() function. Instead of arranging the letters {a,b,c,d,e} in a row of five letters, how many ways are there to arrange them in a row of three letters (in other words, the pattern _)?

We can use the same argument as before.

  • In the first spot, there are five options {a,b,c,d,e}. Let’s say we choose e.
  • In the second spot, we are left with four options {a,b,c,d}. Let’s say we choose c.
  • The last spot has three options {a,b,d}.

Therefore, there are 5*4*3 = 60 options in all.

In Python, we can implement this as:

math.perm(5,3)
# 60

To elaborate, the integer parameter n is the number of letters to arrange, and the integer k is the number of spots on the row. The default value of k is None, meaning that the number of spots on the row is set to n by default.

See also the discussion on permutations in this blog post.

The Factorial Function math.factorial()

math.factorial(int x)

The function math.factorial takes in an integer argument x, and returns its factorial x! in the mathematical sense. In other words, if x is positive, then math.factorial(x) outputs the product

x! = 1 * 2 * 3* ... * (x-1) * x*

For instance, 3! = 1 * 2 * 3 = 6. If x=0, then 0!=1*.

As of version 3.9, math.factorial does not accept negative or non-integer inputs.

The syntax for math.factorial is as follows:

import math

math.factorial(3)
# 6

math.factorial(0)
# 1

For more details on the factorial function, see this blog post.

Number Theoretical Functions

The math.gcd() Function

math.gcd(*integers)

The greatest common divisor (gcd) of a collection of integers n1,…nk is the greatest integer d dividing each of n1,…nk.

  • For instance, the gcd of 12 and 18 is 6 because their common divisors are 1,2,3, and 6, of which 6 is the largest.
  • Similarly, the gcd of 49, 84, and 168 is 7 because 1 and 7 are the only common divisors, of which 7 is the largest.

In general, when manually finding the gcd as above, it is a good idea to first look for the number with the fewest number of divisors. For instance, in the second example, the divisors of 49 are 1,7, and 49, whereas 84 has sixteen divisors. Since the gcd must be a divisor of 49, it is much easier to find it from the list of divisors of 49 than from the divisors of 84.

The syntax for the math.gcd() function is as follows:

import math

math.gcd(12,18)
# 6

math.gcd(49,84,168)
# 7

The math.gcd() function accepts as many integer arguments as desired. (The is what is meant by “*integers” in the documentation.)

When a collection of integers n1,…nk have gcd equal to 1, they are called relatively prime or coprime. (The most important case is when there are only two integers.) Relatively prime integers are generally easier to work with than numbers with common divisors. See the Wikipedia page for more discussion.

Exercise. What do you expect the outputs are for the following pieces of code?

  • a.) math.gcd(15,20),
  • b.) math.gcd(2,3,5),
  • c.) math.gcd(14,21,70),
  • d.) math.gcd(40,62,84)

To achieve a deeper understanding of elementary number theory, we will briefly mention the Euclidean algorithm, an important algorithm for computing gcd’s for a pair of numbers. (For an extensive discussion, read up on “divisibility” in any good elementary number theory or discrete mathematics textbooks. There is also an encyclopedic overview on the Wikipedia page.)

To avoid using too much mathematical notation (see the Wikipedia page or a textbook, if you are mathematically inclined), we will illustrate the algorithm using an example. Let’s take n1 = 6342 and n2 = 3816. The algorithm is a series of divisions where we only care about the remainder (and not the quotient):

First, we divide n1 by n2:

6342 = 1*3816 + 2526

Next, divide 3816 by 2526:

3816 = 1* 2526 + 1290

Next, divide 2526 by 1290:

2526 = 1*1290 + 1236

Next, divide 1290 by 1236:

1290 = 1* 1236 + 54

Next, divide 1236 by 54:

1236 = 22* 54 + 48

Next, divide 54 by 48:

54 = 1* 48 + 6

Finally, divide 48 by 6:

48 = 8* 6 + 0

The algorithm terminates because we have remainder zero. Since 6 is the last nonzero remainder, it is the gcd. (To understand why this works, please see the resources listed above.)

1236 % 54
# 48

Here is one way to implement the algorithm in Python:

def eucl_gcd(n1,n2): 
    # ensure n1 >= n2 
    if n1 < n2: 
        n1,n2 = n2,n1
    # initialize
    a,b = n1,n2
    r = a%b
    s=b
    # algorithm 
    while r > 0: 
        s = r
        a,b = b,r
        r = a%b
    # return remainder 
    return s   

print(eucl_gcd(12,18)) 
# 6

We can extend the Euclidean algorithm to calculate the gcd for three or more integers using the following useful fact:

gcd(n1,n2,n3) = gcd(n1,gcd(n2,n3))

In other words, to calculate the gcd of three numbers, we can first calculate the gcd of two of the numbers (call it d), and then calculate the gcd of d and the third number.

We can demonstrate this with some Python code:

math.gcd(14,21,70) == eucl_gcd(14,eucl_gcd(21,70))
# True

math.gcd(49,84,168) == eucl_gcd(49,eucl_gcd(84,168))
# True

The math.lcm() Function

math.lcm(*integers)

The least common multiple (lcm) of a collection of integers n1,n2,…,nk is the smallest integer divisible by each number.

  • For instance, the lcm of 12 and 18 is 36, because the first few multiples of 12 are 12, 24, 36, and 48, whereas for 18, it is 18, 36, and 54.
  • Similarly, the lcm for 4, 6, and 15 is 60 because the first few multiples of 15 are 15, 30, 45, and 60, of which the first number divisible by both 4 and 6 is 60.

In general, when manually calculating lcm by looking at the multiples of each number as we did above, it is a good idea to look at the multiples of the largest number since there will be fewer numbers to check.

The lcm of two numbers n1 and n2 is closely related to their gcd:

gcd(n1,n2)\ lcm(n1,n2) = n1*n2*

We can calculate lcm(n1,n2) using the above formula by using the Euclidean algorithm to calculate gcd(n1,n2).

The syntax for math.lcm() is as follows:

import math

math.lcm(12,18)
# 36

math.lcm(14,70,84)
# 420