Ordered Collections

Range-Based DSA Applications

Combining BTreeMap and BTreeSet range queries (.range()), boundary queries (.first_key_value(), .last_key_value()), and mutations builds classic data-structure and algorithmic patterns that require balanced search trees.

1. Merging Overlapping Intervals with BTreeMap<Start, End>

A BTreeMap<i64, i64> mapping each disjoint interval's start to its end can insert a new interval [new_start, new_end] and coalesce all overlapping intervals in $O((1 + k) \log n)$ time:

use std::collections::BTreeMap;

struct IntervalSet {
    // Invariant: disjoint intervals `start -> end` where `start <= end`
    intervals: BTreeMap<i64, i64>,
}

impl IntervalSet {
    fn new() -> Self {
        Self {
            intervals: BTreeMap::new(),
        }
    }

    fn insert(&mut self, mut start: i64, mut end: i64) {
        // 1. Check if the interval immediately preceding or at `start` overlaps:
        if let Some((&prev_start, &prev_end)) = self.intervals.range(..=start).next_back() {
            if prev_end >= start {
                start = prev_start;
                end = end.max(prev_end);
            }
        }

        // 2. Collect all existing intervals starting in `start..=end` and merge them:
        let overlapping_starts: Vec<i64> = self
            .intervals
            .range(start..=end)
            .map(|(&s, &e)| {
                end = end.max(e);
                s
            })
            .collect();

        for s in overlapping_starts {
            self.intervals.remove(&s);
        }

        self.intervals.insert(start, end);
    }
}

fn main() {
    let mut set = IntervalSet::new();
    set.insert(1, 4);
    set.insert(8, 10);
    set.insert(3, 8); // Bridges [1, 4] and [8, 10] into [1, 10]!
    set.insert(15, 20);

    println!("Merged intervals: {:?}", set.intervals);
}
Merged intervals: {1: 10, 15: 20}

2. Sliding-Window Multiset (Minimum and Maximum in a Moving Window)

Because BTreeSet<T> only stores unique elements, a multiset (bag) that allows duplicate elements while supporting $O(\log n)$ .first_key_value() (minimum) and .last_key_value() (maximum) is modeled in Rust as BTreeMap<T, usize> (mapping each value to its occurrence count):

use std::collections::BTreeMap;
use std::collections::btree_map::Entry;

struct MultiSet<T> {
    counts: BTreeMap<T, usize>,
}

impl<T: Ord> MultiSet<T> {
    fn new() -> Self {
        Self {
            counts: BTreeMap::new(),
        }
    }

    fn insert(&mut self, value: T) {
        *self.counts.entry(value).or_insert(0) += 1;
    }

    fn remove_one(&mut self, value: T) {
        if let Entry::Occupied(mut occ) = self.counts.entry(value) {
            if *occ.get() > 1 {
                *occ.get_mut() -= 1;
            } else {
                occ.remove();
            }
        }
    }

    fn min(&self) -> Option<&T> {
        self.counts.first_key_value().map(|(k, _)| k)
    }

    fn max(&self) -> Option<&T> {
        self.counts.last_key_value().map(|(k, _)| k)
    }
}

fn main() {
    let nums = [4, 2, 2, 9, 1];
    let k = 3;
    let mut window = MultiSet::new();

    for (i, &x) in nums.iter().enumerate() {
        window.insert(x);
        if i >= k {
            window.remove_one(nums[i - k]);
        }
        if i + 1 >= k {
            println!(
                "Window {:?} -> min={}, max={}",
                &nums[i + 1 - k..=i],
                window.min().unwrap(),
                window.max().unwrap()
            );
        }
    }
}
Window [4, 2, 2] -> min=2, max=4
Window [2, 2, 9] -> min=2, max=9
Window [2, 9, 1] -> min=1, max=9

3. String Prefix Search Using .range()

To find all keys in a BTreeSet<String> or BTreeMap<String, V> that start with a given prefix, start a .range(prefix..) at prefix and .take_while(|k| k.starts_with(prefix))—which stops scanning as soon as the first key past the prefix is reached in $O(\log n + k)$ time.