๐Ÿฆ€ Functional Rust

061: Pangram Check

Difficulty: โญ Level: Foundations Check whether a sentence uses every letter of the alphabet at least once โ€” with iterators and a HashSet.

The Problem This Solves

Every programmer encounters string validation tasks: "does this input contain the right characters?" Pangram detection is the clearest version of that problem. Validate a sentence for a typing game, check a font specimen covers all letters, or write a quick smoke-test for your keyboard โ€” the pattern is the same. The naive approach is a double loop: for each of the 26 letters, scan the entire string. It works, but it's O(26n) and reads like a TODO list. Python coders reach for `set(sentence.lower()) >= set('abcdefghijklmnopqrstuvwxyz')`. JavaScript developers write something similar with `Set`. Rust gives you the exact same idea, but the iterator pipeline is explicit, efficient, and reads like a description of what you're doing: filter to letters, lowercase them, collect into a set, count.

The Intuition

Think of it as a checklist of 26 boxes. For each character in the sentence, if it's a letter, check off that box. At the end, are all 26 checked? A `HashSet<char>` is that checklist. You add characters and duplicates disappear automatically. At the end, `len() == 26` means every letter appeared. The bitflag variant is the same idea with a `u32` โ€” 26 bits for 26 letters, no heap allocation. Useful when you're checking millions of strings.

How It Works in Rust

use std::collections::HashSet;

pub fn is_pangram(sentence: &str) -> bool {
 let unique_letters: HashSet<char> = sentence
     .chars()                          // iterate over Unicode chars
     .filter(|c| c.is_ascii_alphabetic()) // keep only a-z, A-Z
     .map(|c| c.to_ascii_lowercase())  // normalize to lowercase
     .collect();                        // HashSet auto-deduplicates
 unique_letters.len() == 26
}
Zero-allocation bitflag version (same logic, no heap):
pub fn is_pangram_bitflag(sentence: &str) -> bool {
 let mut seen: u32 = 0;
 for c in sentence.chars() {
     if c.is_ascii_alphabetic() {
         let idx = c.to_ascii_lowercase() as u32 - 'a' as u32;
         seen |= 1 << idx;  // set bit for this letter
     }
 }
 seen == (1 << 26) - 1  // all 26 bits set?
}

What This Unlocks

Key Differences

ConceptOCamlRust
Set type`module CharSet = Set.Make(Char)``HashSet<char>` from std
Build set`List.fold_left CharSet.add CharSet.empty chars``.collect()` directly
Count`CharSet.cardinal set``set.len()`
BitflagManual with `lor`/`land``=` / `&` on `u32`
No-alloc checkRequires explicit recursion`bitflag` with plain `u32`
/// # Pangram Check
///
/// A pangram is a sentence using every letter of the alphabet at least once.
/// Demonstrates set-based string analysis.

use std::collections::HashSet;

/// Idiomatic Rust using HashSet and iterator chains.
/// The `collect()` into HashSet automatically deduplicates.
pub fn is_pangram(sentence: &str) -> bool {
    // Filter to only alphabetic chars, lowercase them, collect unique into set
    let unique_letters: HashSet<char> = sentence
        .chars()
        .filter(|c| c.is_ascii_alphabetic())
        .map(|c| c.to_ascii_lowercase())
        .collect();
    unique_letters.len() == 26
}

/// Bitflag approach โ€” uses a u32 as a compact set of 26 bits.
/// Each bit represents a letter: bit 0 = 'a', bit 1 = 'b', etc.
/// No heap allocation needed!
pub fn is_pangram_bitflag(sentence: &str) -> bool {
    let mut seen: u32 = 0;
    for c in sentence.chars() {
        if c.is_ascii_alphabetic() {
            let idx = c.to_ascii_lowercase() as u32 - 'a' as u32;
            seen |= 1 << idx;
        }
    }
    seen == (1 << 26) - 1
}

