Skip to content

Commit

Permalink
add split_off to RingBuf
Browse files Browse the repository at this point in the history
  • Loading branch information
Gankra committed Feb 7, 2015
1 parent 80627cd commit dc930b1
Showing 1 changed file with 102 additions and 0 deletions.
102 changes: 102 additions & 0 deletions src/libcollections/ring_buf.rs
Expand Up @@ -1289,6 +1289,70 @@ impl<T> RingBuf<T> {

return elem;
}

/// Splits the collection into two at the given index.
///
/// Returns a newly allocated `Self`. `self` contains elements `[0, at)`,
/// and the returned `Self` contains elements `[at, len)`.
///
/// Note that the capacity of `self` does not change.
///
/// # Panics
///
/// Panics if `at > len`
///
/// # Examples
/// ```rust
/// let mut buf: RingBuf<_> = vec![1,2,3].into_iter().collect();
/// let buf2 = buf.split_off(1);
/// // buf = [1], buf2 = [2, 3]
/// assert_eq!(buf.len(), 1);
/// assert_eq!(buf2.len(), 2);
/// ```
#[inline]
#[unstable(feature = "collections",
reason = "new API, waiting for dust to settle")]
pub fn split_off(&mut self, at: usize) -> Self {
let len = self.len();
assert!(at <= len, "`at` out of bounds");

let other_len = len - at;
let mut other = RingBuf::with_capacity(other_len);

unsafe {
let (first_half, second_half) = self.as_slices();

let first_len = first_half.len();
let second_len = second_half.len();
if at < first_len {
// `at` lies in the first half.
let amount_in_first = first_len - at;

ptr::copy_nonoverlapping_memory(other.ptr,
first_half.as_ptr().offset(at as isize),
amount_in_first);

// just take all of the second half.
ptr::copy_nonoverlapping_memory(other.ptr.offset(amount_in_first as isize),
second_half.as_ptr(),
second_len);
} else {
// `at` lies in the second half, need to factor in the elements we skipped
// in the first half.
let offset = at - first_len;
let amount_in_second = second_len - offset;
ptr::copy_nonoverlapping_memory(other.ptr,
second_half.as_ptr().offset(offset as isize),
amount_in_second);
}
}

// Cleanup where the ends of the buffers are
self.head = self.wrap_index(self.head - other_len);
other.head = other.wrap_index(other_len);

other
}
}

impl<T: Clone> RingBuf<T> {
Expand Down Expand Up @@ -2711,4 +2775,42 @@ mod tests {
assert_eq!(ring.len() as i32, cap);
assert_eq!(ring.capacity() as i32, cap);
}

#[test]
fn test_split_off() {
// This test checks that every single combination of tail position, length, and
// split position is tested. Capacity 15 should be large enough to cover every case.

let mut tester = RingBuf::with_capacity(15);
// can't guarantee we got 15, so have to get what we got.
// 15 would be great, but we will definitely get 2^k - 1, for k >= 4, or else
// this test isn't covering what it wants to
let cap = tester.capacity();

// len is the length *before* splitting
for len in 0..cap {
// index to split at
for at in 0..len + 1 {
// 0, 1, 2, .., at - 1 (may be empty)
let expected_self = iter::count(0, 1).take(at).collect();
// at, at + 1, .., len - 1 (may be empty)
let expected_other = iter::count(at, 1).take(len - at).collect();

for tail_pos in 0..cap {
tester.tail = tail_pos;
tester.head = tail_pos;
for i in 0..len {
tester.push_back(i);
}
let result = tester.split_off(at);
assert!(tester.tail < tester.cap);
assert!(tester.head < tester.cap);
assert!(result.tail < result.cap);
assert!(result.head < result.cap);
assert_eq!(tester, expected_self);
assert_eq!(result, expected_other);
}
}
}
}
}

0 comments on commit dc930b1

Please sign in to comment.