Skip to content

Commit

Permalink
feat(cordyceps): added new push_back and pop_front methods to list (#198
Browse files Browse the repository at this point in the history
)

This PR adds some nice-to-have methods as detailed in [this issue](#186).

Changes:
* New `push_back` method
* New `pop_front` method
* Tests for each method

Small change but hope it's good :D

Closes #186

* feat(cordyceps): added new push_back and pop_front methods to list

* test(cordyceps): added missing checks to push_back test

* test(cordyceps): added tests for new methods to fuzz_linked_list proptest. updated documentation to clarify the List can now be used as a stack or Doubly-Linked List

* lint(cordyceps): run rustfmt

Co-authored-by: Graham Keenan <[email protected]>
  • Loading branch information
Tyrannican and Tyrannican authored Jun 5, 2022
1 parent 85d5b00 commit c555772
Showing 1 changed file with 132 additions and 8 deletions.
140 changes: 132 additions & 8 deletions cordyceps/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ use core::{
/// [`List::push_front`] and [`List::pop_back`] methods. It also supports
/// random-access removals using the [`List::remove`] method.
///
/// This data structure can also be used as a stack or doubly-linked list by using
/// the [`List::pop_front`] and [`List::push_back`] methods.
///
/// In order to be part of a `List`, a type `T` must implement [`Linked`] for
/// [`list::Links<T>`].
///
Expand Down Expand Up @@ -158,6 +161,42 @@ impl<T: Linked<Links<T>> + ?Sized> List<T> {
// tracing::trace!(?self, "push_front: pushed");
}

/// Appends an item to the tail of the list
pub fn push_back(&mut self, item: T::Handle) {
let ptr = T::into_ptr(item);
assert_ne!(self.tail, Some(ptr));
unsafe {
T::links(ptr).as_mut().set_next(None);
T::links(ptr).as_mut().set_prev(self.tail);
if let Some(tail) = self.tail {
T::links(tail).as_mut().set_next(Some(ptr));
}
}

self.tail = Some(ptr);
if self.head.is_none() {
self.head = Some(ptr);
}
}

/// Remove an item from the head of the list
pub fn pop_front(&mut self) -> Option<T::Handle> {
let head = self.head?;

unsafe {
let mut head_links = T::links(head);
self.head = head_links.as_ref().next();
if let Some(next) = head_links.as_mut().next() {
T::links(next).as_mut().set_prev(None);
} else {
self.tail = None;
}

head_links.as_mut().unlink();
Some(T::from_ptr(head))
}
}

/// Removes an item from the tail of the list.
pub fn pop_back(&mut self) -> Option<T::Handle> {
let tail = self.tail?;
Expand Down Expand Up @@ -563,6 +602,71 @@ mod tests {
assert!(list.is_empty());
}

#[test]
fn pop_front() {
let _trace = trace_init();

let a = entry(5);
let b = entry(7);
let c = entry(9);
let mut list = List::<Entry>::new();

list.push_front(a.as_ref());
list.assert_valid();

list.push_front(b.as_ref());
list.assert_valid();

list.push_front(c.as_ref());
list.assert_valid();

let d = list.pop_front().unwrap();
assert_eq!(9, d.val);

let e = list.pop_front().unwrap();
assert_eq!(7, e.val);

let f = list.pop_front().unwrap();
assert_eq!(5, f.val);

assert!(list.is_empty());
assert!(list.pop_front().is_none());
list.assert_valid();
}

#[test]
fn push_back() {
let _trace = trace_init();

let a = entry(5);
let b = entry(7);
let c = entry(9);
let mut list = List::<Entry>::new();

list.push_back(a.as_ref());
list.assert_valid();

list.push_back(b.as_ref());
list.assert_valid();

list.push_back(c.as_ref());
list.assert_valid();

let d = list.pop_back().unwrap();
assert_eq!(9, d.val);

let e = list.pop_back().unwrap();
assert_eq!(7, e.val);

let f = list.pop_back().unwrap();
assert_eq!(5, f.val);

assert!(list.is_empty());
assert!(list.pop_back().is_none());

list.assert_valid();
}

#[test]
fn push_pop_push_pop() {
let _trace = trace_init();
Expand Down Expand Up @@ -815,8 +919,10 @@ mod tests {

#[derive(Debug)]
enum Op {
Push,
Pop,
PushFront,
PopBack,
PushBack,
PopFront,
Remove(usize),
}

Expand Down Expand Up @@ -847,10 +953,12 @@ mod tests {

let ops = ops
.iter()
.map(|i| match i % 3 {
0 => Op::Push,
1 => Op::Pop,
2 => Op::Remove(i / 3),
.map(|i| match i % 5 {
0 => Op::PushFront,
1 => Op::PopBack,
2 => Op::PushBack,
3 => Op::PopFront,
4 => Op::Remove(i / 5),
_ => unreachable!(),
})
.collect::<Vec<_>>();
Expand All @@ -874,13 +982,13 @@ mod tests {
let _span = tracing::info_span!("op", ?i, ?op).entered();
tracing::info!(?op);
match op {
Op::Push => {
Op::PushFront => {
reference.push_front(i as i32);
assert_eq!(entries[i].val, i as i32);

ll.push_front(entries[i].as_ref());
}
Op::Pop => {
Op::PopBack => {
if reference.is_empty() {
assert!(ll.is_empty());
tracing::debug!("skipping pop; list is empty");
Expand All @@ -890,6 +998,22 @@ mod tests {
let v = reference.pop_back();
assert_eq!(v, ll.pop_back().map(|v| v.val));
}
Op::PushBack => {
reference.push_back(i as i32);
assert_eq!(entries[i].val, i as i32);

ll.push_back(entries[i].as_ref());
}
Op::PopFront => {
if reference.is_empty() {
assert!(ll.is_empty());
tracing::debug!("skipping pop: list is empty");
continue;
}

let v = reference.pop_front();
assert_eq!(v, ll.pop_front().map(|v| v.val));
}
Op::Remove(n) => {
if reference.is_empty() {
assert!(ll.is_empty());
Expand Down

0 comments on commit c555772

Please sign in to comment.