mod aliases;
#[cfg(test)]
mod tests;
use core::ops::{ControlFlow, Index, IndexMut};
pub use aliases::*;
pub struct Quantile<Num, NumArr, UsizeArr>
where
Num: num_traits::Float,
{
window_size: usize,
probability: Num,
root_heap_idx: usize,
heap: NumArr,
total_elem_count: usize,
elem_to_heap_idx: UsizeArr,
heap_to_elem_idx: UsizeArr,
lower_heap_size: usize,
upper_heap_size: usize,
}
#[derive(Clone, Copy)]
enum Heap {
Lower,
Upper,
}
#[cfg(any(feature = "std", feature = "alloc"))]
impl<Num> Quantile<Num, alloc::vec::Vec<Num>, alloc::vec::Vec<usize>>
where
Num: num_traits::Float + num_traits::FromPrimitive + num_traits::ToPrimitive,
{
#[must_use]
pub fn new(probability: Num, window_size: usize) -> Self {
assert!(window_size > 0_usize, "zero window size");
assert!(probability.is_finite(), "probability must be finite");
assert!(probability >= Num::zero(), "probability must be >= 0");
assert!(probability <= Num::one(), "probability must be <= 1");
let root_heap_idx =
Num::from_usize(window_size - 1).unwrap_or(Num::max_value()) * probability;
let root_heap_idx = root_heap_idx.floor().to_usize().unwrap();
Self {
window_size,
probability,
heap: alloc::vec![Num::nan(); window_size],
heap_to_elem_idx: alloc::vec![0_usize; window_size],
elem_to_heap_idx: alloc::vec![0_usize; window_size],
root_heap_idx,
lower_heap_size: 0_usize,
upper_heap_size: 0_usize,
total_elem_count: 0_usize,
}
}
}
impl<Num, const WIN_SIZE: usize> Quantile<Num, [Num; WIN_SIZE], [usize; WIN_SIZE]>
where
Num: num_traits::Float + num_traits::FromPrimitive + num_traits::ToPrimitive,
{
#[must_use]
pub fn new(probability: Num) -> Self {
assert!(WIN_SIZE > 0_usize, "zero window size");
assert!(probability.is_finite(), "probability must be finite");
assert!(probability >= Num::zero(), "probability must be >= 0");
assert!(probability <= Num::one(), "probability must be <= 1");
let root_heap_idx = Num::from_usize(WIN_SIZE - 1).unwrap_or(Num::max_value()) * probability;
let root_heap_idx = root_heap_idx.floor().to_usize().unwrap();
Self {
window_size: WIN_SIZE,
probability,
heap: [Num::nan(); WIN_SIZE],
heap_to_elem_idx: [0_usize; WIN_SIZE],
elem_to_heap_idx: [0_usize; WIN_SIZE],
root_heap_idx,
lower_heap_size: 0_usize,
upper_heap_size: 0_usize,
total_elem_count: 0_usize,
}
}
}
impl<Num, NumArr, UsizeArr> Quantile<Num, NumArr, UsizeArr>
where
Num: num_traits::Float + num_traits::FromPrimitive + num_traits::ToPrimitive,
NumArr: Index<usize, Output = Num> + IndexMut<usize, Output = Num>,
UsizeArr: Index<usize, Output = usize> + IndexMut<usize, Output = usize>,
{
#[inline]
pub fn add(&mut self, value: Num) {
let elem_idx = self.total_elem_count % self.window_size;
self.total_elem_count += 1;
if self.total_elem_count > self.window_size {
let heap_idx = self.elem_to_heap_idx[elem_idx];
self.insert(heap_idx, elem_idx, value);
self.sift(heap_idx);
return;
}
if self.total_elem_count == 1 {
core::hint::cold_path();
self.insert(self.root_heap_idx, elem_idx, value);
return;
}
let desired_lower_heap_size =
Num::from_usize(self.total_elem_count - 1).unwrap() * self.probability;
let desired_lower_heap_size = desired_lower_heap_size.floor().to_usize().unwrap();
let heap_idx = if self.lower_heap_size < desired_lower_heap_size {
self.lower_heap_size += 1;
self.root_heap_idx - self.lower_heap_size
} else {
self.upper_heap_size += 1;
self.root_heap_idx + self.upper_heap_size
};
self.insert(heap_idx, elem_idx, value);
self.sift(heap_idx);
}
#[inline(always)]
fn insert(&mut self, heap_idx: usize, elem_idx: usize, value: Num) {
self.heap[heap_idx] = value;
self.heap_to_elem_idx[heap_idx] = elem_idx;
self.elem_to_heap_idx[elem_idx] = heap_idx;
}
fn sift(&mut self, mut heap_idx: usize) {
while let ControlFlow::Continue(new_heap_idx) = self.sift_once(heap_idx) {
heap_idx = new_heap_idx;
}
}
#[inline(always)]
fn sift_once(&mut self, heap_idx: usize) -> ControlFlow<(), usize> {
if heap_idx == self.root_heap_idx {
if self.lower_heap_size > 0 {
let new_heap_idx =
self.swap_with_children(Heap::Lower, heap_idx, Some(heap_idx - 1), None);
if new_heap_idx != heap_idx {
return ControlFlow::Continue(new_heap_idx);
}
}
if self.upper_heap_size > 0 {
let new_heap_idx =
self.swap_with_children(Heap::Upper, heap_idx, Some(heap_idx + 1), None);
if new_heap_idx != heap_idx {
return ControlFlow::Continue(new_heap_idx);
}
}
core::hint::cold_path();
return ControlFlow::Break(());
}
let heap = if heap_idx > self.root_heap_idx {
Heap::Upper
} else {
Heap::Lower
};
let heap_parent_idx = match heap {
Heap::Upper => self.root_heap_idx + (heap_idx - self.root_heap_idx) / 2,
Heap::Lower => self.root_heap_idx - (self.root_heap_idx - heap_idx) / 2,
};
if self.should_swap(heap, heap_parent_idx, heap_idx) {
self.swap(heap_idx, heap_parent_idx);
return ControlFlow::Continue(heap_parent_idx);
}
let heap_child_idx1 = (2 * heap_idx).checked_sub(self.root_heap_idx);
let heap_child_idx2 = if heap_idx > self.root_heap_idx {
heap_child_idx1.map(|i| i + 1)
} else {
heap_child_idx1.and_then(|i| i.checked_sub(1))
};
let new_heap_idx =
self.swap_with_children(heap, heap_idx, heap_child_idx1, heap_child_idx2);
if new_heap_idx != heap_idx {
return ControlFlow::Continue(new_heap_idx);
}
ControlFlow::Break(())
}
#[inline(always)]
fn should_swap(&self, heap: Heap, parent_idx: usize, child_idx: usize) -> bool {
match heap {
Heap::Lower => self.heap[parent_idx] < self.heap[child_idx],
Heap::Upper => self.heap[parent_idx] > self.heap[child_idx],
}
}
#[inline(always)]
fn swap(&mut self, heap_idx1: usize, heap_idx2: usize) {
let elem_idx1 = self.heap_to_elem_idx[heap_idx1];
let elem_idx2 = self.heap_to_elem_idx[heap_idx2];
let value1 = self.heap[heap_idx1];
let value2 = self.heap[heap_idx2];
self.heap[heap_idx1] = value2;
self.heap[heap_idx2] = value1;
self.heap_to_elem_idx[heap_idx1] = elem_idx2;
self.heap_to_elem_idx[heap_idx2] = elem_idx1;
self.elem_to_heap_idx[elem_idx1] = heap_idx2;
self.elem_to_heap_idx[elem_idx2] = heap_idx1;
}
#[inline(always)]
fn is_heap_idx(&self, heap_idx: usize) -> bool {
let min_lower_heap_idx = self.root_heap_idx - self.lower_heap_size;
let max_upper_heap_idx = self.root_heap_idx + self.upper_heap_size;
heap_idx >= min_lower_heap_idx && heap_idx <= max_upper_heap_idx
}
fn swap_with_children(
&mut self,
heap: Heap,
heap_idx: usize,
maybe_left_child_idx: Option<usize>,
maybe_right_child_idx: Option<usize>,
) -> usize {
let maybe_left_child_idx = maybe_left_child_idx.filter(|i| self.is_heap_idx(*i));
let maybe_right_child_idx = maybe_right_child_idx.filter(|i| self.is_heap_idx(*i));
match (maybe_left_child_idx, maybe_right_child_idx) {
(None, None) => {
heap_idx
}
(Some(left_child_idx), None) if self.should_swap(heap, heap_idx, left_child_idx) => {
self.swap(heap_idx, left_child_idx);
left_child_idx
}
(Some(_), None) => {
heap_idx
}
(Some(left_child_idx), Some(right_child_idx))
if self.should_swap(heap, heap_idx, left_child_idx)
|| self.should_swap(heap, heap_idx, right_child_idx) =>
{
let swap_child_idx = if self.should_swap(heap, left_child_idx, right_child_idx) {
right_child_idx
} else {
left_child_idx
};
self.swap(heap_idx, swap_child_idx);
swap_child_idx
}
(Some(_), Some(_)) => {
heap_idx
}
(None, Some(_)) => {
unsafe { core::hint::unreachable_unchecked() }
}
}
}
#[inline]
#[must_use]
pub fn get(&self) -> Num {
if self.total_elem_count == 0 {
core::hint::cold_path();
return Num::nan();
}
if self.total_elem_count == 1 || self.upper_heap_size == 0 {
core::hint::cold_path();
return self.heap[self.root_heap_idx];
}
let a = self.heap[self.root_heap_idx];
let b = self.heap[self.root_heap_idx + 1];
let n = self.total_elem_count.min(self.window_size);
let h = Num::from_usize(n - 1).unwrap() * self.probability;
a + (h - h.floor()) * (b - a)
}
}