use super::*;
use test_log::test;
fn cmp_u32(a: &u32, b: &u32) -> Ordering {
a.cmp(b)
}
fn collect<F: Fn(&u32, &u32) -> Ordering>(mut t: LoserTree<u32, F>) -> Vec<u32> {
let mut out = Vec::new();
while let Some(v) = t.pop_min() {
out.push(v);
}
out
}
#[test]
fn empty_tree() {
let t: LoserTree<u32, fn(&u32, &u32) -> Ordering> =
LoserTree::build(alloc::vec![None, None, None], cmp_u32);
assert!(t.is_empty());
assert_eq!(t.active_count(), 0);
assert_eq!(t.peek_min(), None);
assert_eq!(t.winner_slot(), None);
}
#[test]
fn single_slot() {
let mut t = LoserTree::build(alloc::vec![Some(42_u32)], cmp_u32);
assert!(!t.is_empty());
assert_eq!(t.peek_min(), Some(&42));
assert_eq!(t.pop_min(), Some(42));
assert!(t.is_empty());
assert_eq!(t.pop_min(), None);
}
#[test]
fn drain_in_order() {
let t = LoserTree::build(alloc::vec![Some(3_u32), Some(1), Some(4), Some(2)], cmp_u32);
assert_eq!(collect(t), [1, 2, 3, 4]);
}
#[test]
fn non_pow2_padding() {
let t = LoserTree::build(
alloc::vec![Some(50_u32), Some(10), Some(40), Some(20), Some(30)],
cmp_u32,
);
assert_eq!(collect(t), [10, 20, 30, 40, 50]);
}
#[test]
fn replace_min_stays_winner_when_still_smallest() {
let mut t = LoserTree::build(
alloc::vec![Some(1_u32), Some(100), Some(200), Some(300)],
cmp_u32,
);
assert_eq!(t.replace_min(2), 1);
assert_eq!(t.peek_min(), Some(&2));
assert_eq!(t.winner_slot(), Some(0));
assert_eq!(t.replace_min(3), 2);
assert_eq!(t.peek_min(), Some(&3));
assert_eq!(t.winner_slot(), Some(0));
}
#[test]
fn replace_min_changes_winner() {
let mut t = LoserTree::build(alloc::vec![Some(1_u32), Some(5), Some(3), Some(7)], cmp_u32);
assert_eq!(t.winner_slot(), Some(0));
assert_eq!(t.replace_min(4), 1);
assert_eq!(t.peek_min(), Some(&3)); assert_eq!(t.winner_slot(), Some(2));
assert_eq!(t.replace_min(6), 3);
assert_eq!(t.peek_min(), Some(&4)); assert_eq!(t.winner_slot(), Some(0));
}
#[test]
fn pop_min_then_drain() {
let mut t = LoserTree::build(
alloc::vec![Some(10_u32), Some(20), Some(5), Some(15)],
cmp_u32,
);
assert_eq!(t.pop_min(), Some(5));
assert_eq!(t.active_count(), 3);
assert_eq!(t.peek_min(), Some(&10));
assert_eq!(collect(t), [10, 15, 20]);
}
#[test]
fn mixed_replace_and_pop() {
let mut t = LoserTree::build(alloc::vec![Some(1_u32), Some(2), Some(3), Some(4)], cmp_u32);
assert_eq!(t.replace_min(5), 1); assert_eq!(t.replace_min(6), 2); assert_eq!(t.pop_min(), Some(3));
assert_eq!(t.pop_min(), Some(4));
assert_eq!(t.pop_min(), Some(5));
assert_eq!(t.pop_min(), Some(6));
assert!(t.is_empty());
}
#[test]
fn reverse_comparator_gives_max_tree() {
let cmp = |a: &u32, b: &u32| b.cmp(a);
let mut t = LoserTree::build(alloc::vec![Some(1_u32), Some(4), Some(2), Some(3)], cmp);
assert_eq!(t.peek_min(), Some(&4)); assert_eq!(t.pop_min(), Some(4));
assert_eq!(t.pop_min(), Some(3));
assert_eq!(t.pop_min(), Some(2));
assert_eq!(t.pop_min(), Some(1));
}
#[test]
fn deterministic_tiebreak_by_cmp() {
let cmp = |a: &(u32, usize), b: &(u32, usize)| (a.0, a.1).cmp(&(b.0, b.1));
let mut t = LoserTree::build(
alloc::vec![Some((5_u32, 0)), Some((5, 1)), Some((5, 2)), Some((5, 3)),],
cmp,
);
assert_eq!(t.winner_slot(), Some(0));
let mut order = Vec::new();
while let Some((_, idx)) = t.pop_min() {
order.push(idx);
}
assert_eq!(order, [0, 1, 2, 3]);
}
#[test]
fn random_inputs_match_sorted_reference() {
use rand::SeedableRng;
use rand::seq::SliceRandom;
let mut rng = rand::rngs::StdRng::seed_from_u64(0xC0DE_F00D);
for n in [1_usize, 2, 3, 7, 8, 9, 31, 32, 33] {
for trial in 0..32 {
let mut all: Vec<u32> = (0..(n as u32 * 4)).collect();
all.shuffle(&mut rng);
let mut buckets: Vec<Vec<u32>> = (0..n).map(|_| Vec::new()).collect();
for (i, v) in all.iter().enumerate() {
#[expect(clippy::indexing_slicing, reason = "i % n always < n")]
buckets[i % n].push(*v);
}
for b in &mut buckets {
b.sort_unstable();
}
let mut reference = all.clone();
reference.sort_unstable();
let mut iters: Vec<std::vec::IntoIter<u32>> =
buckets.into_iter().map(IntoIterator::into_iter).collect();
let initial: Vec<Option<u32>> = iters.iter_mut().map(Iterator::next).collect();
let mut t = LoserTree::build(initial, cmp_u32);
let mut out = Vec::with_capacity(reference.len());
while let Some(slot) = t.winner_slot() {
#[expect(clippy::indexing_slicing, reason = "slot < n by construction")]
if let Some(next_val) = iters[slot].next() {
out.push(t.replace_min(next_val));
} else {
out.push(t.pop_min().unwrap());
}
}
assert_eq!(out, reference, "n={n} trial={trial}");
}
}
}