From bb9cdb5bfc760c5f26ffbe8d59185e36ec2fd172 Mon Sep 17 00:00:00 2001 From: Ian Bull Date: Mon, 1 Jul 2024 18:55:51 -0700 Subject: [PATCH 1/2] Add the final code for the sixth example This change-set adds the final code for the sixth example from the "Learn Rust With Entirely Too Many Linked Lists" book. --- .github/workflows/main.yml | 4 +- lists/src/lib.rs | 3 +- lists/src/sixth.rs | 1200 ++++++++++++++++++++++++++++++++++++ 3 files changed, 1204 insertions(+), 3 deletions(-) create mode 100644 lists/src/sixth.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 992ecfe..a99f49e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,8 +3,8 @@ name: Rust on: push: branches: - - 'master' - - '!gh-pages' + - "master" + - "!gh-pages" pull_request: jobs: diff --git a/lists/src/lib.rs b/lists/src/lib.rs index 8ce8dcc..c581026 100644 --- a/lists/src/lib.rs +++ b/lists/src/lib.rs @@ -3,6 +3,7 @@ pub mod second; pub mod third; pub mod fourth; pub mod fifth; +pub mod sixth; pub mod silly1; -pub mod silly2; \ No newline at end of file +pub mod silly2; diff --git a/lists/src/sixth.rs b/lists/src/sixth.rs new file mode 100644 index 0000000..2eef219 --- /dev/null +++ b/lists/src/sixth.rs @@ -0,0 +1,1200 @@ +use std::cmp::Ordering; +use std::fmt::{self, Debug}; +use std::hash::{Hash, Hasher}; +use std::iter::FromIterator; +use std::marker::PhantomData; +use std::ptr::NonNull; + +pub struct LinkedList { + front: Link, + back: Link, + len: usize, + _boo: PhantomData, +} + +type Link = Option>>; + +struct Node { + front: Link, + back: Link, + elem: T, +} + +pub struct Iter<'a, T> { + front: Link, + back: Link, + len: usize, + _boo: PhantomData<&'a T>, +} + +pub struct IterMut<'a, T> { + front: Link, + back: Link, + len: usize, + _boo: PhantomData<&'a mut T>, +} + +pub struct IntoIter { + list: LinkedList, +} + +pub struct CursorMut<'a, T> { + list: &'a mut LinkedList, + cur: Link, + index: Option, +} + +impl LinkedList { + pub fn new() -> Self { + Self { + front: None, + back: None, + len: 0, + _boo: PhantomData, + } + } + + pub fn push_front(&mut self, elem: T) { + // SAFETY: it's a linked-list, what do you want? + unsafe { + let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node { + front: None, + back: None, + elem, + }))); + if let Some(old) = self.front { + // Put the new front before the old one + (*old.as_ptr()).front = Some(new); + (*new.as_ptr()).back = Some(old); + } else { + // If there's no front, then we're the empty list and need + // to set the back too. + self.back = Some(new); + } + // These things always happen! + self.front = Some(new); + self.len += 1; + } + } + + pub fn push_back(&mut self, elem: T) { + // SAFETY: it's a linked-list, what do you want? + unsafe { + let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node { + back: None, + front: None, + elem, + }))); + if let Some(old) = self.back { + // Put the new back before the old one + (*old.as_ptr()).back = Some(new); + (*new.as_ptr()).front = Some(old); + } else { + // If there's no back, then we're the empty list and need + // to set the front too. + self.front = Some(new); + } + // These things always happen! + self.back = Some(new); + self.len += 1; + } + } + + pub fn pop_front(&mut self) -> Option { + unsafe { + // Only have to do stuff if there is a front node to pop. + self.front.map(|node| { + // Bring the Box back to life so we can move out its value and + // Drop it (Box continues to magically understand this for us). + let boxed_node = Box::from_raw(node.as_ptr()); + let result = boxed_node.elem; + + // Make the next node into the new front. + self.front = boxed_node.back; + if let Some(new) = self.front { + // Cleanup its reference to the removed node + (*new.as_ptr()).front = None; + } else { + // If the front is now null, then this list is now empty! + self.back = None; + } + + self.len -= 1; + result + // Box gets implicitly freed here, knows there is no T. + }) + } + } + + pub fn pop_back(&mut self) -> Option { + unsafe { + // Only have to do stuff if there is a back node to pop. + self.back.map(|node| { + // Bring the Box front to life so we can move out its value and + // Drop it (Box continues to magically understand this for us). + let boxed_node = Box::from_raw(node.as_ptr()); + let result = boxed_node.elem; + + // Make the next node into the new back. + self.back = boxed_node.front; + if let Some(new) = self.back { + // Cleanup its reference to the removed node + (*new.as_ptr()).back = None; + } else { + // If the back is now null, then this list is now empty! + self.front = None; + } + + self.len -= 1; + result + // Box gets implicitly freed here, knows there is no T. + }) + } + } + + pub fn front(&self) -> Option<&T> { + unsafe { self.front.map(|node| &(*node.as_ptr()).elem) } + } + + pub fn front_mut(&mut self) -> Option<&mut T> { + unsafe { self.front.map(|node| &mut (*node.as_ptr()).elem) } + } + + pub fn back(&self) -> Option<&T> { + unsafe { self.back.map(|node| &(*node.as_ptr()).elem) } + } + + pub fn back_mut(&mut self) -> Option<&mut T> { + unsafe { self.back.map(|node| &mut (*node.as_ptr()).elem) } + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn clear(&mut self) { + // Oh look it's drop again + while self.pop_front().is_some() {} + } + + pub fn iter(&self) -> Iter { + Iter { + front: self.front, + back: self.back, + len: self.len, + _boo: PhantomData, + } + } + + pub fn iter_mut(&mut self) -> IterMut { + IterMut { + front: self.front, + back: self.back, + len: self.len, + _boo: PhantomData, + } + } + + pub fn cursor_mut(&mut self) -> CursorMut { + CursorMut { + list: self, + cur: None, + index: None, + } + } +} + +impl Drop for LinkedList { + fn drop(&mut self) { + // Pop until we have to stop + while self.pop_front().is_some() {} + } +} + +impl Default for LinkedList { + fn default() -> Self { + Self::new() + } +} + +impl Clone for LinkedList { + fn clone(&self) -> Self { + let mut new_list = Self::new(); + for item in self { + new_list.push_back(item.clone()); + } + new_list + } +} + +impl Extend for LinkedList { + fn extend>(&mut self, iter: I) { + for item in iter { + self.push_back(item); + } + } +} + +impl FromIterator for LinkedList { + fn from_iter>(iter: I) -> Self { + let mut list = Self::new(); + list.extend(iter); + list + } +} + +impl Debug for LinkedList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self).finish() + } +} + +impl PartialEq for LinkedList { + fn eq(&self, other: &Self) -> bool { + self.len() == other.len() && self.iter().eq(other) + } +} + +impl Eq for LinkedList {} + +impl PartialOrd for LinkedList { + fn partial_cmp(&self, other: &Self) -> Option { + self.iter().partial_cmp(other) + } +} + +impl Ord for LinkedList { + fn cmp(&self, other: &Self) -> Ordering { + self.iter().cmp(other) + } +} + +impl Hash for LinkedList { + fn hash(&self, state: &mut H) { + self.len().hash(state); + for item in self { + item.hash(state); + } + } +} + +impl<'a, T> IntoIterator for &'a LinkedList { + type IntoIter = Iter<'a, T>; + type Item = &'a T; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + // While self.front == self.back is a tempting condition to check here, + // it won't do the right for yielding the last element! That sort of + // thing only works for arrays because of "one-past-the-end" pointers. + if self.len > 0 { + // We could unwrap front, but this is safer and easier + self.front.map(|node| unsafe { + self.len -= 1; + self.front = (*node.as_ptr()).back; + &(*node.as_ptr()).elem + }) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } +} + +impl<'a, T> DoubleEndedIterator for Iter<'a, T> { + fn next_back(&mut self) -> Option { + if self.len > 0 { + self.back.map(|node| unsafe { + self.len -= 1; + self.back = (*node.as_ptr()).front; + &(*node.as_ptr()).elem + }) + } else { + None + } + } +} + +impl<'a, T> ExactSizeIterator for Iter<'a, T> { + fn len(&self) -> usize { + self.len + } +} + +impl<'a, T> IntoIterator for &'a mut LinkedList { + type IntoIter = IterMut<'a, T>; + type Item = &'a mut T; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl<'a, T> Iterator for IterMut<'a, T> { + type Item = &'a mut T; + + fn next(&mut self) -> Option { + // While self.front == self.back is a tempting condition to check here, + // it won't do the right for yielding the last element! That sort of + // thing only works for arrays because of "one-past-the-end" pointers. + if self.len > 0 { + // We could unwrap front, but this is safer and easier + self.front.map(|node| unsafe { + self.len -= 1; + self.front = (*node.as_ptr()).back; + &mut (*node.as_ptr()).elem + }) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } +} + +impl<'a, T> DoubleEndedIterator for IterMut<'a, T> { + fn next_back(&mut self) -> Option { + if self.len > 0 { + self.back.map(|node| unsafe { + self.len -= 1; + self.back = (*node.as_ptr()).front; + &mut (*node.as_ptr()).elem + }) + } else { + None + } + } +} + +impl<'a, T> ExactSizeIterator for IterMut<'a, T> { + fn len(&self) -> usize { + self.len + } +} + +impl IntoIterator for LinkedList { + type IntoIter = IntoIter; + type Item = T; + + fn into_iter(self) -> Self::IntoIter { + IntoIter { list: self } + } +} + +impl Iterator for IntoIter { + type Item = T; + + fn next(&mut self) -> Option { + self.list.pop_front() + } + + fn size_hint(&self) -> (usize, Option) { + (self.list.len, Some(self.list.len)) + } +} + +impl DoubleEndedIterator for IntoIter { + fn next_back(&mut self) -> Option { + self.list.pop_back() + } +} + +impl ExactSizeIterator for IntoIter { + fn len(&self) -> usize { + self.list.len + } +} + +impl<'a, T> CursorMut<'a, T> { + pub fn index(&self) -> Option { + self.index + } + + pub fn move_next(&mut self) { + if let Some(cur) = self.cur { + unsafe { + // We're on a real element, go to its next (back) + self.cur = (*cur.as_ptr()).back; + if self.cur.is_some() { + *self.index.as_mut().unwrap() += 1; + } else { + // We just walked to the ghost, no more index + self.index = None; + } + } + } else if !self.list.is_empty() { + // We're at the ghost, and there is a real front, so move to it! + self.cur = self.list.front; + self.index = Some(0) + } else { + // We're at the ghost, but that's the only element... do nothing. + } + } + + pub fn move_prev(&mut self) { + if let Some(cur) = self.cur { + unsafe { + // We're on a real element, go to its previous (front) + self.cur = (*cur.as_ptr()).front; + if self.cur.is_some() { + *self.index.as_mut().unwrap() -= 1; + } else { + // We just walked to the ghost, no more index + self.index = None; + } + } + } else if !self.list.is_empty() { + // We're at the ghost, and there is a real back, so move to it! + self.cur = self.list.back; + self.index = Some(self.list.len - 1) + } else { + // We're at the ghost, but that's the only element... do nothing. + } + } + + pub fn current(&mut self) -> Option<&mut T> { + unsafe { self.cur.map(|node| &mut (*node.as_ptr()).elem) } + } + + pub fn peek_next(&mut self) -> Option<&mut T> { + unsafe { + let next = if let Some(cur) = self.cur { + // Normal case, try to follow the cur node's back pointer + (*cur.as_ptr()).back + } else { + // Ghost case, try to use the list's front pointer + self.list.front + }; + + // Yield the element if the next node exists + next.map(|node| &mut (*node.as_ptr()).elem) + } + } + + pub fn peek_prev(&mut self) -> Option<&mut T> { + unsafe { + let prev = if let Some(cur) = self.cur { + // Normal case, try to follow the cur node's front pointer + (*cur.as_ptr()).front + } else { + // Ghost case, try to use the list's back pointer + self.list.back + }; + + // Yield the element if the prev node exists + prev.map(|node| &mut (*node.as_ptr()).elem) + } + } + + pub fn split_before(&mut self) -> LinkedList { + // We have this: + // + // list.front -> A <-> B <-> C <-> D <- list.back + // ^ + // cur + // + // + // And we want to produce this: + // + // list.front -> C <-> D <- list.back + // ^ + // cur + // + // + // return.front -> A <-> B <- return.back + // + if let Some(cur) = self.cur { + // We are pointing at a real element, so the list is non-empty. + unsafe { + // Current state + let old_len = self.list.len; + let old_idx = self.index.unwrap(); + let prev = (*cur.as_ptr()).front; + + // What self will become + let new_len = old_len - old_idx; + let new_front = self.cur; + let new_back = self.list.back; + let new_idx = Some(0); + + // What the output will become + let output_len = old_len - new_len; + let output_front = self.list.front; + let output_back = prev; + + // Break the links between cur and prev + if let Some(prev) = prev { + (*cur.as_ptr()).front = None; + (*prev.as_ptr()).back = None; + } + + // Produce the result: + self.list.len = new_len; + self.list.front = new_front; + self.list.back = new_back; + self.index = new_idx; + + LinkedList { + front: output_front, + back: output_back, + len: output_len, + _boo: PhantomData, + } + } + } else { + // We're at the ghost, just replace our list with an empty one. + // No other state needs to be changed. + std::mem::replace(self.list, LinkedList::new()) + } + } + + pub fn split_after(&mut self) -> LinkedList { + // We have this: + // + // list.front -> A <-> B <-> C <-> D <- list.back + // ^ + // cur + // + // + // And we want to produce this: + // + // list.front -> A <-> B <- list.back + // ^ + // cur + // + // + // return.front -> C <-> D <- return.back + // + if let Some(cur) = self.cur { + // We are pointing at a real element, so the list is non-empty. + unsafe { + // Current state + let old_len = self.list.len; + let old_idx = self.index.unwrap(); + let next = (*cur.as_ptr()).back; + + // What self will become + let new_len = old_idx + 1; + let new_back = self.cur; + let new_front = self.list.front; + let new_idx = Some(old_idx); + + // What the output will become + let output_len = old_len - new_len; + let output_front = next; + let output_back = self.list.back; + + // Break the links between cur and next + if let Some(next) = next { + (*cur.as_ptr()).back = None; + (*next.as_ptr()).front = None; + } + + // Produce the result: + self.list.len = new_len; + self.list.front = new_front; + self.list.back = new_back; + self.index = new_idx; + + LinkedList { + front: output_front, + back: output_back, + len: output_len, + _boo: PhantomData, + } + } + } else { + // We're at the ghost, just replace our list with an empty one. + // No other state needs to be changed. + std::mem::replace(self.list, LinkedList::new()) + } + } + + pub fn splice_before(&mut self, mut input: LinkedList) { + // We have this: + // + // input.front -> 1 <-> 2 <- input.back + // + // list.front -> A <-> B <-> C <- list.back + // ^ + // cur + // + // + // Becoming this: + // + // list.front -> A <-> 1 <-> 2 <-> B <-> C <- list.back + // ^ + // cur + // + unsafe { + // We can either `take` the input's pointers or `mem::forget` + // it. Using `take` is more responsible in case we ever do custom + // allocators or something that also needs to be cleaned up! + if input.is_empty() { + // Input is empty, do nothing. + } else if let Some(cur) = self.cur { + // Both lists are non-empty + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + if let Some(prev) = (*cur.as_ptr()).front { + // General Case, no boundaries, just internal fixups + (*prev.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(prev); + (*cur.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(cur); + } else { + // No prev, we're appending to the front + (*cur.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(cur); + self.list.front = Some(in_front); + } + // Index moves forward by input length + *self.index.as_mut().unwrap() += input.len; + } else if let Some(back) = self.list.back { + // We're on the ghost but non-empty, append to the back + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + (*back.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(back); + self.list.back = Some(in_back); + } else { + // We're empty, become the input, remain on the ghost + std::mem::swap(self.list, &mut input); + } + + self.list.len += input.len; + // Not necessary but Polite To Do + input.len = 0; + + // Input dropped here + } + } + + pub fn splice_after(&mut self, mut input: LinkedList) { + // We have this: + // + // input.front -> 1 <-> 2 <- input.back + // + // list.front -> A <-> B <-> C <- list.back + // ^ + // cur + // + // + // Becoming this: + // + // list.front -> A <-> B <-> 1 <-> 2 <-> C <- list.back + // ^ + // cur + // + unsafe { + // We can either `take` the input's pointers or `mem::forget` + // it. Using `take` is more responsible in case we ever do custom + // allocators or something that also needs to be cleaned up! + if input.is_empty() { + // Input is empty, do nothing. + } else if let Some(cur) = self.cur { + // Both lists are non-empty + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + if let Some(next) = (*cur.as_ptr()).back { + // General Case, no boundaries, just internal fixups + (*next.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(next); + (*cur.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(cur); + } else { + // No next, we're appending to the back + (*cur.as_ptr()).back = Some(in_front); + (*in_front.as_ptr()).front = Some(cur); + self.list.back = Some(in_back); + } + // Index doesn't change + } else if let Some(front) = self.list.front { + // We're on the ghost but non-empty, append to the front + let in_front = input.front.take().unwrap(); + let in_back = input.back.take().unwrap(); + + (*front.as_ptr()).front = Some(in_back); + (*in_back.as_ptr()).back = Some(front); + self.list.front = Some(in_front); + } else { + // We're empty, become the input, remain on the ghost + std::mem::swap(self.list, &mut input); + } + + self.list.len += input.len; + // Not necessary but Polite To Do + input.len = 0; + + // Input dropped here + } + } +} + +unsafe impl Send for LinkedList {} +unsafe impl Sync for LinkedList {} + +unsafe impl<'a, T: Send> Send for Iter<'a, T> {} +unsafe impl<'a, T: Sync> Sync for Iter<'a, T> {} + +unsafe impl<'a, T: Send> Send for IterMut<'a, T> {} +unsafe impl<'a, T: Sync> Sync for IterMut<'a, T> {} + +#[allow(dead_code)] +fn assert_properties() { + fn is_send() {} + fn is_sync() {} + + is_send::>(); + is_sync::>(); + + is_send::>(); + is_sync::>(); + + is_send::>(); + is_sync::>(); + + is_send::>(); + is_sync::>(); + + fn linked_list_covariant<'a, T>(x: LinkedList<&'static T>) -> LinkedList<&'a T> { + x + } + fn iter_covariant<'i, 'a, T>(x: Iter<'i, &'static T>) -> Iter<'i, &'a T> { + x + } + fn into_iter_covariant<'a, T>(x: IntoIter<&'static T>) -> IntoIter<&'a T> { + x + } + + /// ```compile_fail,E0308 + /// use linked_list::IterMut; + /// + /// fn iter_mut_covariant<'i, 'a, T>(x: IterMut<'i, &'static T>) -> IterMut<'i, &'a T> { x } + /// ``` + fn iter_mut_invariant() {} +} + +#[cfg(test)] +mod test { + use super::LinkedList; + + fn generate_test() -> LinkedList { + list_from(&[0, 1, 2, 3, 4, 5, 6]) + } + + fn list_from(v: &[T]) -> LinkedList { + v.iter().map(|x| (*x).clone()).collect() + } + + #[test] + fn test_basic_front() { + let mut list = LinkedList::new(); + + // Try to break an empty list + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + + // Try to break a one item list + list.push_front(10); + assert_eq!(list.len(), 1); + assert_eq!(list.pop_front(), Some(10)); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + + // Mess around + list.push_front(10); + assert_eq!(list.len(), 1); + list.push_front(20); + assert_eq!(list.len(), 2); + list.push_front(30); + assert_eq!(list.len(), 3); + assert_eq!(list.pop_front(), Some(30)); + assert_eq!(list.len(), 2); + list.push_front(40); + assert_eq!(list.len(), 3); + assert_eq!(list.pop_front(), Some(40)); + assert_eq!(list.len(), 2); + assert_eq!(list.pop_front(), Some(20)); + assert_eq!(list.len(), 1); + assert_eq!(list.pop_front(), Some(10)); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + assert_eq!(list.pop_front(), None); + assert_eq!(list.len(), 0); + } + + #[test] + fn test_basic() { + let mut m = LinkedList::new(); + assert_eq!(m.pop_front(), None); + assert_eq!(m.pop_back(), None); + assert_eq!(m.pop_front(), None); + m.push_front(1); + assert_eq!(m.pop_front(), Some(1)); + m.push_back(2); + m.push_back(3); + assert_eq!(m.len(), 2); + assert_eq!(m.pop_front(), Some(2)); + assert_eq!(m.pop_front(), Some(3)); + assert_eq!(m.len(), 0); + assert_eq!(m.pop_front(), None); + m.push_back(1); + m.push_back(3); + m.push_back(5); + m.push_back(7); + assert_eq!(m.pop_front(), Some(1)); + + let mut n = LinkedList::new(); + n.push_front(2); + n.push_front(3); + { + assert_eq!(n.front().unwrap(), &3); + let x = n.front_mut().unwrap(); + assert_eq!(*x, 3); + *x = 0; + } + { + assert_eq!(n.back().unwrap(), &2); + let y = n.back_mut().unwrap(); + assert_eq!(*y, 2); + *y = 1; + } + assert_eq!(n.pop_front(), Some(0)); + assert_eq!(n.pop_front(), Some(1)); + } + + #[test] + fn test_iterator() { + let m = generate_test(); + for (i, elt) in m.iter().enumerate() { + assert_eq!(i as i32, *elt); + } + let mut n = LinkedList::new(); + assert_eq!(n.iter().next(), None); + n.push_front(4); + let mut it = n.iter(); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(it.next().unwrap(), &4); + assert_eq!(it.size_hint(), (0, Some(0))); + assert_eq!(it.next(), None); + } + + #[test] + fn test_iterator_double_end() { + let mut n = LinkedList::new(); + assert_eq!(n.iter().next(), None); + n.push_front(4); + n.push_front(5); + n.push_front(6); + let mut it = n.iter(); + assert_eq!(it.size_hint(), (3, Some(3))); + assert_eq!(it.next().unwrap(), &6); + assert_eq!(it.size_hint(), (2, Some(2))); + assert_eq!(it.next_back().unwrap(), &4); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(it.next_back().unwrap(), &5); + assert_eq!(it.next_back(), None); + assert_eq!(it.next(), None); + } + + #[test] + fn test_rev_iter() { + let m = generate_test(); + for (i, elt) in m.iter().rev().enumerate() { + assert_eq!(6 - i as i32, *elt); + } + let mut n = LinkedList::new(); + assert_eq!(n.iter().rev().next(), None); + n.push_front(4); + let mut it = n.iter().rev(); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(it.next().unwrap(), &4); + assert_eq!(it.size_hint(), (0, Some(0))); + assert_eq!(it.next(), None); + } + + #[test] + fn test_mut_iter() { + let mut m = generate_test(); + let mut len = m.len(); + for (i, elt) in m.iter_mut().enumerate() { + assert_eq!(i as i32, *elt); + len -= 1; + } + assert_eq!(len, 0); + let mut n = LinkedList::new(); + assert!(n.iter_mut().next().is_none()); + n.push_front(4); + n.push_back(5); + let mut it = n.iter_mut(); + assert_eq!(it.size_hint(), (2, Some(2))); + assert!(it.next().is_some()); + assert!(it.next().is_some()); + assert_eq!(it.size_hint(), (0, Some(0))); + assert!(it.next().is_none()); + } + + #[test] + fn test_iterator_mut_double_end() { + let mut n = LinkedList::new(); + assert!(n.iter_mut().next_back().is_none()); + n.push_front(4); + n.push_front(5); + n.push_front(6); + let mut it = n.iter_mut(); + assert_eq!(it.size_hint(), (3, Some(3))); + assert_eq!(*it.next().unwrap(), 6); + assert_eq!(it.size_hint(), (2, Some(2))); + assert_eq!(*it.next_back().unwrap(), 4); + assert_eq!(it.size_hint(), (1, Some(1))); + assert_eq!(*it.next_back().unwrap(), 5); + assert!(it.next_back().is_none()); + assert!(it.next().is_none()); + } + + #[test] + fn test_eq() { + let mut n: LinkedList = list_from(&[]); + let mut m = list_from(&[]); + assert!(n == m); + n.push_front(1); + assert!(n != m); + m.push_back(1); + assert!(n == m); + + let n = list_from(&[2, 3, 4]); + let m = list_from(&[1, 2, 3]); + assert!(n != m); + } + + #[test] + fn test_ord() { + let n = list_from(&[]); + let m = list_from(&[1, 2, 3]); + assert!(n < m); + assert!(m > n); + assert!(n <= n); + assert!(n >= n); + } + + #[test] + fn test_ord_nan() { + let nan = 0.0f64 / 0.0; + let n = list_from(&[nan]); + let m = list_from(&[nan]); + assert!(!(n < m)); + assert!(!(n > m)); + assert!(!(n <= m)); + assert!(!(n >= m)); + + let n = list_from(&[nan]); + let one = list_from(&[1.0f64]); + assert!(!(n < one)); + assert!(!(n > one)); + assert!(!(n <= one)); + assert!(!(n >= one)); + + let u = list_from(&[1.0f64, 2.0, nan]); + let v = list_from(&[1.0f64, 2.0, 3.0]); + assert!(!(u < v)); + assert!(!(u > v)); + assert!(!(u <= v)); + assert!(!(u >= v)); + + let s = list_from(&[1.0f64, 2.0, 4.0, 2.0]); + let t = list_from(&[1.0f64, 2.0, 3.0, 2.0]); + assert!(!(s < t)); + assert!(s > one); + assert!(!(s <= one)); + assert!(s >= one); + } + + #[test] + fn test_debug() { + let list: LinkedList = (0..10).collect(); + assert_eq!(format!("{:?}", list), "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"); + + let list: LinkedList<&str> = vec!["just", "one", "test", "more"] + .iter() + .copied() + .collect(); + assert_eq!(format!("{:?}", list), r#"["just", "one", "test", "more"]"#); + } + + #[test] + fn test_hashmap() { + // Check that HashMap works with this as a key + + let list1: LinkedList = (0..10).collect(); + let list2: LinkedList = (1..11).collect(); + let mut map = std::collections::HashMap::new(); + + assert_eq!(map.insert(list1.clone(), "list1"), None); + assert_eq!(map.insert(list2.clone(), "list2"), None); + + assert_eq!(map.len(), 2); + + assert_eq!(map.get(&list1), Some(&"list1")); + assert_eq!(map.get(&list2), Some(&"list2")); + + assert_eq!(map.remove(&list1), Some("list1")); + assert_eq!(map.remove(&list2), Some("list2")); + + assert!(map.is_empty()); + } + + #[test] + fn test_cursor_move_peek() { + let mut m: LinkedList = LinkedList::new(); + m.extend([1, 2, 3, 4, 5, 6]); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + assert_eq!(cursor.current(), Some(&mut 1)); + assert_eq!(cursor.peek_next(), Some(&mut 2)); + assert_eq!(cursor.peek_prev(), None); + assert_eq!(cursor.index(), Some(0)); + cursor.move_prev(); + assert_eq!(cursor.current(), None); + assert_eq!(cursor.peek_next(), Some(&mut 1)); + assert_eq!(cursor.peek_prev(), Some(&mut 6)); + assert_eq!(cursor.index(), None); + cursor.move_next(); + cursor.move_next(); + assert_eq!(cursor.current(), Some(&mut 2)); + assert_eq!(cursor.peek_next(), Some(&mut 3)); + assert_eq!(cursor.peek_prev(), Some(&mut 1)); + assert_eq!(cursor.index(), Some(1)); + + let mut cursor = m.cursor_mut(); + cursor.move_prev(); + assert_eq!(cursor.current(), Some(&mut 6)); + assert_eq!(cursor.peek_next(), None); + assert_eq!(cursor.peek_prev(), Some(&mut 5)); + assert_eq!(cursor.index(), Some(5)); + cursor.move_next(); + assert_eq!(cursor.current(), None); + assert_eq!(cursor.peek_next(), Some(&mut 1)); + assert_eq!(cursor.peek_prev(), Some(&mut 6)); + assert_eq!(cursor.index(), None); + cursor.move_prev(); + cursor.move_prev(); + assert_eq!(cursor.current(), Some(&mut 5)); + assert_eq!(cursor.peek_next(), Some(&mut 6)); + assert_eq!(cursor.peek_prev(), Some(&mut 4)); + assert_eq!(cursor.index(), Some(4)); + } + + #[test] + fn test_cursor_mut_insert() { + let mut m: LinkedList = LinkedList::new(); + m.extend([1, 2, 3, 4, 5, 6]); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.splice_before(Some(7).into_iter().collect()); + cursor.splice_after(Some(8).into_iter().collect()); + // check_links(&m); + assert_eq!( + m.iter().cloned().collect::>(), + &[7, 1, 8, 2, 3, 4, 5, 6] + ); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.move_prev(); + cursor.splice_before(Some(9).into_iter().collect()); + cursor.splice_after(Some(10).into_iter().collect()); + check_links(&m); + assert_eq!( + m.iter().cloned().collect::>(), + &[10, 7, 1, 8, 2, 3, 4, 5, 6, 9] + ); + + /* remove_current not impl'd + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.move_prev(); + assert_eq!(cursor.remove_current(), None); + cursor.move_next(); + cursor.move_next(); + assert_eq!(cursor.remove_current(), Some(7)); + cursor.move_prev(); + cursor.move_prev(); + cursor.move_prev(); + assert_eq!(cursor.remove_current(), Some(9)); + cursor.move_next(); + assert_eq!(cursor.remove_current(), Some(10)); + check_links(&m); + assert_eq!(m.iter().cloned().collect::>(), &[1, 8, 2, 3, 4, 5, 6]); + */ + + let mut m: LinkedList = LinkedList::new(); + m.extend([1, 8, 2, 3, 4, 5, 6]); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + let mut p: LinkedList = LinkedList::new(); + p.extend([100, 101, 102, 103]); + let mut q: LinkedList = LinkedList::new(); + q.extend([200, 201, 202, 203]); + cursor.splice_after(p); + cursor.splice_before(q); + check_links(&m); + assert_eq!( + m.iter().cloned().collect::>(), + &[200, 201, 202, 203, 1, 100, 101, 102, 103, 8, 2, 3, 4, 5, 6] + ); + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.move_prev(); + let tmp = cursor.split_before(); + assert_eq!(m.into_iter().collect::>(), &[]); + m = tmp; + let mut cursor = m.cursor_mut(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + cursor.move_next(); + let tmp = cursor.split_after(); + assert_eq!( + tmp.into_iter().collect::>(), + &[102, 103, 8, 2, 3, 4, 5, 6] + ); + check_links(&m); + assert_eq!( + m.iter().cloned().collect::>(), + &[200, 201, 202, 203, 1, 100, 101] + ); + } + + fn check_links(list: &LinkedList) { + let from_front: Vec<_> = list.iter().collect(); + let from_back: Vec<_> = list.iter().rev().collect(); + let re_reved: Vec<_> = from_back.into_iter().rev().collect(); + + assert_eq!(from_front, re_reved); + } +} From c75dc9a7cd478faff3a949ef49a6c78f9bb29b3d Mon Sep 17 00:00:00 2001 From: Ian Bull Date: Tue, 2 Jul 2024 10:06:33 -0700 Subject: [PATCH 2/2] Fix an empty split bug Fixes a bug in the tutorial that happened when `split_before` and `split_after` resulted in an empty split. This was fixed by checking for an empty split and updating the `front` and `back` pointers accordingly. This change-set fixes both the final code and the tutorial text. --- .github/workflows/main.yml | 4 +-- lists/src/sixth.rs | 45 +++++++++++++++++++++++++++++++--- src/sixth-cursors-testing.md | 40 ++++++++++++++++++++++++++++++ src/sixth-final.md | 47 ++++++++++++++++++++++++++++++++---- 4 files changed, 125 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a99f49e..992ecfe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,8 +3,8 @@ name: Rust on: push: branches: - - "master" - - "!gh-pages" + - 'master' + - '!gh-pages' pull_request: jobs: diff --git a/lists/src/sixth.rs b/lists/src/sixth.rs index 2eef219..5db3f98 100644 --- a/lists/src/sixth.rs +++ b/lists/src/sixth.rs @@ -535,8 +535,13 @@ impl<'a, T> CursorMut<'a, T> { // What the output will become let output_len = old_len - new_len; - let output_front = self.list.front; - let output_back = prev; + let mut output_front = self.list.front; + let mut output_back = prev; + + if output_len == 0 { + output_front = None; + output_back = None; + } // Break the links between cur and prev if let Some(prev) = prev { @@ -597,8 +602,13 @@ impl<'a, T> CursorMut<'a, T> { // What the output will become let output_len = old_len - new_len; - let output_front = next; - let output_back = self.list.back; + let mut output_front = next; + let mut output_back = self.list.back; + + if output_len == 0 { + output_front = None; + output_back = None; + } // Break the links between cur and next if let Some(next) = next { @@ -1190,6 +1200,33 @@ mod test { ); } + #[test] + fn test_empty_slice() { + let mut list = LinkedList::new(); + list.push_back(7); + list.push_back(8); + list.push_back(9); + + let mut cur = list.cursor_mut(); + cur.move_next(); // Point at the first node + let result = cur.split_before(); // Everything before the first node + + assert_eq!(result.len, 0); + assert_eq!(result.front(), None); + assert_eq!(result.back(), None); + + assert_eq!(list.len, 3); + assert_eq!(list.front(), Some(7).as_ref()); + + let mut cur = list.cursor_mut(); + cur.move_prev(); // Point at the last node + let result = cur.split_after(); // Everything after the last node + + assert_eq!(result.len, 0); + assert_eq!(result.front(), None); + assert_eq!(result.back(), None); + } + fn check_links(list: &LinkedList) { let from_front: Vec<_> = list.iter().collect(); let from_back: Vec<_> = list.iter().rev().collect(); diff --git a/src/sixth-cursors-testing.md b/src/sixth-cursors-testing.md index fd20a25..9135ca3 100644 --- a/src/sixth-cursors-testing.md +++ b/src/sixth-cursors-testing.md @@ -377,6 +377,46 @@ DONE. Done. +> **Community Contribution:** After completing the exercises a small bug was discovered when creating an empty slice from the list. + +```rust ,ignore + #[test] + fn test_empty_slice() { + let mut list = LinkedList::new(); + list.push_back(7); + list.push_back(8); + list.push_back(9); + + let mut cur = list.cursor_mut(); + cur.move_next(); // Point at the first node + let result = cur.split_before(); // Everything before the first node + + assert_eq!(result.len, 0); + assert_eq!(result.front(), None); + assert_eq!(result.back(), None); + + assert_eq!(list.len, 3); + assert_eq!(list.front(), Some(7).as_ref()); + + let mut cur = list.cursor_mut(); + cur.move_prev(); // Point at the last node + let result = cur.split_after(); // Everything after the last node + + assert_eq!(result.len, 0); + assert_eq!(result.front(), None); + assert_eq!(result.back(), None); + } +``` + +In this case, we attempt to create a slice from all the elements before the first node (which should be empty). This is because the `split_before` method does not set the front and back pointers to None when the list is empty. This can be fixed by adding the following line to the `split_before` method: + +```rust ,ignore + if self.len == 0 { + self.front = None; + self.back = None; + } +``` + We did it. We made a god damn production-quality LinkedList, with basically all the same functionality as the one in std. Are we missing little convenience methods here and there? Absolutely. Will I add them into the final published version of the crate? Probably! But, I am, So Very Tired. diff --git a/src/sixth-final.md b/src/sixth-final.md index a895ce1..6b18ac0 100644 --- a/src/sixth-final.md +++ b/src/sixth-final.md @@ -546,8 +546,13 @@ impl<'a, T> CursorMut<'a, T> { // What the output will become let output_len = old_len - new_len; - let output_front = self.list.front; - let output_back = prev; + let mut output_front = self.list.front; + let mut output_back = prev; + + if output_len == 0 { + output_front = None; + output_back = None; + } // Break the links between cur and prev if let Some(prev) = prev { @@ -608,8 +613,13 @@ impl<'a, T> CursorMut<'a, T> { // What the output will become let output_len = old_len - new_len; - let output_front = next; - let output_back = self.list.back; + let mut output_front = next; + let mut output_back = self.list.back; + + if output_len == 0 { + output_front = None; + output_back = None; + } // Break the links between cur and next if let Some(next) = next { @@ -1201,6 +1211,33 @@ mod test { ); } + #[test] + fn test_empty_slice() { + let mut list = LinkedList::new(); + list.push_back(7); + list.push_back(8); + list.push_back(9); + + let mut cur = list.cursor_mut(); + cur.move_next(); // Point at the first node + let result = cur.split_before(); // Everything before the first node + + assert_eq!(result.len, 0); + assert_eq!(result.front(), None); + assert_eq!(result.back(), None); + + assert_eq!(list.len, 3); + assert_eq!(list.front(), Some(7).as_ref()); + + let mut cur = list.cursor_mut(); + cur.move_prev(); // Point at the last node + let result = cur.split_after(); // Everything after the last node + + assert_eq!(result.len, 0); + assert_eq!(result.front(), None); + assert_eq!(result.back(), None); + } + fn check_links(list: &LinkedList) { let from_front: Vec<_> = list.iter().collect(); let from_back: Vec<_> = list.iter().rev().collect(); @@ -1209,4 +1246,4 @@ mod test { assert_eq!(from_front, re_reved); } } -``` \ No newline at end of file +```