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
- Input validation โ check that a string contains required character classes (digits, symbols, uppercase, etc.)
- Font/keyboard testing โ verify a sample text exercises all characters you care about
- Set intersection patterns โ the same `.filter().map().collect::<HashSet<_>>()` idiom applies to any "does A contain all of B?" question
Key Differences
| Concept | OCaml | Rust | |
|---|---|---|---|
| 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()` | |
| Bitflag | Manual with `lor`/`land` | ` | =` / `&` on `u32` |
| No-alloc check | Requires 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
| Aspect | OCaml | Rust |
|---|---|---|
| Memory | Tree-based set (heap nodes) | HashSet (heap) or u32 bitflag (stack) |
| Null safety | N/A (bool result) | N/A (bool result) |
| Errors | Not applicable | Not 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)