use std::borrow::Borrow;
use std::cmp::{Ordering, Reverse};
use std::collections::{BinaryHeap, HashMap};
use std::fmt;
use std::hash::Hash;
use std::iter::FusedIterator;
pub const MAX_CAPACITY: usize = isize::MAX as usize / 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LazyMinHeapError {
CapacityTooLarge {
requested: usize,
max: usize,
},
AllocationFailed {
requested: usize,
},
}
impl fmt::Display for LazyMinHeapError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LazyMinHeapError::CapacityTooLarge { requested, max } => {
write!(f, "LazyMinHeap capacity {requested} exceeds maximum {max}")
},
LazyMinHeapError::AllocationFailed { requested } => {
write!(
f,
"LazyMinHeap failed to allocate backing storage for capacity {requested}"
)
},
}
}
}
impl std::error::Error for LazyMinHeapError {}
#[derive(Debug, Clone)]
struct HeapEntry<K, S> {
score: S,
seq: u64,
key: K,
}
impl<K, S> PartialEq for HeapEntry<K, S>
where
S: Ord,
{
fn eq(&self, other: &Self) -> bool {
self.score == other.score && self.seq == other.seq
}
}
impl<K, S> Eq for HeapEntry<K, S> where S: Ord {}
impl<K, S> PartialOrd for HeapEntry<K, S>
where
S: Ord,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<K, S> Ord for HeapEntry<K, S>
where
S: Ord,
{
fn cmp(&self, other: &Self) -> Ordering {
match self.score.cmp(&other.score) {
Ordering::Equal => self.seq.cmp(&other.seq),
ordering => ordering,
}
}
}
#[derive(Clone)]
pub struct LazyMinHeap<K, S> {
scores: HashMap<K, ScoreEntry<S>>,
heap: BinaryHeap<Reverse<HeapEntry<K, S>>>,
seq: u64,
auto_rebuild: Option<usize>,
}
impl<K, S> fmt::Debug for LazyMinHeap<K, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LazyMinHeap")
.field("len", &self.scores.len())
.field("heap_len", &self.heap.len())
.field("seq", &self.seq)
.field("auto_rebuild_factor", &self.auto_rebuild)
.finish_non_exhaustive()
}
}
impl<K, S> LazyMinHeap<K, S>
where
K: Eq + Hash + Clone,
S: Ord + Clone,
{
pub fn new() -> Self {
Self {
scores: HashMap::new(),
heap: BinaryHeap::new(),
seq: 0,
auto_rebuild: None,
}
}
#[track_caller]
pub fn with_capacity(capacity: usize) -> Self {
Self::try_with_capacity(capacity)
.expect("LazyMinHeap::with_capacity: capacity exceeds MAX_CAPACITY")
}
pub fn try_with_capacity(capacity: usize) -> Result<Self, LazyMinHeapError> {
if capacity > MAX_CAPACITY {
return Err(LazyMinHeapError::CapacityTooLarge {
requested: capacity,
max: MAX_CAPACITY,
});
}
let mut scores: HashMap<K, ScoreEntry<S>> = HashMap::new();
scores
.try_reserve(capacity)
.map_err(|_| LazyMinHeapError::AllocationFailed {
requested: capacity,
})?;
let mut heap_vec: Vec<Reverse<HeapEntry<K, S>>> = Vec::new();
heap_vec
.try_reserve_exact(capacity)
.map_err(|_| LazyMinHeapError::AllocationFailed {
requested: capacity,
})?;
Ok(Self {
scores,
heap: BinaryHeap::from(heap_vec),
seq: 0,
auto_rebuild: None,
})
}
#[track_caller]
pub fn reserve(&mut self, additional: usize) {
self.try_reserve(additional)
.expect("LazyMinHeap::reserve: capacity exceeds MAX_CAPACITY");
}
pub fn try_reserve(&mut self, additional: usize) -> Result<(), LazyMinHeapError> {
let projected = self.scores.len().checked_add(additional).ok_or(
LazyMinHeapError::CapacityTooLarge {
requested: additional,
max: MAX_CAPACITY,
},
)?;
if projected > MAX_CAPACITY {
return Err(LazyMinHeapError::CapacityTooLarge {
requested: projected,
max: MAX_CAPACITY,
});
}
self.scores
.try_reserve(additional)
.map_err(|_| LazyMinHeapError::AllocationFailed {
requested: additional,
})?;
let mut vec: Vec<Reverse<HeapEntry<K, S>>> = std::mem::take(&mut self.heap).into_vec();
let reserve_result = vec.try_reserve(additional);
self.heap = BinaryHeap::from(vec);
reserve_result.map_err(|_| LazyMinHeapError::AllocationFailed {
requested: additional,
})
}
pub fn with_auto_rebuild(factor: usize) -> Self {
let mut heap = Self::new();
heap.set_auto_rebuild(Some(factor));
heap
}
pub fn set_auto_rebuild(&mut self, factor: Option<usize>) {
self.auto_rebuild = factor.map(|f| f.max(1));
}
pub fn auto_rebuild_factor(&self) -> Option<usize> {
self.auto_rebuild
}
pub fn shrink_to_fit(&mut self) {
self.scores.shrink_to_fit();
self.heap.shrink_to_fit();
}
pub fn clear(&mut self) {
self.scores.clear();
self.heap.clear();
}
pub fn clear_shrink(&mut self) {
self.clear();
self.scores.shrink_to_fit();
self.heap.shrink_to_fit();
}
pub fn len(&self) -> usize {
self.scores.len()
}
pub fn is_empty(&self) -> bool {
self.scores.is_empty()
}
pub fn heap_len(&self) -> usize {
self.heap.len()
}
pub fn iter(&self) -> Iter<'_, K, S> {
Iter {
inner: self.scores.iter(),
}
}
pub fn score_of<Q>(&self, key: &Q) -> Option<&S>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.scores.get(key).map(|entry| &entry.score)
}
pub fn update(&mut self, key: K, score: S) -> Option<S> {
if self.seq == u64::MAX {
self.renumber_seqs();
}
let seq = self.seq;
self.seq = self
.seq
.checked_add(1)
.expect("LazyMinHeap::update: seq overflow after renumber (unreachable)");
let previous = self.scores.insert(
key.clone(),
ScoreEntry {
score: score.clone(),
seq,
},
);
self.push_entry_with_seq(key, score, seq);
if let Some(factor) = self.auto_rebuild {
self.maybe_rebuild(factor);
}
previous.map(|entry| entry.score)
}
pub fn renumber_seqs(&mut self) {
let mut live: Vec<(K, S, u64)> = self
.scores
.iter()
.map(|(k, entry)| (k.clone(), entry.score.clone(), entry.seq))
.collect();
live.sort_by_key(|(_, _, seq)| *seq);
self.scores.clear();
self.heap.clear();
self.seq = 0;
for (key, score, _) in live {
let seq = self.seq;
self.seq += 1;
self.scores.insert(
key.clone(),
ScoreEntry {
score: score.clone(),
seq,
},
);
self.push_entry_with_seq(key, score, seq);
}
}
pub fn remove<Q>(&mut self, key: &Q) -> Option<S>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.scores.remove(key).map(|entry| entry.score)
}
pub fn pop_best(&mut self) -> Option<(K, S)> {
loop {
let Reverse(entry) = self.heap.pop()?;
match self.scores.get(&entry.key) {
Some(current) if current.score == entry.score && current.seq == entry.seq => {
self.scores.remove(&entry.key);
return Some((entry.key, entry.score));
},
_ => continue,
}
}
}
pub fn rebuild(&mut self) {
self.heap.clear();
let entries: Vec<(K, ScoreEntry<S>)> = self
.scores
.iter()
.map(|(key, entry)| (key.clone(), entry.clone()))
.collect();
for (key, entry) in entries {
self.push_entry_with_seq(key, entry.score, entry.seq);
}
}
pub fn try_rebuild(&mut self) -> Result<(), LazyMinHeapError> {
let n = self.scores.len();
let mut new_vec: Vec<Reverse<HeapEntry<K, S>>> = Vec::new();
new_vec
.try_reserve_exact(n)
.map_err(|_| LazyMinHeapError::AllocationFailed { requested: n })?;
for (key, entry) in self.scores.iter() {
new_vec.push(Reverse(HeapEntry {
score: entry.score.clone(),
seq: entry.seq,
key: key.clone(),
}));
}
self.heap = BinaryHeap::from(new_vec);
Ok(())
}
pub fn maybe_rebuild(&mut self, factor: usize) {
let factor = factor.max(1);
if self.heap.len() > self.scores.len().saturating_mul(factor) {
self.rebuild();
}
}
#[cfg(test)]
fn debug_snapshot(&self) -> LazyHeapSnapshot {
LazyHeapSnapshot {
len: self.len(),
heap_len: self.heap_len(),
}
}
pub fn approx_bytes(&self) -> usize {
let scores_bytes = self
.scores
.capacity()
.saturating_mul(std::mem::size_of::<(K, ScoreEntry<S>)>());
let heap_bytes = self
.heap
.capacity()
.saturating_mul(std::mem::size_of::<std::cmp::Reverse<HeapEntry<K, S>>>());
std::mem::size_of::<Self>()
.saturating_add(scores_bytes)
.saturating_add(heap_bytes)
}
#[cfg(test)]
fn debug_snapshot_scores(&self) -> Vec<(K, S)>
where
K: Clone,
S: Clone,
{
self.scores
.iter()
.map(|(key, entry)| (key.clone(), entry.score.clone()))
.collect()
}
#[cfg(test)]
fn debug_validate_invariants(&self) {
assert_eq!(self.len(), self.scores.len());
if self.is_empty() {
assert!(self.scores.is_empty());
}
}
fn push_entry_with_seq(&mut self, key: K, score: S, seq: u64) {
let entry = HeapEntry { score, seq, key };
self.heap.push(Reverse(entry));
}
}
#[derive(Debug, Clone)]
struct ScoreEntry<S> {
score: S,
seq: u64,
}
pub struct Iter<'a, K, S> {
inner: std::collections::hash_map::Iter<'a, K, ScoreEntry<S>>,
}
impl<K: fmt::Debug, S: fmt::Debug> fmt::Debug for Iter<'_, K, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Iter").finish_non_exhaustive()
}
}
impl<'a, K, S> Iterator for Iter<'a, K, S> {
type Item = (&'a K, &'a S);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(key, entry)| (key, &entry.score))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<K, S> ExactSizeIterator for Iter<'_, K, S> {}
impl<K, S> FusedIterator for Iter<'_, K, S> {}
pub struct IntoIter<K, S> {
inner: std::collections::hash_map::IntoIter<K, ScoreEntry<S>>,
}
impl<K: fmt::Debug, S: fmt::Debug> fmt::Debug for IntoIter<K, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IntoIter").finish_non_exhaustive()
}
}
impl<K, S> Iterator for IntoIter<K, S> {
type Item = (K, S);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(key, entry)| (key, entry.score))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<K, S> ExactSizeIterator for IntoIter<K, S> {}
impl<K, S> FusedIterator for IntoIter<K, S> {}
#[cfg(test)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct LazyHeapSnapshot {
len: usize,
heap_len: usize,
}
impl<K, S> Default for LazyMinHeap<K, S>
where
K: Eq + Hash + Clone,
S: Ord + Clone,
{
fn default() -> Self {
Self::new()
}
}
impl<K, S> FromIterator<(K, S)> for LazyMinHeap<K, S>
where
K: Eq + Hash + Clone,
S: Ord + Clone,
{
fn from_iter<I: IntoIterator<Item = (K, S)>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
let mut heap = Self::try_with_capacity(lower).unwrap_or_else(|_| Self::new());
for (key, score) in iter {
heap.update(key, score);
}
heap
}
}
impl<K, S> Extend<(K, S)> for LazyMinHeap<K, S>
where
K: Eq + Hash + Clone,
S: Ord + Clone,
{
fn extend<I: IntoIterator<Item = (K, S)>>(&mut self, iter: I) {
for (key, score) in iter {
self.update(key, score);
}
}
}
impl<K, S> IntoIterator for LazyMinHeap<K, S>
where
K: Eq + Hash + Clone,
S: Ord + Clone,
{
type Item = (K, S);
type IntoIter = IntoIter<K, S>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
inner: self.scores.into_iter(),
}
}
}
impl<'a, K, S> IntoIterator for &'a LazyMinHeap<K, S>
where
K: Eq + Hash + Clone,
S: Ord + Clone,
{
type Item = (&'a K, &'a S);
type IntoIter = Iter<'a, K, S>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lazy_heap_skips_stale_entries() {
let mut heap = LazyMinHeap::new();
heap.update("a", 5);
heap.update("a", 2);
heap.update("b", 3);
assert_eq!(heap.pop_best(), Some(("a", 2)));
assert_eq!(heap.pop_best(), Some(("b", 3)));
}
#[test]
fn lazy_heap_remove_and_rebuild() {
let mut heap = LazyMinHeap::new();
heap.update("a", 5);
heap.update("b", 1);
heap.remove(&"b");
heap.maybe_rebuild(1);
assert_eq!(heap.pop_best(), Some(("a", 5)));
assert_eq!(heap.pop_best(), None);
}
#[test]
fn lazy_heap_update_overwrites_score_and_len() {
let mut heap = LazyMinHeap::new();
assert_eq!(heap.len(), 0);
assert_eq!(heap.update("a", 10), None);
assert_eq!(heap.len(), 1);
assert_eq!(heap.score_of(&"a"), Some(&10));
assert_eq!(heap.update("a", 3), Some(10));
assert_eq!(heap.len(), 1);
assert_eq!(heap.score_of(&"a"), Some(&3));
}
#[test]
fn lazy_heap_pop_best_removes_key() {
let mut heap = LazyMinHeap::new();
heap.update("a", 2);
heap.update("b", 1);
assert_eq!(heap.pop_best(), Some(("b", 1)));
assert_eq!(heap.score_of(&"b"), None);
assert_eq!(heap.len(), 1);
assert_eq!(heap.pop_best(), Some(("a", 2)));
assert!(heap.is_empty());
}
#[test]
fn lazy_heap_tie_breaks_by_seq() {
let mut heap = LazyMinHeap::new();
heap.update("a", 1);
heap.update("b", 1);
heap.update("c", 1);
assert_eq!(heap.pop_best(), Some(("a", 1)));
assert_eq!(heap.pop_best(), Some(("b", 1)));
assert_eq!(heap.pop_best(), Some(("c", 1)));
}
#[test]
fn lazy_heap_update_same_score_refreshes_order() {
let mut heap = LazyMinHeap::new();
heap.update("a", 1);
heap.update("b", 1);
heap.update("a", 1); assert_eq!(heap.pop_best(), Some(("b", 1)));
assert_eq!(heap.pop_best(), Some(("a", 1)));
}
#[test]
fn lazy_heap_remove_does_not_touch_heap_until_pop() {
let mut heap = LazyMinHeap::new();
heap.update("a", 2);
heap.update("b", 1);
assert_eq!(heap.remove(&"b"), Some(1));
assert_eq!(heap.len(), 1);
assert_eq!(heap.pop_best(), Some(("a", 2)));
assert_eq!(heap.pop_best(), None);
}
#[test]
fn lazy_heap_rebuild_cleans_stale_entries() {
let mut heap = LazyMinHeap::new();
heap.update("a", 5);
heap.update("a", 4);
heap.update("a", 3);
heap.update("b", 2);
assert!(heap.heap_len() > heap.len());
heap.rebuild();
assert_eq!(heap.heap_len(), heap.len());
assert_eq!(heap.pop_best(), Some(("b", 2)));
assert_eq!(heap.pop_best(), Some(("a", 3)));
}
#[test]
fn lazy_heap_maybe_rebuild_triggers_on_factor() {
let mut heap = LazyMinHeap::new();
heap.update("a", 3);
heap.update("a", 2);
heap.update("a", 1);
heap.update("b", 4);
assert!(heap.heap_len() > heap.len());
heap.maybe_rebuild(1);
assert_eq!(heap.heap_len(), heap.len());
assert_eq!(heap.pop_best(), Some(("a", 1)));
}
#[test]
fn lazy_heap_debug_invariants_hold() {
let mut heap = LazyMinHeap::new();
heap.update("a", 2);
heap.update("b", 1);
heap.remove(&"b");
heap.debug_validate_invariants();
}
#[test]
fn lazy_heap_debug_snapshots() {
let mut heap = LazyMinHeap::new();
heap.update("a", 2);
heap.update("b", 1);
let snapshot = heap.debug_snapshot();
assert_eq!(snapshot.len, 2);
assert!(snapshot.heap_len >= snapshot.len);
let scores = heap.debug_snapshot_scores();
assert_eq!(scores.len(), 2);
}
#[test]
fn lazy_heap_iter_visits_live_entries() {
let mut heap = LazyMinHeap::new();
heap.update("a", 3);
heap.update("b", 1);
heap.update("a", 2);
let mut entries: Vec<_> = heap.iter().collect();
entries.sort();
assert_eq!(entries, vec![(&"a", &2), (&"b", &1)]);
}
#[test]
fn lazy_heap_into_iter_yields_live_entries() {
let mut heap = LazyMinHeap::new();
heap.update("a", 3);
heap.update("b", 1);
heap.update("a", 2);
let mut entries: Vec<_> = heap.into_iter().collect();
entries.sort();
assert_eq!(entries, vec![("a", 2), ("b", 1)]);
}
#[test]
fn try_with_capacity_rejects_oversized_capacity() {
let err = LazyMinHeap::<u32, u32>::try_with_capacity(MAX_CAPACITY + 1).unwrap_err();
assert!(matches!(err, LazyMinHeapError::CapacityTooLarge { .. }));
}
#[test]
#[should_panic(expected = "MAX_CAPACITY")]
fn with_capacity_panics_on_oversized_capacity() {
let _heap: LazyMinHeap<u32, u32> = LazyMinHeap::with_capacity(MAX_CAPACITY + 1);
}
#[test]
fn try_reserve_rejects_oversized_total() {
let mut heap: LazyMinHeap<u32, u32> = LazyMinHeap::new();
let err = heap.try_reserve(MAX_CAPACITY + 1).unwrap_err();
assert!(matches!(err, LazyMinHeapError::CapacityTooLarge { .. }));
}
#[test]
fn try_reserve_small_value_succeeds_and_is_usable() {
let mut heap: LazyMinHeap<u32, u32> = LazyMinHeap::new();
heap.try_reserve(16).expect("reserve should succeed");
heap.update(1, 10);
heap.update(2, 5);
assert_eq!(heap.pop_best(), Some((2, 5)));
assert_eq!(heap.pop_best(), Some((1, 10)));
}
#[test]
fn try_rebuild_preserves_live_entries_and_removes_stale() {
let mut heap: LazyMinHeap<&str, u32> = LazyMinHeap::new();
heap.update("a", 3);
heap.update("a", 2);
heap.update("a", 1);
heap.update("b", 4);
assert!(heap.heap_len() > heap.len());
heap.try_rebuild().expect("rebuild should succeed");
assert_eq!(heap.heap_len(), heap.len());
assert_eq!(heap.pop_best(), Some(("a", 1)));
assert_eq!(heap.pop_best(), Some(("b", 4)));
}
#[test]
fn auto_rebuild_bounds_stale_heap_growth() {
let mut heap: LazyMinHeap<&str, u32> = LazyMinHeap::new();
for i in 0..1_000 {
heap.update("hot", i);
}
assert_eq!(heap.len(), 1);
assert_eq!(heap.heap_len(), 1_000);
let mut heap: LazyMinHeap<&str, u32> = LazyMinHeap::with_auto_rebuild(4);
for i in 0..1_000 {
heap.update("hot", i);
}
assert_eq!(heap.len(), 1);
assert!(heap.heap_len() <= 4);
assert_eq!(heap.auto_rebuild_factor(), Some(4));
}
#[test]
fn set_auto_rebuild_clamps_factor_below_one() {
let mut heap: LazyMinHeap<&str, u32> = LazyMinHeap::new();
heap.set_auto_rebuild(Some(0));
assert_eq!(heap.auto_rebuild_factor(), Some(1));
heap.set_auto_rebuild(None);
assert_eq!(heap.auto_rebuild_factor(), None);
}
#[test]
fn renumber_seqs_preserves_fifo_for_equal_scores() {
let mut heap: LazyMinHeap<&str, u32> = LazyMinHeap::new();
heap.update("first", 1);
heap.update("second", 1);
heap.update("third", 1);
heap.renumber_seqs();
assert_eq!(heap.pop_best(), Some(("first", 1)));
assert_eq!(heap.pop_best(), Some(("second", 1)));
assert_eq!(heap.pop_best(), Some(("third", 1)));
}
#[test]
fn update_at_seq_saturation_renumbers_without_stale_match() {
let mut heap: LazyMinHeap<&str, u32> = LazyMinHeap::new();
heap.update("a", 1);
heap.update("b", 2);
heap.seq = u64::MAX;
heap.update("c", 3);
assert_eq!(heap.len(), 3);
assert_eq!(heap.pop_best(), Some(("a", 1)));
assert_eq!(heap.pop_best(), Some(("b", 2)));
assert_eq!(heap.pop_best(), Some(("c", 3)));
assert!(heap.seq < u64::MAX);
}
#[test]
fn approx_bytes_does_not_overflow() {
let heap: LazyMinHeap<u64, u64> = LazyMinHeap::with_capacity(1_024);
let bytes = heap.approx_bytes();
assert!(bytes >= std::mem::size_of::<LazyMinHeap<u64, u64>>());
assert!(bytes < usize::MAX);
}
#[test]
fn debug_impl_does_not_leak_keys_or_scores() {
struct Secret(u32);
impl PartialEq for Secret {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for Secret {}
impl Clone for Secret {
fn clone(&self) -> Self {
Secret(self.0)
}
}
impl std::hash::Hash for Secret {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl Ord for Secret {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl PartialOrd for Secret {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
let mut heap: LazyMinHeap<Secret, Secret> = LazyMinHeap::new();
heap.update(Secret(0xdead_beef), Secret(0xfeed_face));
let rendered = format!("{:?}", heap);
assert!(rendered.contains("LazyMinHeap"));
assert!(rendered.contains("len"));
assert!(!rendered.contains("dead"));
assert!(!rendered.contains("beef"));
assert!(!rendered.contains("feed"));
assert!(!rendered.contains("face"));
}
#[test]
fn from_iter_clamps_size_hint_to_max_capacity() {
struct AdversarialHint;
impl Iterator for AdversarialHint {
type Item = (u32, u32);
fn next(&mut self) -> Option<Self::Item> {
None
}
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::MAX, None)
}
}
let heap: LazyMinHeap<u32, u32> = AdversarialHint.collect();
assert!(heap.is_empty());
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_min_heap_ordering(
entries in prop::collection::vec((any::<u32>(), any::<u32>()), 0..50)
) {
let mut heap = LazyMinHeap::new();
for (key, score) in entries {
heap.update(key, score);
}
let mut last_score = None;
while let Some((_key, score)) = heap.pop_best() {
if let Some(prev_score) = last_score {
prop_assert!(score >= prev_score);
}
last_score = Some(score);
}
prop_assert!(heap.is_empty());
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_tie_breaking_fifo(
keys in prop::collection::vec(any::<u32>(), 3..20)
) {
let mut heap = LazyMinHeap::new();
let score = 1u32;
for key in &keys {
heap.update(*key, score);
}
for expected_key in keys {
if let Some((key, s)) = heap.pop_best() {
prop_assert_eq!(s, score);
prop_assert_eq!(key, expected_key);
}
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_update_overwrites(
key in any::<u32>(),
scores in prop::collection::vec(any::<u32>(), 1..20)
) {
let mut heap = LazyMinHeap::new();
for score in &scores {
heap.update(key, *score);
prop_assert_eq!(heap.score_of(&key), Some(score));
prop_assert_eq!(heap.len(), 1);
}
let popped = heap.pop_best();
prop_assert!(popped.is_some());
let (k, s) = popped.unwrap();
prop_assert_eq!(k, key);
prop_assert_eq!(s, *scores.last().unwrap());
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_update_idempotent(
key in any::<u32>(),
score in any::<u32>(),
repeat_count in 1usize..10
) {
let mut heap = LazyMinHeap::new();
for _ in 0..repeat_count {
heap.update(key, score);
prop_assert_eq!(heap.score_of(&key), Some(&score));
prop_assert_eq!(heap.len(), 1);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_update_returns_old_score(
key in any::<u32>(),
score1 in any::<u32>(),
score2 in any::<u32>()
) {
let mut heap = LazyMinHeap::new();
let old = heap.update(key, score1);
prop_assert_eq!(old, None);
let old = heap.update(key, score2);
prop_assert_eq!(old, Some(score1));
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_remove_decreases_length(
entries in prop::collection::vec((any::<u32>(), any::<u32>()), 1..30)
) {
let mut heap = LazyMinHeap::new();
for (key, score) in &entries {
heap.update(*key, *score);
}
for (key, score) in entries {
let old_len = heap.len();
let removed = heap.remove(&key);
if removed == Some(score) {
prop_assert_eq!(heap.len(), old_len - 1);
prop_assert_eq!(heap.score_of(&key), None);
}
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_remove_makes_unavailable(
key in any::<u32>(),
score in any::<u32>()
) {
let mut heap = LazyMinHeap::new();
heap.update(key, score);
prop_assert_eq!(heap.score_of(&key), Some(&score));
let removed = heap.remove(&key);
prop_assert_eq!(removed, Some(score));
prop_assert_eq!(heap.score_of(&key), None);
prop_assert!(heap.is_empty());
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_remove_missing_returns_none(
insert_keys in prop::collection::vec(0u32..20, 1..10),
query_key in 20u32..40
) {
let mut heap = LazyMinHeap::new();
for key in insert_keys {
heap.update(key, 1);
}
let removed = heap.remove(&query_key);
prop_assert_eq!(removed, None);
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_pop_decreases_length(
entries in prop::collection::vec((any::<u32>(), any::<u32>()), 1..30)
) {
let mut heap = LazyMinHeap::new();
for (key, score) in entries {
heap.update(key, score);
}
while !heap.is_empty() {
let old_len = heap.len();
let popped = heap.pop_best();
prop_assert!(popped.is_some());
prop_assert_eq!(heap.len(), old_len - 1);
}
prop_assert_eq!(heap.pop_best(), None);
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_pop_removes_key(
entries in prop::collection::vec((any::<u32>(), any::<u32>()), 1..30)
) {
let mut heap = LazyMinHeap::new();
for (key, score) in entries {
heap.update(key, score);
}
while let Some((key, _score)) = heap.pop_best() {
prop_assert_eq!(heap.score_of(&key), None);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_pop_empty_returns_none(_unit in any::<()>()) {
let mut heap: LazyMinHeap<u32, u32> = LazyMinHeap::new();
prop_assert_eq!(heap.pop_best(), None);
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_stale_entries_skipped(
updates in prop::collection::vec((0u32..10, any::<u32>()), 10..50)
) {
let mut heap = LazyMinHeap::new();
for (key, score) in updates {
heap.update(key, score);
}
let mut seen_keys = std::collections::HashSet::new();
while let Some((key, _score)) = heap.pop_best() {
prop_assert!(!seen_keys.contains(&key));
seen_keys.insert(key);
}
prop_assert!(heap.is_empty());
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_rebuild_preserves_order(
updates in prop::collection::vec((0u32..20, any::<u32>()), 10..50)
) {
let mut heap = LazyMinHeap::new();
for (key, score) in updates {
heap.update(key, score);
}
let len_before = heap.len();
heap.rebuild();
prop_assert_eq!(heap.len(), len_before);
prop_assert_eq!(heap.heap_len(), heap.len());
let mut last_score = None;
while let Some((_key, score)) = heap.pop_best() {
if let Some(prev_score) = last_score {
prop_assert!(score >= prev_score);
}
last_score = Some(score);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_maybe_rebuild_factor(
key in any::<u32>(),
updates in prop::collection::vec(any::<u32>(), 3..20)
) {
let mut heap = LazyMinHeap::new();
for score in updates {
heap.update(key, score);
}
let heap_len_before = heap.heap_len();
let len = heap.len();
if heap_len_before > len {
heap.maybe_rebuild(1);
prop_assert_eq!(heap.heap_len(), heap.len());
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_len_tracks_unique_keys(
entries in prop::collection::vec((any::<u32>(), any::<u32>()), 0..50)
) {
let mut heap = LazyMinHeap::new();
for (key, score) in &entries {
heap.update(*key, *score);
}
let unique_count = {
let mut unique = std::collections::HashSet::new();
for (key, _) in entries {
unique.insert(key);
}
unique.len()
};
prop_assert_eq!(heap.len(), unique_count);
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_is_empty_consistent(
entries in prop::collection::vec((any::<u32>(), any::<u32>()), 0..30)
) {
let mut heap = LazyMinHeap::new();
for (key, score) in entries {
heap.update(key, score);
if heap.is_empty() {
prop_assert_eq!(heap.len(), 0);
} else {
prop_assert!(!heap.is_empty());
}
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_score_of_returns_current(
entries in prop::collection::vec((any::<u32>(), any::<u32>()), 1..30)
) {
let mut heap = LazyMinHeap::new();
for (key, score) in &entries {
heap.update(*key, *score);
}
for (key, expected_score) in entries {
if let Some(&actual_score) = heap.score_of(&key) {
prop_assert_eq!(actual_score, expected_score);
}
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_score_of_removed_is_none(
key in any::<u32>(),
score in any::<u32>()
) {
let mut heap = LazyMinHeap::new();
heap.update(key, score);
heap.remove(&key);
prop_assert_eq!(heap.score_of(&key), None);
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_clear_resets_state(
entries in prop::collection::vec((any::<u32>(), any::<u32>()), 1..30)
) {
let mut heap = LazyMinHeap::new();
for (key, score) in entries {
heap.update(key, score);
}
heap.clear_shrink();
prop_assert!(heap.is_empty());
prop_assert_eq!(heap.len(), 0);
prop_assert_eq!(heap.pop_best(), None);
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_usable_after_clear(
entries1 in prop::collection::vec((any::<u32>(), any::<u32>()), 1..20),
entries2 in prop::collection::vec((any::<u32>(), any::<u32>()), 1..20)
) {
let mut heap = LazyMinHeap::new();
for (key, score) in entries1 {
heap.update(key, score);
}
heap.clear_shrink();
for (key, score) in &entries2 {
heap.update(*key, *score);
}
let unique_count = {
let mut unique = std::collections::HashSet::new();
for (key, _) in entries2 {
unique.insert(key);
}
unique.len()
};
prop_assert_eq!(heap.len(), unique_count);
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_matches_binary_heap(
operations in prop::collection::vec((0u8..3, any::<u32>(), any::<u32>()), 0..50)
) {
let mut heap = LazyMinHeap::new();
let mut reference = std::collections::BinaryHeap::new();
let mut live_keys = std::collections::HashSet::new();
use std::cmp::Reverse;
for (op, key, score) in operations {
match op % 3 {
0 => {
heap.update(key, score);
reference.retain(|&Reverse((_s, k))| k != key);
reference.push(Reverse((score, key)));
live_keys.insert(key);
}
1 => {
let heap_val = heap.pop_best();
let mut ref_val = None;
while let Some(Reverse((score, key))) = reference.pop() {
if live_keys.contains(&key) {
ref_val = Some((key, score));
live_keys.remove(&key);
break;
}
}
prop_assert_eq!(heap_val, ref_val);
}
2 => {
heap.remove(&key);
live_keys.remove(&key);
}
_ => unreachable!(),
}
prop_assert_eq!(heap.len(), live_keys.len());
prop_assert_eq!(heap.is_empty(), live_keys.is_empty());
}
}
}
}