dsa_rust/sequences/indexed_skip_list.rs
1/*! A safe, indexed skip list
2
3# About
4Skip lists are sorted, probabalistic structures made up of logically stacked lists of varying length to allow for truncated _O(log(n))_ navigation. Canonically linked lists are built from doubly-linked lists, but this is not a defining characteristic of the ADT. Regardless of the base list representation used, the navigational algorithm results in what is essentially a logical linked list.
5
6Properly implemented skip lists provide _O(log(n))_ expected time complexity for search, insert, and removal operations. This provides a significant advantage over keeping sorted array- or link-based list invariants, which have _worst-case O(n)_ removal (average _O(n/2)_) temporal performance. Skip lists are also simpler than self-balancing tree structures, which are commonly used for sorted list and map structures. Skip lists also generally provide easier and finer-grained control when adapted for concurrent operations. There is a reason Java's `concurrentSkipListMap` is so popular.
7
8# Design
9This design uses `Vec`-backed storage for [SkipNode]s that contain a list (tower) of "next" values, and a single "previous" value that represent indexes within the backing vector.
10
11The 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 logarithmic growth ensures that the average search, insertion, and deletion operations remain efficient, typically with expected _O(log(n))_ time complexity.
12
13William Pugh's <a href="https://15721.courses.cs.cmu.edu/spring2018/papers/08-oltpindexes1/pugh-skiplists-cacm1990.pdf" target="_blank" rel="noopener noreferrer">original paper</a> from 1990 conveniently spells out random level, search, insert, and remove operations as pseudocode that is used to guide this module's design. Note that the pseudocode is modified from the original paper to fit the notation convention present in this module (that is, CLRS-style with ASCII characters), but is otherwise unchanged from the original paper.
14
15## The Search Algorithm
16The search algorithm as its presented in the original paper generalizes some _public-facing operation_ for list search which returns either the node representing the `value` associated with a `search_key` or a failure/nil/None value to indicate that the `search_key` is not in the list.
17```text
180 Search(list, search_key)
191 x = list.header
202 // loop invariant: x.key < search_key
213 for i = list.level downto 1 do
224 while x.forward[i].key < search_key do
235 x = x.forward[i]
246 // x.key < search_key <= x.forward[1].key
257 x = x.forward[1]
268 if x.key == search_key then return x.value
279 else return failure
28```
29It makes sense to split this algorithm into two different pieces, roughly separated at line 5. In this implementation, the first part represents a private search operation `skip_search(e)` that returns a position that is strictly < `search_key`. This sub-routine is then re-used by the `get(e)`, `contains(e)`, `insert(e)`, and `remove(e)` operations. If the list is empty, `skip_search(e)` returns `0`. An empty list contains a single sentinel node, so there is _always_ a previous node to insert a value, even if its the sentinel.
30
31The second part of the algorithm simply represents a forward iteration and an equality check with the supplied `search_key`. This second phase is represented in a public `get(e)` operation that returns the value associated with the `search_key`, if it exists in the list. The equality check is crucial to determine whether the "next" node is actually the one being searched for.
32
33## Insertion & Removal Algorithms
34The insertion and deletion algorithms re-use much of the search algorithm's first phase, so they can be abstracted into [SkipList::skip_search] operations which return the node that is strictly smaller than the "search_key", which in this case is a new entry. The rest of the algorithm creates a new `SkipNode`, generates the "tower" len with a random number generator, populates the next node array for each level, and sets a singular previous node position.
35The
36```text
37 0 Insert(list, search_key, newValue)
38 1 local update[1..MaxLevel]
39 2 x = list.header
40 3 for i = list.level downto 1 do
41 4 while x.forward[i].key < search_key do
42 5 x = x.forward[i]
43 6 // x.key < search_key <= x.forward[i].key
44 7 update[i] = x
45
46 8 x = x.forward[1]
47 9 if x.key = search_key then x.value = newValue
4810 else
4911 lvl = randomLevel()
5012 if lvl > list.level then
5113 for i = list.level + 1 to lvl do
5214 update[i] = list.header
5315 list.level = lvl
5416 x = makeNode(lvl, search_key, value)
5517 for i = 1 to level do
5618 x.forward[i] = update[i].forward[i]
5719 update[i].forward[i] = x
58```
59
60This structure uses a contiguous backing structure instead of stable pointers/Position objects. As a result the list cannot strictly maintain the original design's asymptotics. The major advantage of linked lists is _O(1)_ node insertion/removal if a handle exists to the node. Contiguous lists generally require either _O(n)_ moves for insertion/removal of arbitrary elements. However, there are two options to deal with this; either use a [Vec::swap_remove] operation for _O(1)_ removals without wasting space, or using a free list to identify and fill holes after removal. For simplicity, this structure uses the first approach, meaning that indexes are _not_ stable, and as such are not surfaced in the public API. This design keeps the space requirements in check, but changes the canonical _O(log(n))_ removal time to _O(n * height)_, which is _O(n * log(n)) expected_, and _O(n^2)_ worst case (even though the list's height is technically capped).
61
62Pugh's original removal algorithm (which is altered slightly in this implementation):
63```text
64 0 Delete(list, search_key)
65 1 local update[1..MaxLevel]
66 2 x = list.header
67 3 for i = list.level downto 1 do
68 4 while x.forward[i].key < search_key do
69 5 x = x.forward[i]
70 6 update[i] = x
71 7 x = x.forward[1]
72 8 if x.key = search_key then
73 9 for i = 1 to list.level do
7410 if update[i].forward[i] != x then break
7511 update[i].forward[i] = x.forward[i]
7612 free(x)
7713 while list.level > 1 and
7814 list.header.forward[list.level] == NIL do
7915 list.level = list.level – 1
80```
81The `remove(e)` as it exists in this module:
82```text
83 0 Delete(list, searchKey)
84
85 1 local update[0..MaxLevel] = FindPredecessors(list, searchKey)
86 2 target = update[0].forward[0]
87
88 3 // Early return for elements not in the list
89 4 if target = NIL or target.key != searchKey then
90 5 return failure
91 6 last = list.nodes.last
92
93 7 // unlink from skip structure
94 8 for i = 0 to list.level - 1 do
95 9 if update[i].forward[i] = target then
9610 update[i].forward[i] = target.forward[i]
97
9811 if target.forward[0] != NIL then
9912 target.forward[0].prev = target.prev
100
10113 removed = swap_remove(list.nodes, target)
102
10314 // fix relocated node (if any)
10415 if target < list.nodes.length then
105
10616 for each node in list.nodes do
10717 replace all forward pointers = last with target
108
10918 if node at target has forward[0] != NIL then
11019 forward[0].prev = target
111
11220 if node at target.prev = last then
11321 node.prev = target
114
11522 while list.level > 1 and list.header.forward[list.level - 1] = NIL do
11623 list.level -= 1
117
11824 return removed.value
119```
120
121## Visual Examples
122An initial, empty skip list with one level and no data:
123```text
124S0: HEAD -> None
125```
126
127Inserting the first node triggers an automatic tower level, even if it ends up empty. This provides the algorithm with a starting point:
128```text
129S1: HEAD ----------> None
130S0: HEAD -> [ 5 ] -> None
131```
132
133After inserting `['a', 'c', 'e', 'd', 'b', 'i', 'g', 'h', 'f']`, the list's `SkipNodes` might contain the following towers.
134```text
135HEAD[0]: [1, 2, 9, 7]
136a[1]: [5]
137c[2]: [4, 4]
138e[3]: [9]
139d[4]: [3, 9]
140b[5]: [2]
141i[6]: []
142g[7]: [8, 6, 6]
143h[8]: [6]
144f[9]: [7, 7, 7]
145```
146Note that its always possible to tell the last item in the list because its tower is empty. This makes sense, because the last element within the sorted arrangement can only point to `None`. As you can see by the index notation on the left-hand side of the table, the backing structure retains the insertion order; the backing structure remains unsorted.
147
148The structure simply appends elements to the backing structure, so when printed the list retains its insertion order, not its sorted arrangement. As a result, the towers appear to contain rather nonsensical values. However, if you follow the indexes from the `HEAD` node, and re-arrange the nodes into _lexicographically sorted order_, which is what the navigational algorithms in the skiplist achieve, you get the following towers.
149```text
150HEAD[0]: [1, 2, 9, 7]
151a[1]: [5]
152b[5]: [2]
153c[2]: [4, 4]
154d[4]: [3, 9]
155e[3]: [9]
156f[9]: [7, 7, 7]
157g[7]: [8, 6, 6]
158h[8]: [6]
159i[6]: []
160```
161
162When you rotate the mapping 90 degrees you can start to visualize the skip list layers as logically linked lists formed by "next" element indexes.
163```text
164L3: [ g[7] ] -> None
165L2: [ f[9] ] -> [ g[7] ] -> [ i[6] ] -> None
166L1: [ c[2] ] -> [ d[4] ] -> [ f[9] ] -> [ g[7] ] -> [ i[6] ] -> None
167L0: [ a[1] ] -> [ b[5] ] -> [ c[2] ] -> [ d[4] ] -> [ e[3] ] -> [ f[9] ] -> [ g[7] ] -> [ h[8] ] -> [ i[6] ] -> None
168```
169Finally, if you extend each "next" index reference to align with its sorted position within the list, a classical skip list diagram of towers emerges.
170```text
171L3: HEAD -------------------------------------------------------------------------> [ g[7] ] -------------------------> None
172L2: HEAD -------------------------------------------------------------> [ f[9] ] -> [ g[7] ] -------------> [ i[6] ] -> None
173L1: HEAD -------------------------> [ c[2] ] -> [ d[4] ] -------------> [ f[9] ] -> [ g[7] ] -------------> [ i[6] ] -> None
174L0: HEAD -> [ a[1] ] -> [ b[5] ] -> [ c[2] ] -> [ d[4] ] -> [ e[3] ] -> [ f[9] ] -> [ g[7] ] -> [ h[8] ] -> [ i[6] ] -> None
175```
176
177# Example code
178```rust
179 let mut list = dsa_rust::sequences::indexed_skip_list::SkipList::<char>::new();
180
181 // An unsorted list of values and a sorted version to compare against
182 let values = ['a', 'c', 'e', 'd', 'b', 'i', 'g', 'h', 'f'];
183 let sorted = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'];
184
185 // Inserts unsorted values into the skip list with a consuming iterator
186 for e in values.into_iter() {
187 list.insert(e)
188 }
189
190 // Illustrates that the list exists as a sorted invariant
191 for (i, e) in list.iter().enumerate() {
192 assert_eq!(e, &sorted[i]);
193 }
194
195 // Illustrates the Kth function in a 0-indexed list.
196 // That is, e occupies the 2 index for insertion order,
197 // but is the 4th element in the 0-indexed sorted arrangement.
198 assert_eq!(list.get_kth(4).unwrap(), &'e');
199
200 // Query by range using Rust's RangeBounds semantics
201 let val = ['c', 'd', 'e', 'f'];
202 for (i, e) in list.range('c'..='f').enumerate() {
203 assert_eq!(e, &val[i])
204 }
205
206```
207*/
208
209use rand::Rng; // For coin flips
210use std::borrow::Borrow; // For passing borrowed parameters
211use std::ops::{Bound, RangeBounds}; // For range iterators
212
213const MAX_HEIGHT: usize = 32;
214//const MAX_HEIGHT: usize = 10;
215
216#[derive(Clone, Debug)]
217struct SkipNode<T> {
218 value: Option<T>, // None for sentinel
219 next: [Option<usize>; MAX_HEIGHT], // forward links
220 prev: Option<usize>, // back links at s0 for reverse iteration
221 //height: usize // stores the node's "tower" height
222}
223
224pub struct SkipList<T> {
225 nodes: Vec<SkipNode<T>>,
226 height: usize,
227}
228impl<T: Ord> Default for SkipList<T> {
229 fn default() -> Self {
230 Self::new()
231 }
232}
233impl<T: Ord> SkipList<T> {
234 /// Creates a new, empty SkipList.
235 pub fn new() -> Self {
236 let sentinel = SkipNode {
237 value: None,
238 next: [None; MAX_HEIGHT],
239 prev: None,
240 };
241
242 Self {
243 nodes: vec![sentinel],
244 height: 1,
245 }
246 }
247
248 /// Returns the number of elements in the list.
249 pub fn len(&self) -> usize {
250 // Even empty lists have a single HEAD node,
251 // which does not count
252 self.nodes.len() - 1
253 }
254
255 /// Wrapper for `len()` that returns a Boolean
256 /// indicating whether the list is empty.
257 pub fn is_empty(&self) -> bool {
258 self.nodes.len() - 1 == 0
259 }
260
261 /// Returns the a reference to the entry associated with the search key
262 /// if it exists in the list, otherwise returns `None` to indicate
263 /// that the key is not in the list.
264 ///
265 /// Represents Pugh's canonical `Search` operation as described in the
266 /// <a href="https://15721.courses.cs.cmu.edu/spring2018/papers/08-oltpindexes1/pugh-skiplists-cacm1990.pdf" target="_blank" rel="noopener noreferrer">original paper</a>.
267 pub fn get<Q>(&self, key: &Q) -> Option<&T>
268 where
269 Q: Ord + ?Sized,
270 T: Borrow<Q>,
271 {
272 //let idx = self.skip_search(key);
273 let idx = self.find_predecessors(key)[0];
274 let next = self.nodes[idx].next[0]?;
275 let val = self.nodes[next].value.as_ref()?;
276
277 (val.borrow() == key).then_some(val)
278 }
279
280 /// Returns a Boolean indicating whether the supplied search key
281 /// exists in the list.
282 ///
283 /// Wrapper for the public `get()` operation, which itself wraps
284 /// the private `skip_search()` operation.
285 pub fn contains<Q>(&self, key: &Q) -> bool
286 where
287 Q: Ord + ?Sized,
288 T: Borrow<Q>,
289 {
290 self.get(key).is_some()
291 }
292
293 /// Inserts a new entry into the skip list.
294 ///
295 /// Allows duplicates, where ordering is determined by insertion order
296 /// such that the most recent duplicates come before older entries.
297 pub fn insert(&mut self, entry: T) {
298
299 // Insert(list, search_key, newValue)
300 // local update[1..MaxLevel]
301 // x = list.header
302 // for i = list.level downto 1 do
303 // while x.forward[i].key < search_key do
304 // x = x.forward[i]
305 // // x.key < search_key <= x.forward[i].key
306 // update[i] = x
307
308
309 // Chooses a random tower height and resets the list height
310 // if it is taller than the current list height
311 let height = self.random_height();
312 if height > self.height {
313 self.height = height;
314 }
315
316 // find_predecessors returns an array of predecessor positions
317 // at each level for the splice point where update[0] is the
318 // entry in the base list strictly < entry
319 let update = self.find_predecessors(&entry);
320 let prev_idx = update[0];
321 let new_index = self.nodes.len(); // Backing list insertion index
322 let next_idx = self.nodes[prev_idx].next[0];
323
324 self.nodes.push(SkipNode {
325 value: Some(entry),
326 next: [None; MAX_HEIGHT],
327 prev: Some(prev_idx),
328 });
329
330 // Reset the previous and current entry's next and previous
331 // positions, respectively
332 // take() only yields the number of elements in update up to
333 // the list's height providing a minimal number of loop iterations
334 for (level, _) in update.iter().enumerate().take(height) {
335 let prev_idx = update[level];
336 self.nodes[new_index].next[level] = self.nodes[prev_idx].next[level];
337 self.nodes[prev_idx].next[level] = Some(new_index);
338 }
339
340 // If there is a "next" node it must now point back to the new node
341 if let Some(next_idx) = next_idx {
342 self.nodes[next_idx].prev = Some(new_index);
343 }
344 }
345
346 /// Removes and returns the value for a given key, if it exists in
347 /// the list. Returns None if the key does not exist in the list.
348 ///
349 /// This function does not technically adhere to Pugh's original
350 /// removal algorithm. It uses [Vec::swap_remove] for simplified
351 /// backing list compaction with the side effect of re-ordering remaining
352 /// elements. The resultant removal time is therefore _O(n * height)_,
353 /// which is _O(n * log(n)) expected_, and _O(n^2)_ worst case.
354 pub fn remove<Q>(&mut self, key: &Q) -> Option<T>
355 where
356 Q: Ord + ?Sized,
357 T: Borrow<Q>,
358 {
359
360 // Delete(list, search_key)
361 // local update[1..MaxLevel]
362 // x = list.header
363 // for i = list.level downto 1 do
364 // while x.forward[i].key < search_key do
365 // x = x.forward[i]
366 // update[i] = x
367 // x = x.forward[1]
368 // if x.key = search_key then
369 // for i = 1 to list.level do
370 // if update[i].forward[i] != x then break
371 // update[i].forward[i] = x.forward[i]
372 // free(x)
373 // while list.level > 1 and
374 // list.header.forward[list.level] == NIL do
375 // list.level = list.level – 1
376
377 // Pre-fetch precessors for target removal node
378 let mut update = self.find_predecessors(key);
379
380 // Check if the target is in the list, if it is, return its index
381 let target = match self.nodes[update[0]].next[0] {
382 Some(idx)
383 if self.nodes[idx]
384 .value
385 .as_ref()
386 .is_some_and(|v| v.borrow() == key) =>
387 {
388 idx
389 }
390 _ => return None,
391 };
392
393 // Find the last node in the backing structure
394 let last_idx = self.nodes.len() - 1;
395
396 // Remove the prev and next positions from adjacent nodes
397 if let Some(next_idx) = self.nodes[target].next[0] {
398 self.nodes[next_idx].prev = self.nodes[target].prev;
399 }
400 for (level, val) in update.iter_mut().enumerate().take(self.height) {
401 if self.nodes[*val].next[level] == Some(target) {
402 self.nodes[*val].next[level] = self.nodes[target].next[level];
403 }
404 }
405
406 // Actual node removal
407 let removed_node = self.nodes.swap_remove(target);
408
409 // Set next/prev positions for the node that just got swapped
410 // into the hole left by the removal
411 if target < self.nodes.len() {
412 // Fix next positions
413 for node in &mut self.nodes {
414 for next in node.next.iter_mut().take(self.height) {
415 if *next == Some(last_idx) {
416 *next = Some(target);
417 }
418 }
419 }
420
421 // Repair adjacent backward links after relocation
422 if let Some(next_idx) = self.nodes[target].next[0] {
423 self.nodes[next_idx].prev = Some(target);
424 }
425
426 // Repair relocated predecessor reference
427 if self.nodes[target].prev == Some(last_idx) {
428 self.nodes[target].prev = Some(target);
429 }
430 }
431
432 // Reduce the list's height, in case the removed tower was tallest
433 while self.height > 1 &&
434 self.nodes[0].next[self.height - 1].is_none()
435 {
436 self.height -= 1;
437 }
438
439 // Return just the entry, not the entire node
440 removed_node.value
441 }
442
443 /// Returns the Kth value in the list, if it exists.
444 pub fn get_kth(&self, k: usize) -> Option<&T> {
445 let mut idx = self.nodes[0].next[0];
446 let mut i = 0;
447 while let Some(current) = idx {
448 if i == k {
449 return self.nodes[current].value.as_ref();
450 }
451 idx = self.nodes[current].next[0];
452 i += 1;
453 }
454 None
455 }
456
457 /// Returns an inclusive iterator over a range of values
458 /// in the list from `start` to `end`.
459 pub fn range<Q, R>(&self, range: R) -> RangeIter<'_, T, Q, R>
460 where
461 Q: Ord + ?Sized,
462 T: Borrow<Q>,
463 R: RangeBounds<Q>,
464 {
465 // FIND FRONT
466 let front = match range.start_bound() {
467 Bound::Included(start) => self.nodes[self.find_predecessors(start)[0]].next[0],
468 Bound::Excluded(start) => {
469 let idx = self.nodes[self.find_predecessors(start)[0]].next[0];
470 if let Some(i) = idx {
471 if self.nodes[i].value.as_ref().unwrap().borrow() == start {
472 self.nodes[i].next[0]
473 } else {
474 Some(i)
475 }
476 } else {
477 None
478 }
479 }
480 Bound::Unbounded => self.nodes[0].next[0],
481 };
482
483 // FIND BACK
484 let back = match range.end_bound() {
485 Bound::Included(end) => {
486 // Find predecessors of 'end'.
487 // If the element at the end of the search IS 'end', that's our back.
488 // If not, the predecessor itself is our back.
489 let update = self.find_predecessors(end);
490 let candidate = self.nodes[update[0]].next[0];
491 if let Some(idx) = candidate {
492 if self.nodes[idx].value.as_ref().unwrap().borrow() == end {
493 Some(idx)
494 } else {
495 // Predicate check: Ensure we aren't returning the sentinel (idx 0)
496 if update[0] == 0 {
497 None
498 } else {
499 Some(update[0])
500 }
501 }
502 } else if update[0] == 0 {
503 None
504 } else {
505 Some(update[0])
506 }
507 }
508 Bound::Excluded(end) => {
509 let update = self.find_predecessors(end);
510 if update[0] == 0 {
511 None
512 } else {
513 Some(update[0])
514 }
515 }
516 Bound::Unbounded => {
517 // To find the absolute end, we find predecessors for a
518 // "theoretically infinite" value
519 // or simply walk the tallest tower to the end.
520 let mut curr = 0;
521 for level in (0..self.height).rev() {
522 while let Some(next_idx) = self.nodes[curr].next[level] {
523 curr = next_idx;
524 }
525 }
526 if curr == 0 {
527 None
528 } else {
529 Some(curr)
530 }
531 }
532 };
533
534 RangeIter {
535 list: self,
536 front,
537 back,
538 range,
539 _marker: std::marker::PhantomData,
540 }
541 }
542
543 /// Returns an iterator over borrowed values in the list.
544 pub fn iter(&self) -> Iter<'_, T> {
545 // Walk the express lanes to find the very last node in O(log n) time
546 let mut tail = 0;
547 for level in (0..self.height).rev() {
548 while let Some(next_idx) = self.nodes[tail].next[level] {
549 tail = next_idx;
550 }
551 }
552
553 Iter {
554 list: self,
555 next: self.nodes[0].next[0], // First node after sentinel
556 prev: if tail == 0 { None } else { Some(tail) },
557 }
558 }
559
560 // Utility functions
561 ////////////////////
562
563 // Uses the external crate rand to determine the height h
564 // of a given tower which is always 1 <= h < MAX_HEIGHT
565 // by performing a series of "coin flips".
566 fn random_height(&self) -> usize {
567 let mut level = 1;
568 let mut rng = rand::rng();
569 while level < MAX_HEIGHT && rng.random::<bool>() {
570 level += 1;
571 }
572 level
573 }
574
575 // Represents the heart of the skip list. This function is used
576 // by the `insert()`, `remove()`, `range()`, `locate()` (and by
577 // proxy `contains()`) functions.
578 //
579 // Returns an array of integers representing entries for each level
580 // in the list that are strictly less than the search key at each
581 // level, where the 0th index represents the base list.
582 //
583 // Performs dual duty in regards to Pugh's original design.
584 // Useful as a basic skip_search by capturing the base list position
585 // of the entry strictly < search_key as the 0th array index in the
586 // return, as well as providing a list of previous positions at the
587 // splice/split point for insert and delete operations.
588 fn find_predecessors<Q>(&self, key: &Q) -> [usize; MAX_HEIGHT]
589 where
590 Q: Ord + ?Sized,
591 T: Borrow<Q>,
592 {
593 let mut update = [0usize; MAX_HEIGHT];
594 let mut idx = 0;
595
596 for level in (0..self.height).rev() {
597 loop {
598 match self.nodes[idx].next[level] {
599 None => break,
600 Some(next_idx) => {
601 let next_val = self.nodes[next_idx].value.as_ref().unwrap();
602 if next_val.borrow() >= key {
603 break;
604 }
605 idx = next_idx;
606 }
607 }
608 }
609 // Record the node index in the update list before descending
610 update[level] = idx;
611 }
612 update
613 }
614
615 /// Represents the heart of the skip list. This function is used
616 /// by the `get()`, `insert()`, `remove()`, `range()`, and
617 /// `contains()`) functions.
618 ///
619 /// Returns the index of the largest entry that is
620 /// strictly less than the provided key.
621 ///
622 /// NOTE: UNUSED
623 fn _skip_search<Q>(&self, key: &Q) -> usize
624 where
625 Q: Ord + ?Sized,
626 T: Borrow<Q>,
627 {
628 // Empty lists have a single sentinel node
629 if self.nodes.len() == 1 { return 0 };
630
631 // p = s: Always start at the HEAD node (index 0)
632 let mut pos = 0usize;
633
634 // Iterate through levels from top to bottom (the vertical "below(p)" steps)
635 for level in (0..self.height).rev() {
636 // "Scan forward" horizontally across the current level
637 while self.nodes[pos].next[level].is_some() {
638 // Peek at the NEXT node index on the current logical linked list
639 match self.nodes[pos].next[level] {
640 // Get the next node's value safely.
641 // If the search key is >= the forward node's value,
642 // advance the pos to that node. If the next node is
643 // either > key or None, break the loop, which
644 // moves to the next level.
645 Some(next_idx) => {
646 let next_val = self.nodes[next_idx].value.as_ref().unwrap().borrow();
647 if next_val < key {
648 pos = next_idx;
649 } else {
650 break; // Break and descend a level
651 }
652 }
653 None => {
654 break; // Break and descend a level
655 }
656 }
657 }
658 }
659
660 // If the position never advances beyond the sentinel,
661 // the key is not either doesn't exist in the list,
662 // or it belongs as the first element
663 pos
664 }
665
666 /// Returns the traversal path of a search key by
667 /// re-using the skip_search logic. The only difference is that
668 /// this traversal records each index and returns the path.
669 fn _traversal<Q>(&self, key: &Q) -> Vec<T>
670 where
671 Q: Ord + ?Sized,
672 T: Borrow<Q> + Clone,
673 {
674 let mut vec = Vec::new();
675
676 // p = s: Always start at the HEAD node (index 0)
677 let mut pos = 0usize;
678
679 // Iterate through levels from top to bottom (the vertical "below(p)" steps)
680 for level in (0..self.height).rev() {
681 // "Scan forward" horizontally across the current level
682 while self.nodes[pos].next[level].is_some() {
683 // Peek at the NEXT node index on the current logical linked list
684 match self.nodes[pos].next[level] {
685 // Get the next node's value safely.
686 // If the search key is >= the forward node's value,
687 // advance the pos to that node. If the next node is
688 // either > key or None, break the loop, which
689 // moves to the next level.
690 Some(next_idx) => {
691 let next_val = self.nodes[next_idx].value.as_ref().unwrap().borrow();
692 if next_val < key {
693 vec.push(self.nodes[next_idx].value.clone().unwrap());
694 pos = next_idx;
695 } else {
696 break; // Break and descend a level
697 }
698 }
699 None => {
700 break; // Break and descend a level
701 }
702 }
703 }
704 }
705 vec
706 }
707}
708
709pub struct Iter<'a, T> {
710 list: &'a SkipList<T>,
711 next: Option<usize>,
712 prev: Option<usize>,
713}
714impl<'a, T> Iterator for Iter<'a, T> {
715 type Item = &'a T;
716
717 fn next(&mut self) -> Option<Self::Item> {
718 let idx = self.next?;
719 let value = self.list.nodes[idx].value.as_ref()?;
720
721 if self.next == self.prev {
722 self.next = None;
723 self.prev = None;
724 } else {
725 self.next = self.list.nodes[idx].next[0];
726 }
727 Some(value)
728 }
729}
730impl<'a, T> DoubleEndedIterator for Iter<'a, T> {
731 fn next_back(&mut self) -> Option<Self::Item> {
732 let idx = self.prev?;
733 let value = self.list.nodes[idx].value.as_ref()?;
734
735 if self.prev == self.next {
736 self.next = None;
737 self.prev = None;
738 } else {
739 let prev = self.list.nodes[idx].prev;
740 // Sentinel check: don't yield index 0
741 self.prev = if prev == Some(0) { None } else { prev };
742 }
743 Some(value)
744 }
745}
746
747pub struct RangeIter<'a, T, Q, R>
748where
749 Q: ?Sized,
750 R: RangeBounds<Q>,
751{
752 list: &'a SkipList<T>,
753 front: Option<usize>, // Moves forward
754 back: Option<usize>, // Moves backward
755 range: R,
756 _marker: std::marker::PhantomData<Q>,
757}
758impl<'a, T, Q, R> Iterator for RangeIter<'a, T, Q, R>
759where
760 Q: Ord + ?Sized,
761 T: Borrow<Q>,
762 R: RangeBounds<Q>,
763{
764 type Item = &'a T;
765
766 fn next(&mut self) -> Option<Self::Item> {
767 let idx = self.front?;
768
769 // Boundary Check
770 let value = self.list.nodes[idx].value.as_ref().unwrap();
771 if !self.range.contains(value.borrow()) {
772 self.front = None;
773 return None;
774 }
775
776 // Meet/Cross Check: If front matches back, this is the last element
777 if self.front == self.back {
778 self.front = None;
779 self.back = None;
780 } else {
781 self.front = self.list.nodes[idx].next[0];
782 }
783
784 Some(value)
785 }
786}
787impl<'a, T, Q, R> DoubleEndedIterator for RangeIter<'a, T, Q, R>
788where
789 Q: Ord + ?Sized,
790 T: Borrow<Q>,
791 R: RangeBounds<Q>,
792{
793 fn next_back(&mut self) -> Option<Self::Item> {
794 let idx = self.back?;
795
796 // Boundary Check
797 let value = self.list.nodes[idx].value.as_ref().unwrap();
798 if !self.range.contains(value.borrow()) {
799 self.back = None;
800 return None;
801 }
802
803 // Meet/Cross Check
804 if self.back == self.front {
805 self.back = None;
806 self.front = None;
807 } else {
808 // Move back, but ensure we don't land on the sentinel (idx 0)
809 let prev = self.list.nodes[idx].prev;
810 self.back = if prev == Some(0) { None } else { prev };
811 }
812
813 Some(value)
814 }
815}
816
817impl<'a, T: Ord> IntoIterator for &'a SkipList<T> {
818 type Item = &'a T;
819 type IntoIter = Iter<'a, T>;
820
821 fn into_iter(self) -> Self::IntoIter {
822 self.iter()
823 }
824}
825
826#[test]
827fn one() {
828 let mut list = SkipList::<char>::new();
829
830 // Tests basic housekeeping on empty list
831 assert_eq!(list.len(), 0);
832 assert!(list.is_empty());
833 assert!(!list.contains(&'z'));
834
835 // Inserts 9 values into the skip list
836 // with a consuming iterator, moving values
837 // into the list
838 let values = ['a', 'c', 'e', 'd', 'b', 'i', 'g', 'h', 'f'];
839 println!("Insert elements in order: {:?}", &values);
840 for e in values.into_iter() {
841 list.insert(e)
842 }
843 println!("LIST DIAGNOSTICS: \n\theight: {}\n\tlength: {}", list.height, list.nodes.len());
844 println!("Tower contents by insertion order, NOT sorted order:");
845 for (i, e) in list.nodes.iter().enumerate() {
846 // Collect only the Some values into a new Vec
847 //let values: Vec<_> = e.next.iter().filter_map(|&x| x).collect();
848 let values: Vec<_> = e.next.iter().collect();
849 match e.value {
850 Some(val) => println!("{val:>04} [{i}]: {values:?}"),
851 None => println!("HEAD [{i}]: {values:?}"),
852 }
853 //if let Some(val) = e.value {
854 // let v = &val.to_string();
855 // println!("{v}[{i}]: {values:?}");
856 //} else {
857 // println!("HEAD[0]: {values:?}");
858 //}
859 }
860 println!();
861
862 // Tests that len gets updated properly
863 assert_eq!(list.len(), 9);
864 assert!(list.contains(&'g'));
865
866 // Tests basic ordering and iteration
867 // Basic iteration with iter()
868 // Clippy wants enumerate instead of external loop counter
869 let sorted = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'];
870 for (i, e) in list.iter().enumerate() {
871 assert_eq!(e, &sorted[i]);
872 }
873 // Double-ended iteration with rev()
874 // Clippy wants saturating_sub instead of loop counter
875 let mut i = 8;
876 for e in list.iter().rev() {
877 assert_eq!(e, &sorted[i]);
878 if i > 0 {
879 i = i.saturating_sub(1)
880 };
881 }
882 // Or if you wanna be fancy about it
883 // zip() stops as soon as one iterator ends,
884 // eliminating the need for an overflow check
885 for (e, i) in list.iter().rev().zip((0..=8).rev()) {
886 assert_eq!(e, &sorted[i]);
887 }
888
889 // Iterator inferance using the IntoIter impl
890 let mut i = 0;
891 #[allow(clippy::explicit_counter_loop)]
892 for e in &list {
893 assert_eq!(e, &sorted[i]);
894 i += 1;
895 }
896
897 // Tests the Kth function in a 0-indexed list
898 assert_eq!(list.get_kth(6).unwrap(), &'g');
899
900 // Tests the range function
901 // NOTE: char is Copy so you dont strictly need to borrow
902 // when setting range bounds, these tests illustrate both
903 // borrowing and not; Note that each bounds must match
904 // so no (&'a'..'f'), only ('a'..'f') or (&'a'..&'f')
905 // Midlist (exclusive)
906 let val = ['c', 'd', 'e'];
907 //for (i, e) in list.range(&'c', &'f').enumerate() {
908 for (i, e) in list.range('c'..'f').enumerate() {
909 assert_eq!(e, &val[i])
910 }
911 // Midlist (inclusive)
912 let val = ['c', 'd', 'e', 'f'];
913 //for (i, e) in list.range(&'c', &'f').enumerate() {
914 for (i, e) in list.range('c'..='f').enumerate() {
915 assert_eq!(e, &val[i])
916 }
917 // Start of list
918 let val = ['a', 'b', 'c', 'd', 'e', 'f'];
919 //for (i, e) in list.range(&'a', &'f').enumerate() {
920 for (i, e) in list.range(..&'f').enumerate() {
921 assert_eq!(e, &val[i])
922 }
923 // End of list
924 let val = ['e', 'f', 'g', 'h', 'i'];
925 for (i, e) in list.range(&'e'..).enumerate() {
926 assert_eq!(e, &val[i])
927 }
928
929 // Tests remove(e)
930 // Removes the first element
931 println!("Remove 'a'");
932 list.remove(&'a');
933 // Removes an arbitrary element
934 println!("Remove 'e'");
935 list.remove(&'e');
936 // Removes the last element
937 println!("Remove 'i'");
938 list.remove(&'i');
939 // List shrinks as expected
940 assert_eq!(list.len(), 6);
941 // List no longer contains elements
942 assert!(!list.contains(&'e'));
943 assert!(!list.contains(&'a'));
944 // Cant remove what isn't there!
945 assert!(list.remove(&'z').is_none());
946 // Debug prints new layout
947 print!("List updated values: [");
948 for e in list.iter() {
949 print!("{e:#?} ")
950 }
951 println!("]");
952
953 // Debug prints the tower contents
954 println!("LIST DIAGNOSTICS: \n\theight: {}\n\tlength: {}", list.height, list.nodes.len());
955 println!("Tower contents by insertion order, NOT sorted order:");
956 for (i, e) in list.nodes.iter().enumerate() {
957 // Collect only the Some values into a new Vec
958 //let values: Vec<_> = e.next.iter().filter_map(|&x| x).collect();
959 let values: Vec<_> = e.next.iter().collect();
960 match e.value {
961 Some(val) => println!("{val:>04} [{i}]: {values:?}"),
962 None => println!("HEAD [{i}]: {values:?}"),
963 }
964 }
965 println!();
966
967 // Tests skip_search(e), find_predecessors(e) and their dependencies:
968 // contains(e), get(e), and find_val(e)
969 //
970 // An element in the list
971 let node = 'h';
972 assert_eq!(list._skip_search(&node), 6); // 6 is g which is < h
973 assert_eq!(list.find_predecessors(&node)[0], 6); // 6 is g which is < h
974 assert!(list.contains(&node));
975 assert_eq!(list.get(&node).unwrap(), &'h');
976 // An element at the beginning of the list
977 let node = 'b';
978 assert_eq!(list._skip_search(&node), 0);
979 assert_eq!(list.find_predecessors(&node)[0], 0);
980 assert!(list.contains(&node));
981 assert_eq!(list.get(&node).unwrap(), &'b');
982 // An element not in the list
983 // 'j' is not in the list, but skip_search returns 6 because
984 // thats the position that 'i' lives at due to insertion order,
985 // and 'i' < 'j'
986 let node = 'j';
987 assert_eq!(list._skip_search(&node), 3); // 3 is h, the last entry
988 assert_eq!(list.find_predecessors(&node)[0], 3); // 3 is h, the last entry
989 assert!(!list.contains(&node));
990 assert!(list.get(&node).is_none());
991 // An element that was previously in the list, but removed
992 let node = 'a';
993 assert_eq!(list._skip_search(&node), 0); // belongs after HEAD
994 assert_eq!(list.find_predecessors(&node)[0], 0); // belongs after HEAD
995 assert!(!list.contains(&node));
996 assert!(list.get(&node).is_none());
997
998 // A bunch of random list mutations to ensure coherence
999 list.insert('p');
1000 list.insert('u');
1001 list.insert('w');
1002 list.remove(&'p');
1003 list.insert('l');
1004 list.insert('m');
1005 list.remove(&'f');
1006 list.remove(&'o');
1007 list.insert('q');
1008 list.remove(&'m');
1009 list.insert('x');
1010 list.insert('z');
1011
1012 // Visual component:
1013 // Combines contains(e) and prints traversal() as proof
1014 let trav = list._traversal(&'g');
1015 let con = list.contains(&'g');
1016 println!("Contains 'g': {con:?}");
1017 println!("Traversal: {trav:?}");
1018 let trav = list._traversal(&'j');
1019 let con = list.contains(&'j');
1020 println!("Contains 'j': {con:?}");
1021 println!("Traversal: {trav:?}");
1022 let trav = list._traversal(&'a');
1023 let con = list.contains(&'a');
1024 println!("Contains 'a': {con:?}");
1025 println!("Traversal: {trav:?}");
1026
1027 // Tests traversal ordering
1028 let l2 = ['b', 'c', 'd', 'g', 'h', 'l', 'q', 'u', 'w', 'x', 'z'];
1029 for (val, i) in list.iter().zip(0..=5) {
1030 assert_eq!(val, &l2[i]);
1031 }
1032 // Visual confirmation of correct traversal
1033 print!("List values:\n ");
1034 for e in list.iter() {
1035 print!("{e:#?} ")
1036 }
1037 println!();
1038
1039 //panic!();
1040}
1041
1042#[test]
1043// AI-written "stress" test
1044fn test_skip_list_removal_integrity() {
1045 // Assumes your SkipList has a standard New or Default implementation
1046 let mut list = SkipList::new();
1047
1048 // 1. Insert a sequence of numbers.
1049 // This allows your natural random_height() generator to build up
1050 // a multi-level tower structure organically.
1051 let total_elements = 300;
1052 for i in 0..total_elements {
1053 list.insert(i);
1054 }
1055
1056 // 2. Remove elements from the middle of the list.
1057 // This forces swap_remove to repeatedly pull the last element of the Vec
1058 // into the newly created holes, triggering your global repair loops.
1059 for i in (50..200).step_by(2) {
1060 list.remove(&i);
1061 }
1062
1063 // 3. STRUCTURAL AUDIT
1064 // Scan every single surviving node's next pointers across every level.
1065 // If swap_remove left a stale index behind, it will point out-of-bounds.
1066 let current_len = list.nodes.len();
1067 for idx in 0..current_len {
1068 for level in 0..list.height {
1069 if let Some(next_idx) = list.nodes[idx].next[level] {
1070
1071 // Assert 1: Out-of-bounds index protection
1072 assert!(
1073 next_idx < current_len,
1074 "CORRUPTION: Node at index {idx} on level {level} points to index {next_idx}, \
1075 which is out-of-bounds for a Vec of length {current_len}!"
1076 );
1077
1078 // Assert 2: Level 0 backlink symmetric validation
1079 if level == 0 {
1080 assert_eq!(
1081 list.nodes[next_idx].prev,
1082 Some(idx),
1083 "CORRUPTION: Link symmetry broken! Node {} points forward to {}, \
1084 but {} points backward to {:?}",
1085 idx, next_idx, next_idx, list.nodes[next_idx].prev
1086 );
1087 }
1088 }
1089 }
1090 }
1091
1092 // 4. ALGORITHMIC AUDIT
1093 // Verify that every single element that wasn't deleted is still perfectly
1094 // searchable via skip_search. If a lane broke, skip_search will overshoot
1095 // or fail to track the element.
1096 for i in 0..total_elements {
1097 // Skip the elements we explicitly deleted
1098 if (50..200).contains(&i) && i % 2 == 0 {
1099 continue;
1100 }
1101
1102 // Your skip_search returns a valid usize position
1103 let pos = list.find_predecessors(&i)[0];
1104
1105 // Ensure the pos returned actually matches our search target
1106 // or points to its direct predecessor.
1107 if pos == 0 {
1108 // If it returned the sentinel, the first item in the list must be >= i
1109 if let Some(first_idx) = list.nodes[0].next[0] {
1110 let first_val = list.nodes[first_idx].value.as_ref().unwrap();
1111 assert!(first_val >= &i);
1112 }
1113 } else {
1114 let found_val = list.nodes[pos].value.as_ref().unwrap();
1115 assert!(found_val <= &i, "skip_search returned a node greater than the key!");
1116 }
1117 }
1118
1119 println!("LIST DIAGNOSTICS: \n\theight: {}\n\tlength: {}", list.height, list.nodes.len());
1120 println!("Tower contents by insertion order, NOT sorted order:");
1121 for (i, e) in list.nodes.iter().enumerate() {
1122 // Collect only the Some values into a new Vec
1123 //let values: Vec<_> = e.next.iter().filter_map(|&x| x).collect();
1124 let values: Vec<_> = e.next.iter().collect();
1125 match e.value {
1126 Some(val) => println!("{val:>04} [{i}]: {values:?}"),
1127 None => println!("HEAD [{i}]: {values:?}"),
1128 }
1129 }
1130 println!();
1131 //panic!()
1132}