Skip to content

Commit

Permalink
Supercede #147
Browse files Browse the repository at this point in the history
  • Loading branch information
novacrazy committed Jul 5, 2024
1 parent 13ddbf3 commit 1402e29
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 0 deletions.
133 changes: 133 additions & 0 deletions src/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,136 @@ where
}
}
}

/// Defines a `GenericSequence` which can be shortened by removing an element at a given index.
///
/// # Safety
/// While the [`remove`](Remove::remove) and [`swap_remove`](Remove::swap_remove) methods are marked safe,
/// care must be taken when implementing it. The [`remove_unchecked`](Remove::remove_unchecked)
/// and [`swap_remove_unchecked`](Remove::swap_remove_unchecked) methods are unsafe
/// and must be used with caution.
pub unsafe trait Remove<T, N: ArrayLength>: GenericSequence<T> {
/// Resulting sequence formed by removing an element at the given index.
type Output: GenericSequence<T>;

/// Removes an element at the given index, shifting elements
/// after the given index to the left to fill the gap, resulting
/// in a time complexity of O(n) where `n=N-idx-1`
///
/// # Example
///
/// ```rust
/// # use generic_array::{arr, sequence::Remove};
/// let a = arr![1, 2, 3, 4];
///
/// let (removed, b) = a.remove(2);
/// assert_eq!(removed, 3);
/// assert_eq!(b, arr![1, 2, 4]);
/// ```
///
/// # Panics
///
/// Panics if the index is out of bounds.
#[inline]
fn remove(self, idx: usize) -> (T, Self::Output) {
assert!(
idx < N::USIZE,
"Index out of bounds: the len is {} but the index is {}",
N::USIZE,
idx
);

unsafe { self.remove_unchecked(idx) }
}

/// Removes an element at the given index, swapping it with the last element.
///
/// # Example
///
/// ```rust
/// # use generic_array::{arr, sequence::Remove};
/// let a = arr![1, 2, 3, 4];
///
/// let (removed, b) = a.swap_remove(1);
/// assert_eq!(removed, 2);
/// assert_eq!(b, arr![1, 4, 3]); // note 4 is now at index 1
/// ```
///
/// # Panics
///
/// Panics if the index is out of bounds.
fn swap_remove(self, idx: usize) -> (T, Self::Output) {
assert!(
idx < N::USIZE,
"Index out of bounds: the len is {} but the index is {}",
N::USIZE,
idx
);

unsafe { self.swap_remove_unchecked(idx) }
}

/// Removes an element at the given index without bounds checking,
/// shifting elements after the given index to the left to fill the gap,
/// resulting in a time complexity of O(n) where `n=N-idx-1`
///
/// See [`remove`](Remove::remove) for an example.
///
/// # Safety
/// The caller must ensure that the index is within bounds, otherwise
/// it is undefined behavior.
unsafe fn remove_unchecked(self, idx: usize) -> (T, Self::Output);

/// Removes an element at the given index without bounds checking, swapping it with the last element.
///
/// See [`swap_remove`](Remove::swap_remove) for an example.
///
/// # Safety
/// The caller must ensure that the index is within bounds, otherwise
/// it is undefined behavior.
unsafe fn swap_remove_unchecked(self, idx: usize) -> (T, Self::Output);
}

unsafe impl<T, N> Remove<T, N> for GenericArray<T, N>
where
N: ArrayLength + Sub<B1>,
Sub1<N>: ArrayLength,
{
type Output = GenericArray<T, Sub1<N>>;

#[inline]
unsafe fn remove_unchecked(self, idx: usize) -> (T, Self::Output) {
if idx >= N::USIZE || N::USIZE == 0 {
core::hint::unreachable_unchecked();
}

let mut array = ManuallyDrop::new(self);

let dst = array.as_mut_ptr().add(idx);

let removed = ptr::read(dst);

// shift all elements over by one to fill gap
ptr::copy(dst.add(1), dst, N::USIZE - idx - 1);

// return removed element and truncated array
(removed, mem::transmute_copy(&array))
}

#[inline]
unsafe fn swap_remove_unchecked(self, idx: usize) -> (T, Self::Output) {
if idx >= N::USIZE || N::USIZE == 0 {
core::hint::unreachable_unchecked();
}

let mut array = ManuallyDrop::new(self);

array.swap(idx, N::USIZE - 1);

// remove the last element
let removed = ptr::read(array.as_ptr().add(N::USIZE - 1));

// return removed element and truncated array
(removed, mem::transmute_copy(&array))
}
}
35 changes: 35 additions & 0 deletions tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,41 @@ fn test_concat() {
assert_eq!(e, arr![3, 4, 5]);
}

#[test]
fn test_removes() {
let a = arr![1, 2, 3, 4];

for i in 0..4 {
let (b, c) = a.remove(i);

assert_eq!(b, i + 1);
assert_eq!(
c,
match i {
0 => arr![2, 3, 4],
1 => arr![1, 3, 4],
2 => arr![1, 2, 4],
3 => arr![1, 2, 3],
_ => unreachable!(),
}
);

let (b, c) = a.swap_remove(i);

assert_eq!(b, i + 1);
assert_eq!(
c,
match i {
0 => arr![4, 2, 3],
1 => arr![1, 4, 3],
2 => arr![1, 2, 4],
3 => arr![1, 2, 3],
_ => unreachable!(),
}
);
}
}

#[test]
fn test_fold() {
let a = arr![1, 2, 3, 4];
Expand Down

0 comments on commit 1402e29

Please sign in to comment.