/// Recursive approach โ€” checks if each letter 'a'..'z' exists in the string.
pub fn is_pangram_recursive(sentence: &str) -> bool {
    fn has_all(sentence: &str, letter: u8) -> bool {
        if letter > b'z' {
            return true;
        }
        let lower = sentence.to_ascii_lowercase();
        lower.contains(letter as char) && has_all(sentence, letter + 1)
    }
    has_all(sentence, b'a')
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_classic_pangram() {
        assert!(is_pangram("The quick brown fox jumps over the lazy dog"));
    }

    #[test]
    fn test_not_pangram() {
        assert!(!is_pangram("Hello world"));
    }

    #[test]
    fn test_empty_string() {
        assert!(!is_pangram(""));
    }

    #[test]
    fn test_missing_x() {
        assert!(!is_pangram("The quick brown fo jumps over the lazy dog"));
    }

    #[test]
    fn test_mixed_case() {
        assert!(is_pangram("THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"));
    }

    #[test]
    fn test_with_numbers_and_punctuation() {
        assert!(is_pangram("The 1 quick brown fox jumps! over the 2 lazy dogs."));
    }

    #[test]
    fn test_bitflag_version() {
        assert!(is_pangram_bitflag("The quick brown fox jumps over the lazy dog"));
        assert!(!is_pangram_bitflag("Hello world"));
    }

    #[test]
    fn test_recursive_version() {
        assert!(is_pangram_recursive("The quick brown fox jumps over the lazy dog"));
        assert!(!is_pangram_recursive("abc"));
    }
}

fn main() {
    println!("{:?}", is_pangram("The quick brown fox jumps over the lazy dog"));
    println!("{:?}", !is_pangram("Hello world"));
    println!("{:?}", !is_pangram(""));
}
(* Pangram Check โ€” Set-based string analysis *)

module CS = Set.Make(Char)

let alphabet =
  List.init 26 (fun i -> Char.chr (i + Char.code 'a'))
  |> CS.of_list

(* Idiomatic OCaml: filter to alpha chars, build set, check subset *)
let is_pangram s =
  let chars = s |> String.lowercase_ascii |> String.to_seq
    |> Seq.filter (fun c -> c >= 'a' && c <= 'z')
    |> CS.of_seq in
  CS.subset alphabet chars

(* Recursive: check each letter exists *)
let is_pangram_recursive s =
  let lower = String.lowercase_ascii s in
  let rec check c =
    if c > 'z' then true
    else String.contains lower c && check (Char.chr (Char.code c + 1))
  in
  check 'a'

let () =
  assert (is_pangram "The quick brown fox jumps over the lazy dog");
  assert (not (is_pangram "Hello world"));
  assert (is_pangram_recursive "The quick brown fox jumps over the lazy dog");
  assert (not (is_pangram_recursive "abc"));
  Printf.printf "All pangram tests passed!\n"

๐Ÿ“Š Detailed Comparison

Pangram Check โ€” OCaml vs Rust Comparison

Core Insight

Both languages excel at set operations on characters, but Rust offers a zero-allocation bitflag approach alongside the idiomatic HashSet version. OCaml's `Set.Make` functor creates a balanced tree set, while Rust's `HashSet` is hash-based.

OCaml Approach

Uses the `Set.Make(Char)` functor to create a character set module. Characters are filtered with `Seq.filter`, collected with `CS.of_seq`, and checked with `CS.subset`. The functor system requires declaring a module upfront.

Rust Approach

Iterator chain: `.chars().filter().map().collect::<HashSet<_>>()` โ€” the type drives `collect()` to deduplicate automatically. The bitflag variant uses `u32` as a 26-bit set with zero heap allocation, which has no direct OCaml equivalent without manual bit manipulation.

Comparison Table

AspectOCamlRust
MemoryTree-based set (heap nodes)HashSet (heap) or u32 bitflag (stack)
Null safetyN/A (bool result)N/A (bool result)
ErrorsNot applicableNot applicable
Iteration`Seq.filter` + `CS.of_seq``.chars().filter().map().collect()`
Set type`Set.Make` functor (balanced tree)`HashSet` (hash table)

Things Rust Learners Should Notice

1. `collect()` is polymorphic โ€” collecting into `HashSet` vs `Vec` changes behavior (dedup vs preserve) 2. Bitflag sets are a common Rust idiom for small fixed domains โ€” zero allocation, cache-friendly 3. `is_ascii_alphabetic()` โ€” Rust has rich character classification methods built in 4. No module functor needed โ€” `HashSet<char>` works directly without declaring a module 5. `to_ascii_lowercase()` works on individual chars, not just strings

Further Reading

  • [std::collections::HashSet](https://doc.rust-lang.org/std/collections/struct.HashSet.html)
  • [Exercism: Pangram](https://exercism.org/tracks/rust/exercises/pangram)
  • [Bit manipulation in Rust](https://doc.rust-lang.org/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators)