Collections · Lesson 38
HashSet and BTreeSet
cargo run --bin 38_setsA set stores each value at most once. HashSet<T> is a HashMap with no values: average O(1) insert and contains, no ordering, and T: Eq + Hash. BTreeSet<T> keeps values sorted, needs T: Ord, and supports first, last, and range.
insert returns true when the value was new, which makes "have I seen this?" a one liner. union, intersection, difference, and symmetric_difference return lazy iterators; the operators |, &, -, and ^ on references build new sets.
Insert, contains, remove
fn demo_basics() {
println!("-- basics --");
let mut s = HashSet::new();
println!("insert 1 new? {}", s.insert(1));
println!("insert 1 again new? {}", s.insert(1));
s.insert(2);
println!("contains 2? {} len {}", s.contains(&2), s.len());
println!("remove 2 {} remove 2 again {}", s.remove(&2), s.remove(&2));
let from_array: HashSet<char> = "hello".chars().collect();
println!("unique letters in hello: {}", from_array.len());
}Removing duplicates
fn demo_dedup() {
println!("-- dedup --");
let words = ["b", "a", "b", "c", "a"];
// Keep first occurrences, in order.
let mut seen = HashSet::new();
let first_seen: Vec<_> = words.iter().filter(|w| seen.insert(**w)).collect();
println!("order kept {first_seen:?}");
// Or let a BTreeSet sort them.
let sorted: BTreeSet<_> = words.iter().collect();
println!("sorted unique {sorted:?}");
let has_dupes = words.len() != words.iter().collect::<HashSet<_>>().len();
println!("has duplicates? {has_dupes}");
}Set algebra
fn demo_algebra() {
println!("-- set algebra --");
let a: BTreeSet<_> = [1, 2, 3, 4].into_iter().collect();
let b: BTreeSet<_> = [3, 4, 5].into_iter().collect();
println!("union {:?}", a.union(&b).collect::<Vec<_>>());
println!("intersection {:?}", a.intersection(&b).collect::<Vec<_>>());
println!("difference a-b {:?}", a.difference(&b).collect::<Vec<_>>());
println!("symmetric {:?}", a.symmetric_difference(&b).collect::<Vec<_>>());
println!("operators: {:?} {:?}", &a | &b, &a & &b);
let small: BTreeSet<_> = [3, 4].into_iter().collect();
println!(
"subset {} superset {} disjoint {}",
small.is_subset(&a),
a.is_superset(&small),
small.is_disjoint(&[9].into_iter().collect())
);
}BTreeSet ordering and ranges
fn demo_btreeset() {
println!("-- BTreeSet --");
let mut s = BTreeSet::new();
for n in [30, 10, 50, 20, 40] {
s.insert(n);
}
println!("{s:?} first {:?} last {:?}", s.first(), s.last());
println!("range 20..=40 {:?}", s.range(20..=40).collect::<Vec<_>>());
println!("next after 25: {:?}", s.range(26..).next());
println!("pop_last {:?} now {s:?}", s.pop_last());
}A practical use: tags and permissions
fn demo_tags() {
println!("-- tags --");
let required: HashSet<&str> = ["read", "write"].into();
let users = [("ann", vec!["read", "write", "admin"]), ("ben", vec!["read"])];
for (name, perms) in &users {
let have: HashSet<&str> = perms.iter().copied().collect();
let missing: BTreeSet<_> = required.difference(&have).collect();
if missing.is_empty() {
println!("{name} ok");
} else {
println!("{name} missing {missing:?}");
}
}
}Sets of tuples and structs
fn demo_compound() {
println!("-- compound values --");
let mut visited: HashSet<(i32, i32)> = HashSet::new();
let moves = [(0, 1), (1, 0), (0, -1), (-1, 0), (0, 1)];
let mut pos = (0, 0);
visited.insert(pos);
for (dx, dy) in moves {
pos = (pos.0 + dx, pos.1 + dy);
visited.insert(pos);
}
println!("visited {} distinct cells", visited.len());
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
enum Color {
Red,
Blue,
}
let colors: BTreeSet<Color> =
[Color::Blue, Color::Red, Color::Blue].into_iter().collect();
println!("{colors:?}");
}$ cargo run --bin 38_sets -- basics -- insert 1 new? true insert 1 again new? false contains 2? true len 2 remove 2 true remove 2 again false unique letters in hello: 4 -- dedup -- order kept ["b", "a", "c"] sorted unique {"a", "b", "c"} has duplicates? true -- set algebra -- union [1, 2, 3, 4, 5] intersection [3, 4] difference a-b [1, 2] symmetric [1, 2, 5] operators: {1, 2, 3, 4, 5} {3, 4} subset true superset true disjoint true -- BTreeSet -- {10, 20, 30, 40, 50} first Some(10) last Some(50) range 20..=40 [20, 30, 40] next after 25: Some(30) pop_last Some(50) now {10, 20, 30, 40} -- tags -- ann ok ben missing {"write"} -- compound values -- visited 4 distinct cells {Red, Blue}
Full program
// 38. HashSet and BTreeSet
//
// A set stores each value at most once. HashSet<T> is a HashMap with no values: average
// O(1) insert and contains, no ordering, and T: Eq + Hash. BTreeSet<T> keeps values
// sorted, needs T: Ord, and supports first, last, and range.
//
// insert returns true when the value was new, which makes "have I seen this?" a one
// liner. union, intersection, difference, and symmetric_difference return lazy
// iterators; the operators |, &, -, and ^ on references build new sets.
//
// Run: cargo run --bin 38_sets
use std::collections::{BTreeSet, HashSet};
// Example 1: insert, contains, remove.
fn demo_basics() {
println!("-- basics --");
let mut s = HashSet::new();
println!("insert 1 new? {}", s.insert(1));
println!("insert 1 again new? {}", s.insert(1));
s.insert(2);
println!("contains 2? {} len {}", s.contains(&2), s.len());
println!("remove 2 {} remove 2 again {}", s.remove(&2), s.remove(&2));
let from_array: HashSet<char> = "hello".chars().collect();
println!("unique letters in hello: {}", from_array.len());
}
// Example 2: removing duplicates.
fn demo_dedup() {
println!("-- dedup --");
let words = ["b", "a", "b", "c", "a"];
// Keep first occurrences, in order.
let mut seen = HashSet::new();
let first_seen: Vec<_> = words.iter().filter(|w| seen.insert(**w)).collect();
println!("order kept {first_seen:?}");
// Or let a BTreeSet sort them.
let sorted: BTreeSet<_> = words.iter().collect();
println!("sorted unique {sorted:?}");
let has_dupes = words.len() != words.iter().collect::<HashSet<_>>().len();
println!("has duplicates? {has_dupes}");
}
// Example 3: set algebra.
fn demo_algebra() {
println!("-- set algebra --");
let a: BTreeSet<_> = [1, 2, 3, 4].into_iter().collect();
let b: BTreeSet<_> = [3, 4, 5].into_iter().collect();
println!("union {:?}", a.union(&b).collect::<Vec<_>>());
println!("intersection {:?}", a.intersection(&b).collect::<Vec<_>>());
println!("difference a-b {:?}", a.difference(&b).collect::<Vec<_>>());
println!("symmetric {:?}", a.symmetric_difference(&b).collect::<Vec<_>>());
println!("operators: {:?} {:?}", &a | &b, &a & &b);
let small: BTreeSet<_> = [3, 4].into_iter().collect();
println!(
"subset {} superset {} disjoint {}",
small.is_subset(&a),
a.is_superset(&small),
small.is_disjoint(&[9].into_iter().collect())
);
}
// Example 4: BTreeSet ordering and ranges.
fn demo_btreeset() {
println!("-- BTreeSet --");
let mut s = BTreeSet::new();
for n in [30, 10, 50, 20, 40] {
s.insert(n);
}
println!("{s:?} first {:?} last {:?}", s.first(), s.last());
println!("range 20..=40 {:?}", s.range(20..=40).collect::<Vec<_>>());
println!("next after 25: {:?}", s.range(26..).next());
println!("pop_last {:?} now {s:?}", s.pop_last());
}
// Example 5: a practical use: tags and permissions.
fn demo_tags() {
println!("-- tags --");
let required: HashSet<&str> = ["read", "write"].into();
let users = [("ann", vec!["read", "write", "admin"]), ("ben", vec!["read"])];
for (name, perms) in &users {
let have: HashSet<&str> = perms.iter().copied().collect();
let missing: BTreeSet<_> = required.difference(&have).collect();
if missing.is_empty() {
println!("{name} ok");
} else {
println!("{name} missing {missing:?}");
}
}
}
// Example 6: sets of tuples and structs.
fn demo_compound() {
println!("-- compound values --");
let mut visited: HashSet<(i32, i32)> = HashSet::new();
let moves = [(0, 1), (1, 0), (0, -1), (-1, 0), (0, 1)];
let mut pos = (0, 0);
visited.insert(pos);
for (dx, dy) in moves {
pos = (pos.0 + dx, pos.1 + dy);
visited.insert(pos);
}
println!("visited {} distinct cells", visited.len());
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
enum Color {
Red,
Blue,
}
let colors: BTreeSet<Color> =
[Color::Blue, Color::Red, Color::Blue].into_iter().collect();
println!("{colors:?}");
}
fn main() {
demo_basics();
demo_dedup();
demo_algebra();
demo_btreeset();
demo_tags();
demo_compound();
}