diff --git a/src/lib.rs b/src/lib.rs index 07465bb..1c66009 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,6 +91,7 @@ use serde::{ Deserialize, Serialize, }; +use core::fmt; use core::iter; use core::mem; use core::{cmp::Ordering, num::NonZeroUsize}; @@ -215,6 +216,22 @@ impl ExactSizeIterator for Iter<'_, T> { impl core::iter::FusedIterator for Iter<'_, T> {} +pub enum RemoveError { + LengthOne, + IndexOutOfBounds { index: usize, len: usize }, +} + +impl fmt::Debug for RemoveError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::LengthOne => write!(f, "Cannot remove from NonEmpty of length 1"), + Self::IndexOutOfBounds { index, len } => { + write!(f, "removal index ({index}) should be < len ({len})") + } + } + } +} + impl NonEmpty { /// Alias for [`NonEmpty::singleton`]. pub const fn new(e: T) -> Self { @@ -338,6 +355,90 @@ impl NonEmpty { } } + /// Removes an element from the vector, shifting all elements after it to the left. + /// + /// # Panics + /// + /// Panics if the index is out of bounds or the tail vector is empty. + pub fn remove(&mut self, index: usize) -> T { + self.checked_remove(index).unwrap() + } + + /// Checks if the element at a certain index can be removed from the vector, then removes it, + /// shifting all elements after it to the left. + /// + /// # Errors + /// + /// Returns `Err` if the index is out of bounds or the tail vector is empty. + /// + /// # Examples + /// + /// ``` + /// use nonempty::NonEmpty; + /// + /// let mut non_empty = nonempty![1, 2, 3]; + /// assert_eq!(non_empty.checked_remove(1), Ok(2)); + /// assert_eq!(non_empty, nonempty![1, 3]); + /// assert_eq!(non_empty.checked_remove(3), Err(RemoveError::IndexOutOfBounds { index: 3, len: 2 })); + /// assert_eq!(non_empty.checked_remove(0), Ok(1)); + /// assert_eq!(non_empty, nonempty![3]); + /// assert_eq!(non_empty.checked_remove(0), Err(RemoveError::LengthOne)); + /// ``` + pub fn checked_remove(&mut self, index: usize) -> Result { + if self.tail.is_empty() { + return Err(RemoveError::LengthOne); + } + let len = self.len(); + if index >= len { + return Err(RemoveError::IndexOutOfBounds { index, len }); + } + match index.checked_sub(1) { + None => Ok(mem::replace(&mut self.head, self.tail.remove(0))), + Some(tail_index) => Ok(self.tail.remove(tail_index)), + } + } + + /// Removes an element from the vector, replacing it with the last element. + /// + /// # Panics + /// + /// Panics if the index is out of bounds or the tail vector is empty. + pub fn swap_remove(&mut self, index: usize) -> T { + self.checked_swap_remove(index).unwrap() + } + + /// Checks if the element at a certain index can be removed from the vector, then removes it, replacing it with the last element. + /// + /// # Errors + /// + /// Returns `Err` if the index is out of bounds or the tail vector is empty. + /// + /// # Examples + /// + /// ``` + /// use nonempty::NonEmpty; + /// + /// let mut non_empty = nonempty![1, 2, 3, 4]; + /// assert_eq!(non_empty.checked_swap_remove(1), Ok(2)); + /// assert_eq!(non_empty, nonempty![1, 4, 3]); + /// assert_eq!(non_empty.checked_swap_remove(3), Err(RemoveError::IndexOutOfBounds { index: 3, len: 3 })); + /// assert_eq!(non_empty.checked_swap_remove(0), Ok(1)); + /// assert_eq!(non_empty, nonempty![3, 4]); + /// ``` + pub fn checked_swap_remove(&mut self, index: usize) -> Result { + if self.tail.is_empty() { + return Err(RemoveError::LengthOne); + } + let len = self.len(); + if index >= len { + return Err(RemoveError::IndexOutOfBounds { index, len }); + } + match index.checked_sub(1) { + None => Ok(mem::replace(&mut self.head, self.tail.pop().unwrap())), + Some(tail_index) => Ok(self.tail.swap_remove(tail_index)), + } + } + /// Get the length of the list. pub fn len(&self) -> usize { self.tail.len() + 1