dsa_rust/associative/
skip_list.rs

1// NOTE: This is a de-prioritized WIP and may be on ice for a while,
2// please pardon the mess
3#![allow(warnings, clippy::all)]
4
5/*! A naive attempt at implementing a skip list
6
7# About
8The idea of a skip list is to implement sorted structures with _expected O(log(n))_ time complexity for search, insert, and removal operations. This provides a significant advantage over sorted array-based lists, which have _worst-case O(n)_ removal (average _O(n/2)_) temporal performance. Skip lists are also simpler than self-balancing tree structures and provide an easier and finer-grained control when adapting these structures for concurrent operations. There is a reason Java's `concurrentSkipListMap` is so popular.
9
10This structure specifically implements the [SkipListMap<K, V>] structure which provides a map implementation, as the name suggests. See the [SkipListSet<T>] module for a set implementation.
11
12# Design
13At it's root is the logic behind the [doubly-linked list](crate::sequences::doubly_linked_list) which squarely places this list into "advanced Rust" territory. Later, when we implement concurrency, this will propel the skip list map to "expert Rust" territory.
14
15The list features a dynamic max height `h` that is logarithmically proportional to the number of elements in the list `n` such that _h = log(n)_ in the expected case. The list does not _rebuild_ any towers (besides the head node) at insert, but simply changes the maximum potential height for those towers to grow. The logarithmic growth ensures that the average search, insertion, and deletion operations remain efficient, typically with expected O(log n) time complexity.
16
17## The Search Algorithm
18The point of the search algorithm is to find the first node (or handle/position) `p` in skip list `S` that represents the largest comparable value <= to the search key `k`. This algorithm can be broken into two steps:
19Step 1) loop`let candidate = p.peek()`, if `candidate >= k`, return `p`, otherwise move to `p.next()`. Repeat until `p.peek()` >= `k`.
20Step 2) Drop down a level: If `S.below(p) == 0` you're at the lowest level and the search terminates.
21
22## Visual Examples
23An initial, empty skip list with one level and no data:
24```text
25S0: None -> None
26```
27
28Inserting the first node triggers an automatic tower level, even if it ends up empty. This provides the algorithm with a starting point:
29```text
30S1: None ---------> None
31S0: None -> [ 5] -> None
32```
33
34Each subsequent addition may add more tower levels up to log(n) where `n` is the number of elements in the list. This example illustrates a fully built skip list:
35```text
36S3: None ---------> [*2] ---------------------------------------------> [*9] ----------> None
37S2: None ---------> [*2] ------------------------------> [*7] --------> [*9] ----------> None
38S1: None ---------> [*2] --------> [*4] ---------------> [*7] --------> [*9] -> [*10] -> None
39S0: None -> [ 1] -> [ 2] -> [3] -> [ 4] -> [5] -> [6] -> [ 7] -> [8] -> [ 9] -> [ 10] -> None
40```
41
42For example, at level 2 you could traverse the list as pointers to [2, 7, 9]. The distinction here is that all of the node data is stored in one location, with only pointers being duplicated and populating tower lists.
43
44# Example code
45```rust
46```
47*/
48use std::fmt::Debug;
49
50// Creates a raw pointer to some Node
51type Link<T> = Option<*mut SkipNode<T>>;
52
53#[derive(Debug)]
54struct SkipNode<T>
55where
56    T: Ord + PartialEq,
57{
58    data: Option<T>, // Each allocated node MUST contain data, only sentinels can be None
59    level: usize,    // The current node's level
60    tower: Vec<Link<T>>, // A list of "next" nodes for each level in the node's tower
61    prev: Link<T>,   // The prev node in S0
62    next: Link<T>,   // The next node in S0
63}
64impl<T> SkipNode<T>
65where
66    T: Debug + Ord,
67{
68    fn new_sen() -> Self {
69        SkipNode {
70            data: None,
71            level: 0,
72            tower: Vec::new(),
73            prev: None,
74            next: None,
75        }
76    }
77
78    /// Returns `true` if the data for the given node is None, which
79    /// marks it as a sentinel node.
80    fn is_sen(&self) -> bool {
81        self.data.is_none()
82    }
83}
84
85#[derive(Debug)]
86pub struct SkipList<T>
87where
88    T: Ord + PartialEq,
89{
90    head: Link<T>,
91    height: usize, // 0-indexed dynamic max height value
92    size: usize,   // Number of Some nodes in the list (no ghosts! 👻)
93}
94impl<T> SkipList<T>
95where
96    T: Debug + PartialEq + Ord,
97{
98    /// Creates a new `SkipList` with a single sentinel node.
99    pub fn new() -> Self {
100        SkipList {
101            //data: Some(Box::into_raw(Box::new(SkipNode::new_sen()))),
102            head: None, // Sentinel node is always None
103            height: 0,
104            size: 0,
105        }
106    }
107
108    /// Returns true if there is only an empty sentinel node in the list
109    pub fn is_empty(&self) -> bool {
110        self.head.is_none()
111    }
112
113    /// Returns a link to the node that is guaranteed to be <= the given value, allowing placement
114    /// into the "next" node slot.
115    ///
116    /// WARNING: Unimplemented
117    fn skip_search(&self, element: &T) -> Option<Link<T>> {
118        // Case 1) The list is empty
119        //if self.is_empty() {
120        //    return None;
121        //}
122
123        // Case 2) The list contains elements
124
125        // Search only if there are real nodes in the list
126        if let Some(node) = self.head {
127            // Start at the highest level of the head node
128            // SAFETY: The list always has a head node with a Vec of Links
129            let link = unsafe { (&(*node).tower)[self.height] };
130
131            // Follow the link to the first node
132            if let Some(value) = link {
133                //
134            }
135
136            let mut node_data = unsafe { (&(*node).tower)[self.height].unwrap().as_ref().unwrap() };
137
138            // The actual skip search algorithm:
139            let mut result = std::cmp::Ordering::Less;
140
141            // let mut level = self.height;  // Start at the structure's max height
142            //
143            // while level >= 0 {
144            //     // 1) Scan forward at current level
145            //     while let Some(next) = node.next[level] {
146            //         if next.key < k {
147            //             node = next;
148            //         } else {
149            //             break;
150            //         }
151            //     }
152            //
153            //     // 2) Drop down a level
154            //     if level == 0 {
155            //         break;
156            //     } else {
157            //         level -= 1;
158            //     }
159            // }
160            //
161            // return node;  // Node such that node.key <= k
162
163            Some(self.head) // Algorithmic placeholder
164        } else {
165            None
166        }
167    }
168
169    pub fn insert(&mut self, entry: T) {
170        // Find insertion point
171        let insert_after = self.skip_search(&entry);
172
173        // Allocate
174        let node = Box::new(entry);
175        let node_ptr = Box::into_raw(node);
176
177        // Build new entry tower
178        // let n = Self::tower_roll();
179
180        // Identify all
181        //
182    }
183}
184
185#[cfg(test)]
186mod tests {
187
188    use super::*;
189
190    #[test]
191    fn first() {
192        let sk: SkipList<usize> = SkipList::new();
193    }
194}