#[derive(Clone)]
pub(crate) struct ExactOrderStats {
blocks: Vec<Vec<f64>>,
len: usize,
block_size: usize,
}
impl ExactOrderStats {
pub(crate) fn new(capacity: usize) -> Self {
let block_size = ((capacity as f64).sqrt() as usize).max(64);
Self {
blocks: Vec::new(),
len: 0,
block_size,
}
}
pub(crate) fn from_unsorted(mut values: Vec<f64>) -> Self {
values.sort_unstable_by(f64::total_cmp);
Self::from_sorted(values)
}
pub(crate) fn from_sorted(values: Vec<f64>) -> Self {
debug_assert!(
values
.windows(2)
.all(|pair| !pair[0].total_cmp(&pair[1]).is_gt()),
"order-statistics input must be sorted"
);
let mut stats = Self::new(values.len());
stats.len = values.len();
let mut values = values.into_iter();
loop {
let block: Vec<_> = values.by_ref().take(stats.block_size).collect();
if block.is_empty() {
break;
}
stats.blocks.push(block);
}
stats
}
pub(crate) fn len(&self) -> usize {
self.len
}
pub(crate) fn is_empty(&self) -> bool {
self.len == 0
}
pub(crate) fn insert(&mut self, value: f64) {
self.len += 1;
if self.blocks.is_empty() {
self.blocks.push(vec![value]);
return;
}
let block_index = self
.blocks
.partition_point(|block| {
block
.last()
.is_some_and(|candidate| candidate.total_cmp(&value).is_lt())
})
.min(self.blocks.len() - 1);
let block = &mut self.blocks[block_index];
let position = block.partition_point(|candidate| candidate.total_cmp(&value).is_lt());
block.insert(position, value);
if block.len() > 2 * self.block_size {
let right = block.split_off(block.len() / 2);
self.blocks.insert(block_index + 1, right);
}
}
pub(crate) fn remove(&mut self, value: f64) -> bool {
let block_index = self.blocks.partition_point(|block| {
block
.last()
.is_some_and(|candidate| candidate.total_cmp(&value).is_lt())
});
let Some(block) = self.blocks.get_mut(block_index) else {
return false;
};
let position = block.partition_point(|candidate| candidate.total_cmp(&value).is_lt());
if block
.get(position)
.is_none_or(|candidate| !candidate.total_cmp(&value).is_eq())
{
return false;
}
block.remove(position);
self.len -= 1;
if block.is_empty() {
self.blocks.remove(block_index);
}
true
}
pub(crate) fn kth(&self, mut index: usize) -> f64 {
for block in &self.blocks {
if index < block.len() {
return block[index];
}
index -= block.len();
}
unreachable!("order-statistics index out of bounds")
}
pub(crate) fn values_at(&self, ranks: &[usize], out: &mut [f64]) {
debug_assert_eq!(ranks.len(), out.len());
debug_assert!(ranks.windows(2).all(|pair| pair[0] < pair[1]));
debug_assert!(ranks.last().is_none_or(|&rank| rank < self.len));
let mut rank_index = 0;
let mut block_start = 0;
for block in &self.blocks {
let block_end = block_start + block.len();
while rank_index < ranks.len() && ranks[rank_index] < block_end {
out[rank_index] = block[ranks[rank_index] - block_start];
rank_index += 1;
}
block_start += block.len();
if rank_index == ranks.len() {
return;
}
}
debug_assert_eq!(rank_index, ranks.len());
}
pub(crate) fn count_lt(&self, value: f64) -> usize {
let mut count = 0;
for block in &self.blocks {
if block
.last()
.is_some_and(|candidate| candidate.total_cmp(&value).is_lt())
{
count += block.len();
continue;
}
count += block.partition_point(|candidate| candidate.total_cmp(&value).is_lt());
break;
}
count
}
pub(crate) fn count_le(&self, value: f64) -> usize {
let mut count = 0;
for block in &self.blocks {
if block
.last()
.is_some_and(|candidate| !candidate.total_cmp(&value).is_gt())
{
count += block.len();
continue;
}
count += block.partition_point(|candidate| !candidate.total_cmp(&value).is_gt());
break;
}
count
}
pub(crate) fn first(&self) -> f64 {
self.blocks.first().unwrap().first().copied().unwrap()
}
pub(crate) fn last(&self) -> f64 {
self.blocks.last().unwrap().last().copied().unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bulk_build_and_rank_queries_preserve_duplicates() {
let stats = ExactOrderStats::from_unsorted(vec![3.0, 1.0, 2.0, 2.0]);
assert_eq!(stats.len(), 4);
assert_eq!(stats.kth(0), 1.0);
assert_eq!(stats.kth(1), 2.0);
assert_eq!(stats.kth(2), 2.0);
assert_eq!(stats.kth(3), 3.0);
assert_eq!(stats.count_lt(2.0), 1);
assert_eq!(stats.count_le(2.0), 3);
let mut values = [0.0; 3];
stats.values_at(&[0, 2, 3], &mut values);
assert_eq!(values, [1.0, 2.0, 3.0]);
}
#[test]
fn insert_and_remove_update_order_statistics() {
let mut stats = ExactOrderStats::new(4);
for value in [3.0, 1.0, 2.0, 2.0] {
stats.insert(value);
}
assert!(stats.remove(2.0));
assert!(!stats.remove(4.0));
assert_eq!(stats.len(), 3);
assert_eq!(stats.kth(0), 1.0);
assert_eq!(stats.kth(1), 2.0);
assert_eq!(stats.kth(2), 3.0);
}
#[test]
fn total_order_distinguishes_signed_zero() {
let stats = ExactOrderStats::from_unsorted(vec![0.0, -0.0]);
assert_eq!(stats.kth(0).to_bits(), (-0.0_f64).to_bits());
assert_eq!(stats.kth(1).to_bits(), 0.0_f64.to_bits());
assert_eq!(stats.count_lt(0.0), 1);
assert_eq!(stats.count_le(-0.0), 1);
}
}