1044-sorted-vec — Sorted Vec with Binary Search
Tutorial
The Problem
When you need sorted iteration AND fast membership tests but do not need to insert and delete frequently, a sorted Vec<T> is more cache-efficient than a BTreeSet. Binary search over a contiguous array benefits from hardware prefetching, while pointer-heavy B-tree nodes fragment the cache. The sorted_vec and bsearch crates exploit this trade-off.
partition_point (Rust 1.52) and binary_search provide O(log n) insertion point and lookup over a sorted Vec.
🎯 Learning Outcomes
Vec<T> using partition_point for O(log n) insert positionbinary_search for O(log n) membership testVec to BTreeSet for read-heavy vs write-heavy workloadspartition_point for bisection (lower bound equivalent)Vec using partition_point on both endsCode Example
#![allow(clippy::all)]
// 1044: Sorted Vec — Binary Search Insert with partition_point
// Maintain a sorted Vec with O(log n) search and O(n) insert
/// A sorted vector that maintains order on insertion
struct SortedVec<T: Ord> {
data: Vec<T>,
}
impl<T: Ord> SortedVec<T> {
fn new() -> Self {
SortedVec { data: Vec::new() }
}
/// Insert maintaining sorted order — O(log n) search + O(n) shift
fn insert(&mut self, value: T) {
let pos = self.data.partition_point(|x| x < &value);
self.data.insert(pos, value);
}
/// Binary search — O(log n)
fn contains(&self, value: &T) -> bool {
self.data.binary_search(value).is_ok()
}
/// Find index of value — O(log n)
fn find(&self, value: &T) -> Option<usize> {
self.data.binary_search(value).ok()
}
/// Remove a value — O(log n) search + O(n) shift
fn remove(&mut self, value: &T) -> bool {
if let Ok(pos) = self.data.binary_search(value) {
self.data.remove(pos);
true
} else {
false
}
}
fn len(&self) -> usize {
self.data.len()
}
fn as_slice(&self) -> &[T] {
&self.data
}
/// Range query: elements in [lo, hi]
fn range(&self, lo: &T, hi: &T) -> &[T] {
let start = self.data.partition_point(|x| x < lo);
let end = self.data.partition_point(|x| x <= hi);
&self.data[start..end]
}
}
fn basic_sorted_vec() {
let mut sv = SortedVec::new();
sv.insert(5);
sv.insert(3);
sv.insert(7);
sv.insert(1);
sv.insert(4);
assert_eq!(sv.as_slice(), &[1, 3, 4, 5, 7]);
assert!(sv.contains(&4));
assert!(!sv.contains(&6));
assert_eq!(sv.find(&5), Some(3));
}
fn partition_point_demo() {
let data = vec![1, 3, 5, 7, 9, 11];
// partition_point: first index where predicate is false
let pos = data.partition_point(|&x| x < 6);
assert_eq!(pos, 3); // Insert point for 6
let pos = data.partition_point(|&x| x < 5);
assert_eq!(pos, 2); // 5 would go at index 2
// binary_search returns Ok(index) or Err(insert_point)
assert_eq!(data.binary_search(&5), Ok(2));
assert_eq!(data.binary_search(&6), Err(3));
}
fn range_query() {
let mut sv = SortedVec::new();
for x in [1, 3, 5, 7, 9, 11] {
sv.insert(x);
}
assert_eq!(sv.range(&3, &9), &[3, 5, 7, 9]);
assert_eq!(sv.range(&4, &8), &[5, 7]);
assert_eq!(sv.range(&20, &30), &[] as &[i32]);
}
fn remove_test() {
let mut sv = SortedVec::new();
for x in [1, 2, 3, 4, 5] {
sv.insert(x);
}
assert!(sv.remove(&3));
assert_eq!(sv.as_slice(), &[1, 2, 4, 5]);
assert!(!sv.remove(&3)); // Already removed
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
basic_sorted_vec();
}
#[test]
fn test_partition_point() {
partition_point_demo();
}
#[test]
fn test_range() {
range_query();
}
#[test]
fn test_remove() {
remove_test();
}
#[test]
fn test_duplicates() {
let mut sv = SortedVec::new();
sv.insert(1);
sv.insert(1);
sv.insert(2);
assert_eq!(sv.as_slice(), &[1, 1, 2]);
}
}Key Differences
partition_point**: Rust's partition_point is the canonical lower-bound bisection; OCaml's Base.Array.binary_search with ~how: parameter provides equivalent.BTreeSet / Set.Make are O(log n) insert.partition_point is zero-copy; OCaml's Array.sub allocates a new array.OCaml Approach
OCaml's arrays with Array.blit for insertion and binary search:
let binary_search arr target =
let lo = ref 0 and hi = ref (Array.length arr - 1) in
let result = ref None in
while !lo <= !hi do
let mid = (!lo + !hi) / 2 in
if arr.(mid) = target then (result := Some mid; lo := !hi + 1)
else if arr.(mid) < target then lo := mid + 1
else hi := mid - 1
done;
!result
The Base.Array.binary_search function provides a one-liner. Sorted arrays are the standard for static lookup tables in OCaml.
Full Source
#![allow(clippy::all)]
// 1044: Sorted Vec — Binary Search Insert with partition_point
// Maintain a sorted Vec with O(log n) search and O(n) insert
/// A sorted vector that maintains order on insertion
struct SortedVec<T: Ord> {
data: Vec<T>,
}
impl<T: Ord> SortedVec<T> {
fn new() -> Self {
SortedVec { data: Vec::new() }
}
/// Insert maintaining sorted order — O(log n) search + O(n) shift
fn insert(&mut self, value: T) {
let pos = self.data.partition_point(|x| x < &value);
self.data.insert(pos, value);
}
/// Binary search — O(log n)
fn contains(&self, value: &T) -> bool {
self.data.binary_search(value).is_ok()
}
/// Find index of value — O(log n)
fn find(&self, value: &T) -> Option<usize> {
self.data.binary_search(value).ok()
}
/// Remove a value — O(log n) search + O(n) shift
fn remove(&mut self, value: &T) -> bool {
if let Ok(pos) = self.data.binary_search(value) {
self.data.remove(pos);
true
} else {
false
}
}
fn len(&self) -> usize {
self.data.len()
}
fn as_slice(&self) -> &[T] {
&self.data
}
/// Range query: elements in [lo, hi]
fn range(&self, lo: &T, hi: &T) -> &[T] {
let start = self.data.partition_point(|x| x < lo);
let end = self.data.partition_point(|x| x <= hi);
&self.data[start..end]
}
}
fn basic_sorted_vec() {
let mut sv = SortedVec::new();
sv.insert(5);
sv.insert(3);
sv.insert(7);
sv.insert(1);
sv.insert(4);
assert_eq!(sv.as_slice(), &[1, 3, 4, 5, 7]);
assert!(sv.contains(&4));
assert!(!sv.contains(&6));
assert_eq!(sv.find(&5), Some(3));
}
fn partition_point_demo() {
let data = vec![1, 3, 5, 7, 9, 11];
// partition_point: first index where predicate is false
let pos = data.partition_point(|&x| x < 6);
assert_eq!(pos, 3); // Insert point for 6
let pos = data.partition_point(|&x| x < 5);
assert_eq!(pos, 2); // 5 would go at index 2
// binary_search returns Ok(index) or Err(insert_point)
assert_eq!(data.binary_search(&5), Ok(2));
assert_eq!(data.binary_search(&6), Err(3));
}
fn range_query() {
let mut sv = SortedVec::new();
for x in [1, 3, 5, 7, 9, 11] {
sv.insert(x);
}
assert_eq!(sv.range(&3, &9), &[3, 5, 7, 9]);
assert_eq!(sv.range(&4, &8), &[5, 7]);
assert_eq!(sv.range(&20, &30), &[] as &[i32]);
}
fn remove_test() {
let mut sv = SortedVec::new();
for x in [1, 2, 3, 4, 5] {
sv.insert(x);
}
assert!(sv.remove(&3));
assert_eq!(sv.as_slice(), &[1, 2, 4, 5]);
assert!(!sv.remove(&3)); // Already removed
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
basic_sorted_vec();
}
#[test]
fn test_partition_point() {
partition_point_demo();
}
#[test]
fn test_range() {
range_query();
}
#[test]
fn test_remove() {
remove_test();
}
#[test]
fn test_duplicates() {
let mut sv = SortedVec::new();
sv.insert(1);
sv.insert(1);
sv.insert(2);
assert_eq!(sv.as_slice(), &[1, 1, 2]);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
basic_sorted_vec();
}
#[test]
fn test_partition_point() {
partition_point_demo();
}
#[test]
fn test_range() {
range_query();
}
#[test]
fn test_remove() {
remove_test();
}
#[test]
fn test_duplicates() {
let mut sv = SortedVec::new();
sv.insert(1);
sv.insert(1);
sv.insert(2);
assert_eq!(sv.as_slice(), &[1, 1, 2]);
}
}
Deep Comparison
Sorted Vec — Comparison
Core Insight
A sorted vector trades O(n) insertion for O(log n) search and excellent cache locality. For small to medium collections, it often outperforms tree-based structures. Rust provides binary_search, partition_point, and slice-based range queries; OCaml uses manual binary search on arrays or sorted list insertion.
OCaml Approach
sorted_insert walks list to find position — O(n)lo/hibinary_search on arraysRust Approach
Vec::binary_search(): returns Ok(idx) or Err(insert_point)partition_point(|x| x < &val): first index where predicate failsVec::insert(pos, val): shifts elements right — O(n)partition_point for both boundsbinary_search for exact match, partition_point for boundsComparison Table
| Feature | OCaml | Rust |
|---|---|---|
| Binary search | Manual | binary_search() / partition_point() |
| Insert | List walk O(n) | insert(pos, val) O(n) |
| Search result | Index | Result<usize, usize> |
| Range query | Manual slice | partition_point × 2 |
| Cache locality | List: poor / Array: good | Vec: excellent |
| Dedup | Manual walk | dedup() built-in |
Exercises
remove(&mut self, value: &T) -> bool using binary_search to find the index and Vec::remove to remove it.count_in_range(lo: &T, hi: &T) -> usize using two partition_point calls and subtraction.merge_sorted(a: SortedVec<T>, b: SortedVec<T>) -> SortedVec<T> in O(n) using the merge step from merge sort.