Generating All Sublists of a List in Python

Linux School Tech
7 min readJul 7, 2024

--

When working with lists in Python, there are times when you need to generate all possible sublists (or subsets) of a given list. This can be useful in a variety of applications such as combinatorial problems, data analysis, and more. The concept of a power set is fundamental here; it represents all possible subsets of a set, including the empty set and the set itself.

In this guide, we will walk you through a Python solution that leverages the itertools library to generate all sublists of a list. We'll explore the code implementation, discuss its time and space complexity, and demonstrate its usage with an example.

Problem

Given a list, the challenge is to write a Python function that generates all possible sublists of this list. The solution should account for sublists of all possible lengths, including the empty list. Each sublist should preserve the order of elements as they appear in the original list.

Solution

To find all sublists (subsets) of a given list in Python, you can use the concept of the power set, which includes all possible combinations of the elements of the list, including the empty set and the list itself. Here is a step-by-step approach using Python’s itertools library.

What is combination?

In mathematics, a combination refers to a selection of items from a set where the order of selection doesn’t matter.

Here’s a breakdown of the key points:

  • Selection: You choose a subset of elements from a larger set.
  • Order doesn’t matter: Think of picking things from a bag — grabbing apple then orange is the same as picking orange then apple. Both selections end up with the same combination (fruits).

The formula for combinations is denoted by C(n, k), which represents the number of ways to choose k elements out of a set of n elements where order doesn’t matter.

Here’s how the formula works:

  • C(n, k) = n! / (k! * (n-k)!)
  • n!: This is n factorial, which is the product of all positive integers less than or equal to n (n x (n-1) x (n-2) x … x 1).
  • k!: This is k factorial, which is the product of all positive integers less than or equal to k (similar to n!).
  • (n-k)!: This is the factorial of (n minus k).

This formula might seem complex at first, but it essentially captures the logic behind counting combinations. Let’s break it down further:

  • We have n choices for the first element.
  • Once we pick the first element, there are only (n-1) choices remaining for the second element (since we can’t pick the same element twice).
  • This pattern continues, so for the k-th element, we only have (n-k+1) choices left.

However, this approach overcounts selections because it considers order. For example, if we pick element A then element B, it’s treated differently than B then A.

To account for this overcounting, we divide by k!. This essentially removes the extra ways of counting selections due to order.

The term (n-k)! comes from the number of times we’ve overcounted selections for elements beyond the first k selections.

Example:

Calculate the number of ways to choose 2 elements (k = 2) from a set of 4 elements (n = 4).

C(4, 2) = 4! / (2! * (4–2)!) = 4 x 3 / (2 x 2) = 6

Therefore, there are 6 different combinations (groups of 2) you can form by choosing 2 elements from a set of 4 elements when order doesn’t matter.

Python code

In Python’s itertools library, the combinations function is used to generate all possible combinations of a given iterable (such as a list, tuple, or string) with a specified length.

The combinations function returns an iterator that produces tuples, where each tuple is a combination of the elements from the input iterable. The length of each combination is specified by the r parameter, which stands for "length" or "replicate".

Here’s the syntax:

itertools.combinations(iterable, r)

Where:

  • iterable is the input iterable (e.g., a list, tuple, or string)
  • r is the length of each combination (an integer)

For example, if you have the list [1, 2, 3] and you call combinations([1, 2, 3], 2), it will return an iterator that produces the following combinations:

[(1, 2), (1, 3), (2, 3)]

If you call combinations([1, 2, 3], 3), it will return an iterator that produces the following combinations:

[(1, 2, 3)]

Because there’s only one way to choose three elements from a list of three elements.

You can use the list function to convert the iterator to a list if you need to:

import itertools

numbers = [1, 2, 3]
combinations = list(itertools.combinations(numbers, 2))
print(combinations) # Output: [(1, 2), (1, 3), (2, 3)]

The combinations function is useful when you need to generate all possible combinations of elements from an iterable without considering order or repetition. If you need to consider order or repetition, you may want to use other functions like permutations or product instead.

import itertools

def all_sublists(lst):
# Generate all possible sublists
sublists = []
for r in range(len(lst) + 1):
sublists.extend(itertools.combinations(lst, r))
# Convert tuples to lists
return [list(sublist) for sublist in sublists]

# Example usage
lst = [1, 2, 3]
sublists = all_sublists(lst)
print(sublists)

Function takes a list ( lst) as input and returns a list containing all possible sublists of the original list. Here’s a breakdown of what it does:

Output

This code will output:

[[], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]

This approach ensures that all sublists, including the empty list and the list itself, are included.

The output of itertools.combinations in Python is an iterator that produces tuples. Each tuple represents a combination of elements from the input iterable, with the size of each combination determined by the r parameter.

Figure 1

combinations of r=0 out of n elements

In the context of combinations, choosing 0 elements out of n elements is a unique case.

  • If you choose 0 elements, you’re essentially not selecting anything from the set.
  • Since order doesn’t matter in combinations, there’s only one way to “not select anything” regardless of how many elements (n) are in the original set.

This concept can also be reflected in the combination formula (C(n, k) = n! / (k! * (n-k)!)) where k (r in the code) represents the number of elements you choose.

  • In this case, k (r) = 0 (you’re choosing 0 elements).
  • The formula becomes C(n, 0) = n! / (0! * (n-0)!).

Dividing by 0! is mathematically undefined. However, there’s a mathematical convention that 0! is equal to 1.

Using this convention, C(n, 0) = n! / (1 * n!) which simplifies to 1. This signifies there’s only one unique combination possible when you choose 0 elements out of n elements.

Some might argue that there are technically no elements selected, so there shouldn’t be a “combination” at all. However, for consistency and to account for all possible selection sizes (from 0 to n), mathematicians use the convention of considering the empty selection as a single valid combination.

Time Complexity

The time complexity of the given code is O(n*2^n), where n is the length of the input list.

Here’s a breakdown of the time complexity:

Generating Combinations

  1. The outer loop iterates over range(len(lst) + 1), which takes O(n) time. [n+1 -> n]
  2. Inside the loop, itertools.combinations(lst, r) generates all combinations of the input list with length r. The time complexity of this function is O(n choose r), which is approximately O(2^n) when r is close to n.

Converting Tuples to Lists

  1. After generating all combinations (which are tuples), the code converts each tuple to a list.
  2. This conversion takes O(n)for each tuple, and since there are 2^ntuples, the total time is O(n*2^n).

Therefore, the overall time complexity is O(n*2^n).

In practice, this means that the code’s running time will grow extremely rapidly as the input size increases. For example, if you double the length of the input list, the running time will increase by a factor of 2¹ = 2. If you double it again, the running time will increase by a factor of 2² = 4, and so on.

To give you an idea of just how rapidly this grows, here are some approximate running times for different input sizes:

  • n = 10: 1024 combinations, ~0.01 seconds
  • n = 20: 1048576 combinations, ~1 second
  • n = 30: 1073741824 combinations, ~30 seconds
  • n = 40: 1099511627776 combinations, ~1 hour
  • n = 50: 1125899906842624 combinations, ~1 day

Keep in mind that these are rough estimates and may vary depending on your specific system and implementation.

Read More:

My YouTube Channel

More shell script videos and linux tutorials on my YouTube Channel.

--

--