2

I have a Vec that is the allocation for a circular buffer. Let's assume the buffer is full, so there are no elements in the allocation that aren't in the circular buffer. I now want to turn that circular buffer into a Vec where the first element of the circular buffer is also the first element of the Vec. As an example I have this (allocating) function:

fn normalize(tail: usize, buf: Vec<usize>) -> Vec<usize> {
    let n = buf.len();
    buf[tail..n]
        .iter()
        .chain(buf[0..tail].iter())
        .cloned()
        .collect()
}

Playground

Obviously this can also be done without allocating anything, since we already have an allocation that is large enough, and we have a swap operation to swap arbitrary elements of the allocation.

fn normalize(tail: usize, mut buf: Vec<usize>) -> Vec<usize> {
    for _ in 0..tail {
        for i in 0..(buf.len() - 1) {
            buf.swap(i, i + 1);
        }
    }
    buf
}

Playground

Sadly this requires buf.len() * tail swap operations. I'm fairly sure it can be done in buf.len() + tail swap operations. For concrete values of tail and buf.len() I have been able to figure out solutions, but I'm not sure how to do it in the general case.

My recursive partial solution can be seen in action.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
oli_obk
  • 28,729
  • 6
  • 82
  • 98

2 Answers2

9

The simplest solution is to use 3 reversals, indeed this is what is recommended in Algorithm to rotate an array in linear time.

//  rotate to the left by "k".
fn rotate<T>(array: &mut [T], k: usize) {
    if array.is_empty() { return; }

    let k = k % array.len();

    array[..k].reverse();
    array[k..].reverse();
    array.reverse();
}

While this is linear, this requires reading and writing each element at most twice (reversing a range with an odd number of elements does not require touching the middle element). On the other hand, the very predictable access pattern of the reversal plays nice with prefetching, YMMV.

Community
  • 1
  • 1
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • NB. you don't need to implement [`reverse`](http://doc.rust-lang.org/std/primitive.slice.html#method.reverse) yourself: `array[..k].reverse(); array[k..].reverse(); array.reverse();` should work. – huon Nov 02 '15 at 12:58
  • @huon: Humpf... I did not even thought about looking for it. Not only does it work, but it also makes it much simpler. – Matthieu M. Nov 02 '15 at 13:04
5

This operation is typically called a "rotation" of the vector, e.g. the C++ standard library has std::rotate to do this. There are known algorithms for doing the operation, although you may have to quite careful when porting if you're trying to it generically/with non-Copy types, where swaps become key, as one can't generally just read something straight out from a vector.

That said, one is likely to be able to use unsafe code with std::ptr::read/std::ptr::write for this, since data is just being moved around, and hence there's no need to execute caller-defined code or very complicated concerns about exception safety.

A port of the C code in the link above (by @ker):

fn rotate(k: usize, a: &mut [i32]) {
    if k == 0 { return }

    let mut c = 0;
    let n = a.len();
    let mut v = 0;
    while c < n {
        let mut t = v;
        let mut tp = v + k;
        let tmp = a[v];
        c += 1;
        while tp != v {
            a[t] = a[tp];
            t = tp;
            tp += k;
            if tp >= n { tp -= n; }
            c += 1;
        }
        a[t] = tmp;
        v += 1;
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
huon
  • 94,605
  • 21
  • 231
  • 225
  • in fact it gets even more compilcated when the ring-buffer is not full, as then I need to prevent reads from uninitialized memory. But that should be surmountable. – oli_obk Nov 02 '15 at 13:21
  • @ker: I think you can deal with the not full case relatively easily if you are willing to implement a two-steps process: (1) move all elements to the front (where `Vec` will need them) and (2) shuffle appropriately. Of course, dealing with everything at once would be more efficient in terms of moves... but (1) can be implement with bulk `memcpy` (and thus vectorized operations) so the two steps process might end up more cache-friendly. – Matthieu M. Nov 02 '15 at 15:12
  • that's actually pretty smart. But I'm still not sure about the uninitialized memory. Am I allowed to memcpy uninitialized memory? – oli_obk Nov 03 '15 at 07:30
  • Your port can be made move semantics aware by just removing the `tmp` variable and using `a.swap(t, tp)` instead of `a[t] = a[tp]` – bluss Nov 07 '15 at 02:37