ExamplesBy LevelBy TopicLearning Paths
1168 Fundamental

Generating All Subsets of a List Recursively

Functional Programming

Tutorial

The Problem

The power set of a set with n elements contains 2ⁿ subsets. Generating all subsets is fundamental to combinatorial algorithms: exhaustive search, constraint satisfaction, feature selection in machine learning, and generating test cases. The recursive insight is elegant — for each element, either include it or exclude it, then recurse on the rest. This divide-and-conquer structure maps directly onto functional recursion.

🎯 Learning Outcomes

  • • Understand the recursive structure of subset generation (include/exclude branching)
  • • Learn how to accumulate results across recursive branches in Rust
  • • See how the exponential output size (2ⁿ) relates to the linear depth of recursion
  • • Practice returning Vec<Vec<T>> from recursive functions correctly
  • Code Example

    pub fn subsets<T: Clone>(items: &[T]) -> Vec<Vec<T>> {
        match items.split_first() {
            None => vec![vec![]],
            Some((first, rest)) => {
                let without = subsets(rest);
                let with_first: Vec<Vec<T>> = without.iter().map(|s| {
                    let mut new_s = vec![first.clone()];
                    new_s.extend_from_slice(s);
                    new_s
                }).collect();
                let mut result = without;
                result.extend(with_first);
                result
            }
        }
    }

    Key Differences

  • Cloning cost: OCaml's persistent lists share tails, so x :: s is O(1); Rust must clone() each subset to add an element, making it O(k) per subset.
  • Pattern matching: OCaml's | [] -> ... | x :: rest -> is the canonical idiom; Rust uses if let Some((first, rest)) = slice.split_first().
  • Output type: OCaml returns 'a list list; Rust returns Vec<Vec<T>> with explicit Clone bound on T.
  • Accumulator style: Rust often uses extend to combine result vectors; OCaml uses @ (list append) naturally.
  • OCaml Approach

    OCaml's idiomatic solution uses pattern matching and list comprehension style:

    let rec subsets = function
      | [] -> [[]]
      | x :: rest ->
        let without = subsets rest in
        let with_x = List.map (fun s -> x :: s) without in
        without @ with_x
    

    OCaml lists are persistent and cheap to prepend, so x :: s costs O(1). The garbage collector manages all the shared list structure automatically.

    Full Source

    #![allow(dead_code)]
    //! Generating All Subsets of a List Recursively
    //! See example.ml for OCaml reference
    //!
    //! The power set of a list with n elements contains 2ⁿ subsets.
    //! The recursive insight: for each element, either include it or exclude it.
    
    /// Idiomatic Rust: generate all subsets of a slice.
    /// Mirrors OCaml's `let rec powerset = function | [] -> [[]] | x :: rest -> ...`
    ///
    /// Requires `T: Clone` because each element is copied into subset Vecs.
    pub fn subsets<T: Clone>(items: &[T]) -> Vec<Vec<T>> {
        match items.split_first() {
            None => {
                // Empty slice: one subset, the empty set.
                vec![vec![]]
            }
            Some((first, rest)) => {
                let without = subsets(rest);
                // Subsets containing `first`: prepend it to each subset from `without`.
                let with_first: Vec<Vec<T>> = without
                    .iter()
                    .map(|s| {
                        let mut new_s = vec![first.clone()];
                        new_s.extend_from_slice(s);
                        new_s
                    })
                    .collect();
                // Combine: subsets without first + subsets with first.
                let mut result = without;
                result.extend(with_first);
                result
            }
        }
    }
    
    /// Functional recursive: exactly mirrors OCaml `powerset` structure.
    /// `without @ (List.map (fun s -> x :: s) without)` becomes:
    /// `[without..., (first prepended to each)...]`
    pub fn subsets_recursive<T: Clone>(items: &[T]) -> Vec<Vec<T>> {
        if items.is_empty() {
            return vec![vec![]];
        }
        let first = &items[0];
        let rest = &items[1..];
        let ps = subsets_recursive(rest);
        // Map: prepend `first` to each existing subset.
        let with_first: Vec<Vec<T>> = ps
            .iter()
            .map(|s| {
                let mut sub = vec![first.clone()];
                sub.extend_from_slice(s);
                sub
            })
            .collect();
        let mut result = ps;
        result.extend(with_first);
        result
    }
    
    /// Generate all subsets of a given size k (combinations C(n, k)).
    pub fn subsets_of_size<T: Clone>(items: &[T], k: usize) -> Vec<Vec<T>> {
        subsets(items)
            .into_iter()
            .filter(|s| s.len() == k)
            .collect()
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_subsets_empty() {
            // The power set of {} has exactly 1 element: the empty set.
            let result = subsets::<i32>(&[]);
            assert_eq!(result, vec![vec![]]);
        }
    
        #[test]
        fn test_subsets_single_element() {
            let result = subsets(&[1_i32]);
            // {}, {1}
            assert_eq!(result.len(), 2);
            assert!(result.contains(&vec![]));
            assert!(result.contains(&vec![1]));
        }
    
        #[test]
        fn test_subsets_three_elements_count() {
            let result = subsets(&[1_i32, 2, 3]);
            // 2^3 = 8 subsets
            assert_eq!(result.len(), 8);
        }
    
        #[test]
        fn test_subsets_four_elements_count() {
            let result = subsets(&[1_i32, 2, 3, 4]);
            assert_eq!(result.len(), 16);
        }
    
        #[test]
        fn test_subsets_recursive_matches_idiomatic() {
            let items = [1_i32, 2, 3];
            let mut a = subsets(&items);
            let mut b = subsets_recursive(&items);
            // Sort to compare regardless of order.
            for s in a.iter_mut() {
                s.sort();
            }
            for s in b.iter_mut() {
                s.sort();
            }
            a.sort();
            b.sort();
            assert_eq!(a, b);
        }
    
        #[test]
        fn test_subsets_of_size_two() {
            let result = subsets_of_size(&[1_i32, 2, 3, 4], 2);
            // C(4,2) = 6 pairs.
            assert_eq!(result.len(), 6);
            for s in &result {
                assert_eq!(s.len(), 2);
            }
        }
    
        #[test]
        fn test_subsets_contains_empty_and_full() {
            let items = [1_i32, 2, 3];
            let result = subsets(&items);
            // Must contain the empty subset and the full set.
            assert!(result.contains(&vec![]));
            assert!(result.contains(&vec![1, 2, 3]));
        }
    
        #[test]
        fn test_subsets_five_elements() {
            assert_eq!(subsets(&[1_i32, 2, 3, 4, 5]).len(), 32);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_subsets_empty() {
            // The power set of {} has exactly 1 element: the empty set.
            let result = subsets::<i32>(&[]);
            assert_eq!(result, vec![vec![]]);
        }
    
        #[test]
        fn test_subsets_single_element() {
            let result = subsets(&[1_i32]);
            // {}, {1}
            assert_eq!(result.len(), 2);
            assert!(result.contains(&vec![]));
            assert!(result.contains(&vec![1]));
        }
    
        #[test]
        fn test_subsets_three_elements_count() {
            let result = subsets(&[1_i32, 2, 3]);
            // 2^3 = 8 subsets
            assert_eq!(result.len(), 8);
        }
    
        #[test]
        fn test_subsets_four_elements_count() {
            let result = subsets(&[1_i32, 2, 3, 4]);
            assert_eq!(result.len(), 16);
        }
    
        #[test]
        fn test_subsets_recursive_matches_idiomatic() {
            let items = [1_i32, 2, 3];
            let mut a = subsets(&items);
            let mut b = subsets_recursive(&items);
            // Sort to compare regardless of order.
            for s in a.iter_mut() {
                s.sort();
            }
            for s in b.iter_mut() {
                s.sort();
            }
            a.sort();
            b.sort();
            assert_eq!(a, b);
        }
    
        #[test]
        fn test_subsets_of_size_two() {
            let result = subsets_of_size(&[1_i32, 2, 3, 4], 2);
            // C(4,2) = 6 pairs.
            assert_eq!(result.len(), 6);
            for s in &result {
                assert_eq!(s.len(), 2);
            }
        }
    
        #[test]
        fn test_subsets_contains_empty_and_full() {
            let items = [1_i32, 2, 3];
            let result = subsets(&items);
            // Must contain the empty subset and the full set.
            assert!(result.contains(&vec![]));
            assert!(result.contains(&vec![1, 2, 3]));
        }
    
        #[test]
        fn test_subsets_five_elements() {
            assert_eq!(subsets(&[1_i32, 2, 3, 4, 5]).len(), 32);
        }
    }

    Deep Comparison

    OCaml vs Rust: Generating All Subsets of a List Recursively

    Side-by-Side Code

    OCaml

    let rec powerset = function
      | [] -> [[]]
      | x :: rest ->
        let ps = powerset rest in
        ps @ List.map (fun s -> x :: s) ps
    

    Rust (idiomatic)

    pub fn subsets<T: Clone>(items: &[T]) -> Vec<Vec<T>> {
        match items.split_first() {
            None => vec![vec![]],
            Some((first, rest)) => {
                let without = subsets(rest);
                let with_first: Vec<Vec<T>> = without.iter().map(|s| {
                    let mut new_s = vec![first.clone()];
                    new_s.extend_from_slice(s);
                    new_s
                }).collect();
                let mut result = without;
                result.extend(with_first);
                result
            }
        }
    }
    

    Rust (functional/recursive — mirror of OCaml structure)

    pub fn subsets_recursive<T: Clone>(items: &[T]) -> Vec<Vec<T>> {
        if items.is_empty() { return vec![vec![]]; }
        let first = &items[0];
        let ps = subsets_recursive(&items[1..]);
        let with_first: Vec<Vec<T>> = ps.iter().map(|s| {
            let mut sub = vec![first.clone()];
            sub.extend_from_slice(s);
            sub
        }).collect();
        let mut result = ps;
        result.extend(with_first);
        result
    }
    

    Type Signatures

    ConceptOCamlRust
    powerset'a list -> 'a list listfn subsets<T: Clone>(items: &[T]) -> Vec<Vec<T>>
    element copyx :: s — O(1) cons (persistent list)first.clone() — O(1) clone for Copy types, O(k) for others
    appendps @ (List.map ...)result.extend(with_first)
    empty case\| [] -> [[]]None => vec![vec![]]
    decomposition\| x :: rest ->Some((first, rest)) = items.split_first()

    Key Insights

  • Clone cost: OCaml's persistent lists share tails — x :: s is O(1) because it just prepends one node. Rust must clone() each element to build new Vecs, making the operation O(subset_size) per subset.
  • Pattern matching on lists: OCaml's | [] -> ... | x :: rest -> is the canonical idiom for list decomposition. Rust mirrors this with split_first() which returns Option<(&T, &[T])>.
  • Output size: Both produce 2ⁿ subsets for an n-element input. The exponential output size is inherent to the problem — both implementations are optimal.
  • Argument borrowing: Rust takes &[T] (a borrowed slice); items are not consumed. The T: Clone bound is required to copy elements into new subset Vecs. OCaml's polymorphic 'a list handles this without an explicit bound.
  • Accumulator order: OCaml ps @ (map ...) appends "with-first" subsets after "without-first"; both Rust versions do the same. This means the empty set appears first and the full set appears last.
  • When to Use Each Style

    **Use split_first() when:** writing idiomatic Rust that handles slices — it expresses the head/tail decomposition cleanly. **Use items[0] / &items[1..] when:** you prefer a more direct translation of the OCaml pattern that's immediately recognizable to OCaml readers.

    Exercises

  • Generate all subsets of [1, 2, 3, 4] and verify there are exactly 16 (2⁴) subsets including the empty set.
  • Modify the function to generate only subsets of a fixed size k (combinations C(n, k)).
  • Add a filter predicate parameter so only subsets satisfying a condition (e.g., sum > threshold) are returned.
  • Open Source Repos