liveBook · Manning

Combination

As explained in Fabulous Adventures in Data Structures and Algorithms

A combination is a subsequence or subset selected from a sequence without changing the original element order.

A combination chooses k items from a sequence of n items without changing their order. For ABCDE with k = 3, ABC, ABE, and CDE qualify; a reordering does not. Where permutations help test what happens when actions run in a different order, combinations help test what happens when some actions are skipped. section 6.3

Choose positions, then apply them

Generate combinations of the indices 0 through n−1, then use those indices with any list. For ["ham", "jam", "spam", "lamb"], the index combination [1, 2] selects ["jam", "spam"]. This separates the selection algorithm from the type of item being selected. section 6.3

Two ways to enumerate combinations expose different useful structures:

  • For a mutable, right-to-left lexicographic enumeration, start at 0, 1, …, k−1. To advance, increment the rightmost position that can increase while leaving the indices strictly increasing, then reset every position to its left to its smallest possible value. With n = 6 and k = 4, 1245 advances to 0345. Stop when no position can increase. section 6.3.1
  • For a recursive enumeration, split combinations by whether they contain n−1. First yield the k-item combinations from 0 through n−2; then append n−1 to each (k−1)-item combination from that range. An impossible choice yields nothing, while choosing zero items yields one empty combination. section 6.3.1
Listing 6.17 An elegant recursive method for generating combinations
static IEnumerable<IDeque<int>> Combinations2(int n, int k)
{
    if (k > n || k < 0)
        yield break; #1
    if (k == 0)
    {
        yield return Deque<int>.Empty; #2
        yield break;
    }
    foreach (var r in Combinations2(n - 1, k)) #3
        yield return r; 
    foreach (var r in Combinations2(n - 1, k - 1)) #4
        yield return r.PushRight(n-1);
}

The C# iterator makes the include-or-exclude split concrete; its deque result lets the recursive branch append an index without mutating an earlier result. section 6.3.1

The number of results is n C k, pronounced “n choose k.” There is one empty combination, none when k > n, and n C k = n C (n−k): choosing items corresponds to identifying those left out. The recursive split gives n C k = (n−1) C k + (n−1) C (k−1). Equivalently, n C k = n(n−1)…(n−k+1)/k!: counting selection orders overcounts each combination by the k! orders of its chosen items. section 6.3.2

Listing 6.18 Implementing the Choose operator
static class Extensions
{
    public static BigInteger Choose(this int n, int k)
    {
        if (k > n) 
            return 0; #1
        if (k > n / 2) #2
            k = n - k;
        BigInteger result = 1; #3
        for (int i = 1; i <= k; i += 1)
            result = result * (n - k + i) / i; #4
        return result;
    }
}

The Choose implementation uses symmetry to shorten its loop and BigInteger for the count, multiplying and dividing incrementally rather than forming the entire numerator first. section 6.3.2

Counting also lets you retrieve a combination directly by its position p, without enumerating every earlier result. The first (n−1) C k results omit n−1. If p lies beyond that boundary, subtract it, find the corresponding (k−1)-item combination, and append n−1. section 6.3.2

Listing 6.19 Recursively generating combinations from combination numbers
static IDeque<int> GetCombination(BigInteger p, int n, int k)
{
    if (k > n || k == 0)
        return Deque<int>.Empty;
    var boundary = (n - 1).Choose(k);
    return p < boundary ?
        GetCombination(p, n - 1, k) :
        GetCombination(p - boundary, n - 1, k - 1).PushRight(n - 1);
}

The recursive GetCombination method turns the counting boundary into a branch decision for a requested combination index. section 6.3.2

How Fabulous Adventures in Data Structures and Algorithms covers it

How deep it goes
Develops mutable and recursive enumeration, counting identities, an iterative Choose implementation, and direct retrieval by position.
Examples
Works through ABCDE with k = 3, selecting foods by index, and advancing the four-index combination 1245 to 0345.
What it assumes
Assumes familiarity with C# sequences, indexing, and recursion while defining combinations and choose notation.
What you can do after
Build it from scratch: enumerate combinations, count them with Choose, or retrieve one directly by its position.
How it connects
Connects combinations to permutations, lexicographic order, recursion, and binomial coefficients.

Also explained in

Compare how 3 books explain combination →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Fabulous Adventures in Data Structures and Algorithms. The section links point to the parts of the book it draws on.