#![expect(
clippy::pub_use,
reason = "re-exporting public types from private submodules"
)]
use core::ptr::NonNull;
use crate::{
level_generator::{LevelGenerator, geometric::Geometric},
node::{
Node,
visitor::{IndexMutVisitor, Visitor},
},
};
mod access;
mod filter;
mod insert_remove;
mod iter;
mod push_pop;
mod structural;
mod traits;
pub use iter::{Drain, ExtractIf, IntoIter, Iter, IterMut};
const DEFAULT_P: f64 = 0.5;
pub struct SkipList<T, const N: usize = 16, G: LevelGenerator = Geometric> {
head: NonNull<Node<T, N>>,
tail: Option<NonNull<Node<T, N>>>,
len: usize,
generator: G,
}
unsafe impl<T: Send, G: LevelGenerator + Send, const N: usize> Send for SkipList<T, N, G> {}
unsafe impl<T: Sync, G: LevelGenerator + Sync, const N: usize> Sync for SkipList<T, N, G> {}
impl<T, const N: usize> SkipList<T, N, Geometric> {
#[inline]
#[must_use]
pub fn new() -> Self {
Self::with_level_generator(Geometric::default())
}
#[inline]
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
let levels = if capacity <= 1 {
1
} else {
#[expect(
clippy::as_conversions,
reason = "usize::BITS is a u32 value, but should always a valid usize value, \
even if usize is smaller than 32 bits."
)]
let ceil_log2 =
usize::BITS.saturating_sub(capacity.saturating_sub(1).leading_zeros()) as usize;
ceil_log2.saturating_add(1)
};
#[expect(
clippy::expect_used,
reason = "`levels` is always >= 1 and DEFAULT_P is a valid probability"
)]
let generator = Geometric::new(levels.min(N), DEFAULT_P)
.expect("`levels >= 1` and `DEFAULT_P` are valid Geometric parameters");
Self::with_level_generator(generator)
}
}
impl<T, G: LevelGenerator, const N: usize> SkipList<T, N, G> {
#[inline]
#[must_use]
pub fn with_level_generator(generator: G) -> Self {
let max_levels = generator.total();
debug_assert!(
max_levels <= N,
"generator.total() ({max_levels}) exceeds node capacity ({N})"
);
let head = unsafe {
NonNull::new_unchecked(Box::into_raw(Box::new(Node::new(max_levels.min(N)))))
};
Self {
head,
tail: None,
len: 0,
generator,
}
}
#[inline]
fn head_ref(&self) -> &Node<T, N> {
unsafe { self.head.as_ref() }
}
#[inline]
fn head_mut(&mut self) -> &mut Node<T, N> {
unsafe { self.head.as_mut() }
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[expect(
clippy::expect_used,
reason = "index < self.len is validated by the debug_assert and all callers; \
the expect fires only on internal invariant violations"
)]
#[inline]
fn node_ptr_at(&self, index: usize) -> NonNull<Node<T, N>> {
debug_assert!(
index < self.len,
"index {index} out of bounds (len={})",
self.len
);
IndexMutVisitor::new(self.head, index.saturating_add(1))
.traverse()
.expect("node at index exists because index < self.len")
}
}
impl<T, const N: usize> Default for SkipList<T, N, Geometric> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<T, G: LevelGenerator, const N: usize> Drop for SkipList<T, N, G> {
#[inline]
fn drop(&mut self) {
unsafe { drop(Box::from_raw(self.head.as_ptr())) };
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::SkipList;
use crate::level_generator::geometric::Geometric;
#[test]
fn new_is_empty() {
let list = SkipList::<i32>::new();
assert!(list.is_empty());
}
#[test]
fn new_len_zero() {
let list = SkipList::<i32>::new();
assert_eq!(list.len(), 0);
}
#[test]
fn with_capacity_is_empty() {
let list = SkipList::<i32>::with_capacity(8);
assert!(list.is_empty());
assert_eq!(list.len(), 0);
}
#[test]
fn with_capacity_zero_clamped() {
let list = SkipList::<i32>::with_capacity(0);
assert!(list.is_empty());
}
#[test]
fn with_level_generator_custom() {
let g = Geometric::new(4, 0.25).expect("valid parameters");
let list = SkipList::<String>::with_level_generator(g);
assert!(list.is_empty());
assert_eq!(list.len(), 0);
}
#[test]
fn default_is_empty() {
let list = SkipList::<i32>::default();
assert_eq!(list.len(), 0);
assert!(list.is_empty());
}
}