In the destination buffer after rotation buffer position n
gets the contens of position (n + f) % N
. The tricky part is the fact that all sorts of sequences of replacement can occur. This can be handled by traversing these sequences until the original position occurs. Keeping track of how many replacements have been done allows the algorithm to stop in time.
Following test method acts on a char array as that is easiest to setup:
private char[] rotate(char[] buf, int start) {
int len = buf.length;
int count = 0;
int offset = 0;
while (count < len) {
int index = offset;
char tmp = buf[index];
int index2 = (start + index) % len;
while (index2 != offset) {
buf[index] = buf[index2];
count++;
index = index2;
index2 = (start + index) % len;
}
buf[index] = tmp;
count++;
offset++;
}
return buf;
}
The following tests succeed:
public void testRotate() {
assertEquals("A", rotate("A", 0));
assertEquals("AB", rotate("AB", 0));
assertEquals("AB", rotate("BA", 1));
assertEquals("ABCD", rotate("DABC", 1));
assertEquals("ABCDE", rotate("DEABC", 2));
assertEquals("ABCDEF", rotate("DEFABC", 3));
assertEquals("ABCDEF1", rotate("DEF1ABC", 4));
assertEquals("ABCDEF12", rotate("DEF12ABC", 5));
assertEquals("ABCDEF123", rotate("DEF123ABC", 6));
}
private String rotate(String buf, int start) {
return new String(rotate(buf.toCharArray(), start));
}
Update:
The above algorithm rotates a full buffer, to optimise rotating buffers thats are not full you can pick out the quick wins and use a full rotation for what is left:
private char[] realign(char[] buf, int start, int items) {
int len = buf.length;
int offset = 0;
if (0 == start) {
// done
} else if (items <= len - start) {
// simply move to front
while (offset < items) {
buf[offset++] = buf[start++];
}
} else if (items * 2 <= len) {
// move lead out of the way first
int last = start;
int end = items - len + start;
while (0 < end) {
buf[--last] = buf[--end];
}
while (offset < items && start < len) {
buf[offset++] = buf[start++];
}
while (offset < items) {
buf[offset++] = buf[last++];
}
} else {
// use full rotate on the rest
buf = rotate(buf, start);
}
return buf;
}
This will take care of most of the situations, those where the buffer if more than half full and where it wraps over the end of the buffer are being rotated in full.
The following tests succeed:
public void testRealign() {
assertEquals("A", realign("A", 0, 1));
assertEquals("AB", realign("BA", 1, 2));
assertEquals("ABCD", realign("DABC", 1, 4));
assertEquals("ABCDE", realign("DEABC", 2, 5));
assertEquals("ABCDEF", realign("DEF123ABC", 6, 6));
assertEquals("0123456789", realign("4567890123", 6, 10));
assertEquals("ABC", realign("ABC", 0, 3));
assertEquals("ABC", realign("012ABC3", 3, 3));
assertEquals("ABC", realign("01234ABC", 5, 3));
assertEquals("ABCD", realign("D1234ABC", 5, 4));
assertEquals("ABCD", realign("CD1234AB", 6, 4));
assertEquals("ABCD", realign("BCD1234A", 7, 4));
}
private String realign(String buf, int start, int items) {
return (new String(realign(buf.toCharArray(), start, items))).substring(0, items);
}