Skip to content

Commit

Permalink
auto merge of #19569 : murphm8/rust/ring_buf_insert, r=Gankro
Browse files Browse the repository at this point in the history
This is a first pass at insert on RingBuf. I tried to keep it as simple as possible. I'm not sure of the performance implications of doing one copy vs. copying multiple times but moving a smaller amount of memory. I chose to stick with one copy, even if the amount of memory I have to move is larger.

I believe this is part of #18424 

@gankro mentioned this was missing.
  • Loading branch information
bors committed Dec 12, 2014
2 parents 9146a91 + 40f28c7 commit ffc1118
Showing 1 changed file with 267 additions and 1 deletion.
268 changes: 267 additions & 1 deletion src/libcollections/ring_buf.rs
Expand Up @@ -99,6 +99,21 @@ impl<T> RingBuf<T> {
/// Returns the index in the underlying buffer for a given logical element index.
#[inline]
fn wrap_index(&self, idx: uint) -> uint { wrap_index(idx, self.cap) }

/// Copies a contiguous block of memory len long from src to dst
#[inline]
fn copy(&self, dst: uint, src: uint, len: uint) {
unsafe {
debug_assert!(dst + len <= self.cap, "dst={} src={} len={} cap={}", dst, src, len,
self.cap);
debug_assert!(src + len <= self.cap, "dst={} src={} len={} cap={}", dst, src, len,
self.cap);
ptr::copy_memory(
self.ptr.offset(dst as int),
self.ptr.offset(src as int) as *const T,
len);
}
}
}

impl<T> RingBuf<T> {
Expand Down Expand Up @@ -648,6 +663,213 @@ impl<T> RingBuf<T> {
unsafe { Some(self.buffer_read(head)) }
}
}

