use core::cmp::Ordering;
use crate::node::{
Node,
visitor::{Step, Visitor},
};
pub(crate) struct OrdIndexVisitor<'a, 'b, T, Q: ?Sized, F: Fn(&T, &Q) -> Ordering, const N: usize> {
current: &'a Node<T, N>,
level: usize,
found: bool,
target: &'b Q,
cmp: F,
rank: usize,
}
impl<'a, 'b, T, Q: ?Sized, F: Fn(&T, &Q) -> Ordering, const N: usize>
OrdIndexVisitor<'a, 'b, T, Q, F, N>
{
pub(crate) fn new(head: &'a Node<T, N>, target: &'b Q, cmp: F) -> Self {
Self {
current: head,
level: head.level(),
found: false,
target,
cmp,
rank: 0,
}
}
pub(crate) fn rank(&self) -> usize {
self.rank.saturating_sub(1)
}
}
impl<'a, T, Q: ?Sized, F: Fn(&T, &Q) -> Ordering, const N: usize> Visitor
for OrdIndexVisitor<'a, '_, T, Q, F, N>
{
type NodeRef = &'a Node<T, N>;
fn current(&self) -> Self::NodeRef {
self.current
}
fn level(&self) -> usize {
self.level
}
fn found(&self) -> bool {
self.found
}
fn step(&mut self) -> Step<Self::NodeRef> {
if self.found {
return Step::FoundTarget;
}
for (level, maybe_link) in (0..self.level).zip(self.current.links()).rev() {
if let Some(link) = maybe_link {
let node: &'a Node<T, N> = unsafe { link.node().as_ref() };
let ord = node
.value()
.map_or(Ordering::Less, |v| (self.cmp)(v, self.target));
if ord == Ordering::Less {
self.current = node;
self.rank = self.rank.saturating_add(link.distance().get());
self.level = level.saturating_add(1);
return Step::Advanced(self.current);
}
}
}
self.level = 0;
if let Some(next) = self.current.next_as_ref() {
let ord = next
.value()
.map_or(Ordering::Less, |v| (self.cmp)(v, self.target));
if ord == Ordering::Greater {
return Step::Exhausted;
}
self.current = next;
self.rank = self.rank.saturating_add(1);
self.found = ord == Ordering::Equal;
return Step::Advanced(self.current);
}
Step::Exhausted
}
}
#[expect(
clippy::undocumented_unsafe_blocks,
reason = "test code, safety guarantees can be relaxed"
)]
#[cfg(test)]
mod tests {
use core::ptr::NonNull;
use anyhow::Result;
use pretty_assertions::assert_eq;
use rstest::rstest;
use super::OrdIndexVisitor;
use crate::node::{
Node,
tests::{MAX_LEVELS, skiplist},
visitor::{Step, Visitor},
};
#[rstest]
fn find_existing_value(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let mut visitor = OrdIndexVisitor::new(unsafe { head.as_ref() }, &30_u8, Ord::cmp);
let found = visitor.traverse();
assert!(visitor.found());
assert_eq!(found.and_then(|n| n.value().copied()), Some(30));
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn find_first_value(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let mut visitor = OrdIndexVisitor::new(unsafe { head.as_ref() }, &10_u8, Ord::cmp);
let found = visitor.traverse();
assert!(visitor.found());
assert_eq!(found.and_then(|n| n.value().copied()), Some(10));
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn find_last_value(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let mut visitor = OrdIndexVisitor::new(unsafe { head.as_ref() }, &40_u8, Ord::cmp);
let found = visitor.traverse();
assert!(visitor.found());
assert_eq!(found.and_then(|n| n.value().copied()), Some(40));
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn value_not_found(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let mut visitor = OrdIndexVisitor::new(unsafe { head.as_ref() }, &25_u8, Ord::cmp);
let found = visitor.traverse();
assert!(!visitor.found());
assert!(found.is_none());
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn value_beyond_list(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let mut visitor = OrdIndexVisitor::new(unsafe { head.as_ref() }, &99_u8, Ord::cmp);
let found = visitor.traverse();
assert!(!visitor.found());
assert!(found.is_none());
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn rank_first_element(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let mut visitor = OrdIndexVisitor::new(unsafe { head.as_ref() }, &10_u8, Ord::cmp);
visitor.traverse();
assert!(visitor.found());
assert_eq!(visitor.rank(), 0);
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn rank_second_element(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let mut visitor = OrdIndexVisitor::new(unsafe { head.as_ref() }, &20_u8, Ord::cmp);
visitor.traverse();
assert!(visitor.found());
assert_eq!(visitor.rank(), 1);
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn rank_third_element(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let mut visitor = OrdIndexVisitor::new(unsafe { head.as_ref() }, &30_u8, Ord::cmp);
visitor.traverse();
assert!(visitor.found());
assert_eq!(visitor.rank(), 2);
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn rank_last_element(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let mut visitor = OrdIndexVisitor::new(unsafe { head.as_ref() }, &40_u8, Ord::cmp);
visitor.traverse();
assert!(visitor.found());
assert_eq!(visitor.rank(), 3);
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn exhausted_when_target_out_of_range(
skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>,
) -> Result<()> {
let head = skiplist?;
let mut visitor = OrdIndexVisitor::new(unsafe { head.as_ref() }, &99_u8, Ord::cmp);
loop {
let s = visitor.step();
match s {
Step::Advanced(_) => (),
Step::Exhausted => {
assert!(!visitor.found());
break;
}
Step::FoundTarget => panic!("should not find target 99"),
}
}
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn step_after_found(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let mut visitor = OrdIndexVisitor::new(unsafe { head.as_ref() }, &20_u8, Ord::cmp);
visitor.traverse();
assert!(visitor.found());
assert!(matches!(visitor.step(), Step::FoundTarget));
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
}