/// Inserts an element at position `i` within the ringbuf. Whichever
/// end is closer to the insertion point will be moved to make room,
/// and all the affected elements will be moved to new positions.
///
/// # Panics
///
/// Panics if `i` is greater than ringbuf's length
///
/// # Example
/// ```rust
/// use std::collections::RingBuf;
///
/// let mut buf = RingBuf::new();
/// buf.push_back(10i);
/// buf.push_back(12);
/// buf.insert(1,11);
/// assert_eq!(Some(&11), buf.get(1));
/// ```
pub fn insert(&mut self, i: uint, t: T) {
assert!(i <= self.len(), "index out of bounds");
if self.is_full() {
self.reserve(1);
debug_assert!(!self.is_full());
}

// Move the least number of elements in the ring buffer and insert
// the given object
//
// At most len/2 - 1 elements will be moved. O(min(n, n-i))
//
// There are three main cases:
// Elements are contiguous
// - special case when tail is 0
// Elements are discontiguous and the insert is in the tail section
// Elements are discontiguous and the insert is in the head section
//
// For each of those there are two more cases:
// Insert is closer to tail
// Insert is closer to head
//
// Key: H - self.head
// T - self.tail
// o - Valid element
// I - Insertion element
// A - The element that should be after the insertion point
// M - Indicates element was moved

let idx = self.wrap_index(self.tail + i);

let distance_to_tail = i;
let distance_to_head = self.len() - i;

let contiguous = self.tail <= self.head;

match (contiguous, distance_to_tail <= distance_to_head, idx >= self.tail) {
(true, true, _) if i == 0 => {
// push_front
//
// T
// I H
// [A o o o o o o . . . . . . . . .]
//
// H T
// [A o o o o o o o . . . . . I]
//

self.tail = self.wrap_index(self.tail - 1);
},
(true, true, _) => {
// contiguous, insert closer to tail:
//
// T I H
// [. . . o o A o o o o . . . . . .]
//
// T H
// [. . o o I A o o o o . . . . . .]
// M M
//
// contiguous, insert closer to tail and tail is 0:
//
//
// T I H
// [o o A o o o o . . . . . . . . .]
//
// H T
// [o I A o o o o o . . . . . . . o]
// M M

let old_tail = self.tail;
self.tail = self.wrap_index(self.tail - 1);

self.copy(self.tail, old_tail, 1);
self.copy(old_tail, old_tail + 1, i);
},
(true, false, _) => {
// contiguous, insert closer to head:
//
// T I H
// [. . . o o o o A o o . . . . . .]
//
// T H
// [. . . o o o o I A o o . . . . .]
// M M M

let old_head = self.head;
self.head = self.wrap_index(self.head + 1);
self.copy(idx + 1, idx, old_head - idx);
},
(false, true, true) => {
// discontiguous, tail section, insert closer to tail:
//
// H T I
// [o o o o o o . . . . . o o A o o]
//
// H T
// [o o o o o o . . . . o o I A o o]
// M M

let old_tail = self.tail;
self.tail = self.tail - 1;
self.copy(self.tail, old_tail, i);
},
(false, false, true) => {
// discontiguous, tail section, insert closer to head:
//
// H T I
// [o o . . . . . . . o o o o o A o]
//
// H T
// [o o o . . . . . . o o o o o I A]
// M M M M

let old_head = self.head;
self.head = self.head + 1;

// copy elements up to new head
self.copy(1, 0, old_head);

// copy last element into empty spot at bottom of buffer
self.copy(0, self.cap - 1, 1);

// move elements from idx to end forward not including ^ element
self.copy(idx + 1, idx, self.cap - 1 - idx);
},
(false, true, false) if idx == 0 => {
// discontiguous, head section, insert is closer to tail,
// and is at index zero in the internal buffer:
//
// I H T
// [A o o o o o o o o o . . . o o o]
//
// H T
// [A o o o o o o o o o . . o o o I]
// M M M

let old_tail = self.tail;
self.tail = self.tail - 1;
// copy elements up to new tail
self.copy(old_tail - 1, old_tail, i);

// copy last element into empty spot at bottom of buffer
self.copy(self.cap - 1, 0, 1);
},
(false, true, false) => {
// discontiguous, head section, insert closer to tail:
//
// I H T
// [o o o A o o o o o o . . . o o o]
//
// H T
// [o o I A o o o o o o . . o o o o]
// M M M M M M

let old_tail = self.tail;
self.tail = self.tail - 1;
// copy elements up to new tail
self.copy(old_tail - 1, old_tail, i);

// copy last element into empty spot at bottom of buffer
self.copy(self.cap - 1, 0, 1);

// move elements from idx-1 to end forward not including ^ element
self.copy(0, 1, idx - 1);
}
(false, false, false) => {
// discontiguous, head section, insert closer to head:
//
// I H T
// [o o o o A o o . . . . . . o o o]
//
// H T
// [o o o o I A o o . . . . . o o o]
// M M M

let old_head = self.head;
self.head = self.head + 1;
self.copy(idx + 1, idx, old_head - idx);
}
}

// tail might've been changed so we need to recalculate
let new_idx = self.wrap_index(self.tail + i);
unsafe {
self.buffer_write(new_idx, t);
}
}
}

/// Returns the index in the underlying buffer for a given logical element index.
Expand Down Expand Up @@ -878,6 +1100,7 @@ impl<T: fmt::Show> fmt::Show for RingBuf<T> {

#[cfg(test)]
mod tests {
use core::iter;
use self::Taggy::*;
use self::Taggypar::*;
use std::fmt::Show;
Expand Down Expand Up @@ -1102,7 +1325,6 @@ mod tests {
})
}


#[deriving(Clone, PartialEq, Show)]
enum Taggy {
One(int),
Expand Down Expand Up @@ -1666,4 +1888,48 @@ mod tests {
assert_eq!(ring.get_mut(1), Some(&mut 2));
assert_eq!(ring.get_mut(2), None);
}

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

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


// len is the length *after* insertion
for len in range(1, cap) {
// 0, 1, 2, .., len - 1
let expected = iter::count(0, 1).take(len).collect();
for tail_pos in range(0, cap) {
for to_insert in range(0, len) {
tester.tail = tail_pos;
tester.head = tail_pos;
for i in range(0, len) {
if i != to_insert {
tester.push_back(i);
}
}
tester.insert(to_insert, to_insert);
assert_eq!(tester, expected);
}
}
}
}

#[test]
fn test_front() {
let mut ring = RingBuf::new();
ring.push_back(10i);
ring.push_back(20i);
assert_eq!(ring.front(), Some(&10));
ring.pop_front();
assert_eq!(ring.front(), Some(&20));
ring.pop_front();
assert_eq!(ring.front(), None);
}
}

0 comments on commit ffc1118

Please sign in to comment.