-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0821b43
commit b4e5ad6
Showing
5 changed files
with
290 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package frc.robot.utils; | ||
import java.util.NoSuchElementException; | ||
|
||
public class RingBuffer<T> { | ||
|
||
private Object[] backingArray; | ||
private int firstIdx = 0; // Index of the first elements | ||
private int nextIdx = 0; // Index of the slot into which we would insert an element on addLast | ||
// If nextIdx == (firstIdx - 1) % array_size, then insertion requires resizing the array. | ||
// If nextIdx == firstIdx, then the buffer is empty. | ||
|
||
public RingBuffer() { | ||
backingArray = new Object[2]; | ||
} | ||
|
||
synchronized private void doubleBackingArraySize() { | ||
int newSize = backingArray.length * 2; | ||
Object[] newArray = new Object[newSize]; | ||
int oldSize; | ||
|
||
if (nextIdx < firstIdx) { | ||
int numElementsToEndOfArray = backingArray.length - firstIdx; | ||
System.arraycopy(backingArray, firstIdx, newArray, 0, numElementsToEndOfArray); | ||
System.arraycopy(backingArray, 0, newArray, numElementsToEndOfArray, nextIdx); | ||
oldSize = numElementsToEndOfArray + nextIdx; | ||
} else { | ||
// This will happen if firstIdx == 0 | ||
System.arraycopy(backingArray, firstIdx, newArray, 0, nextIdx - firstIdx); | ||
oldSize = nextIdx - firstIdx; | ||
} | ||
|
||
backingArray = newArray; | ||
// Update our indices into that array. | ||
firstIdx = 0; | ||
nextIdx = oldSize; | ||
} | ||
|
||
/** | ||
* Returns the number of elements that the array backing this can hold. | ||
* This is NOT necessarily the number of elements presently in the buffer. | ||
* Useful for testing that the implementation works correctly, and for figuring out if an add{First, Last} will | ||
* cause a re-allocation. | ||
*/ | ||
public int capacity() { | ||
return backingArray.length - 1; | ||
} | ||
|
||
/** | ||
* The number of elements currently held in the buffer. | ||
*/ | ||
public int size() { | ||
if (firstIdx <= nextIdx) { | ||
return nextIdx - firstIdx; | ||
} else { | ||
return (backingArray.length - firstIdx + nextIdx); | ||
} | ||
} | ||
|
||
public void addLast(T x) { | ||
if ((nextIdx + 1) % backingArray.length == firstIdx) { | ||
doubleBackingArraySize(); | ||
} | ||
|
||
backingArray[nextIdx] = x; | ||
nextIdx +=1; | ||
nextIdx %= backingArray.length; | ||
} | ||
|
||
public void addFirst(T x) { | ||
if ((nextIdx + 1) % backingArray.length == firstIdx) { | ||
doubleBackingArraySize(); | ||
} | ||
|
||
firstIdx -= 1; | ||
// Note: in Java -1 % n == -1 | ||
if (firstIdx < 0) { | ||
firstIdx += backingArray.length; | ||
} | ||
backingArray[firstIdx] = x; | ||
} | ||
|
||
public void removeFirst() { | ||
if (size() == 0) { | ||
throw new NoSuchElementException(); | ||
} else { | ||
firstIdx += 1; | ||
firstIdx %= backingArray.length; | ||
} | ||
} | ||
|
||
public void removeLast() { | ||
if (size() == 0) { | ||
throw new NoSuchElementException(); | ||
} else { | ||
nextIdx -= 1; | ||
if (nextIdx < 0) { | ||
nextIdx += backingArray.length; | ||
} | ||
} | ||
} | ||
|
||
public T getFromFirst(int i) { | ||
if (i < 0 || i >= size()) { | ||
throw new NoSuchElementException(); | ||
} else { | ||
//noinspection unchecked | ||
return (T)backingArray[(firstIdx + i) % backingArray.length]; | ||
} | ||
} | ||
|
||
public T getFromLast(int i) { | ||
if (i < 0 || i >= size()) { | ||
throw new NoSuchElementException(); | ||
} else { | ||
int idx = nextIdx - 1 - i; | ||
if (idx < 0) { | ||
idx += backingArray.length; | ||
} | ||
//noinspection unchecked | ||
return (T)backingArray[idx]; | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import static org.junit.Assert.*; | ||
import org.junit.jupiter.api.Test; | ||
|
||
|
||
import frc.robot.utils.RingBuffer; | ||
|
||
import java.util.NoSuchElementException; | ||
|
||
public class RingBufferTests { | ||
|
||
@Test | ||
public void populateAndRandomAccess() { | ||
RingBuffer<Integer> buffer = new RingBuffer<>(); | ||
buffer.addLast(5); // [5] | ||
buffer.addLast(6); // [5, 6] | ||
buffer.addLast(7); // [5, 6, 7] | ||
|
||
Assert.assertEquals(5, (int)buffer.getFromFirst(0)); | ||
Assert.assertEquals(6, (int)buffer.getFromFirst(1)); | ||
Assert.assertEquals(7, (int)buffer.getFromFirst(2)); | ||
} | ||
|
||
@Test | ||
public void populateAndRandomAccessFromLast() { | ||
RingBuffer<Integer> buffer = new RingBuffer<>(); | ||
buffer.addLast(5); // [5]; | ||
buffer.addLast(6); // [5, 6] | ||
buffer.addLast(7); // [5, 6, 7] | ||
|
||
Assert.assertEquals(7, (int)buffer.getFromLast(0)); | ||
Assert.assertEquals(6, (int)buffer.getFromLast(1)); | ||
Assert.assertEquals(5, (int)buffer.getFromLast(2)); | ||
} | ||
|
||
@Test | ||
public void populateFromFirst() { | ||
RingBuffer<Integer> buffer = new RingBuffer<>(); | ||
buffer.addFirst(5); // [5] | ||
buffer.addFirst(6); // [6, 5] | ||
buffer.addFirst(7); // [7, 6, 5] | ||
|
||
Assert.assertEquals(5, (int)buffer.getFromLast(0)); | ||
Assert.assertEquals(6, (int)buffer.getFromLast(1)); | ||
Assert.assertEquals(7, (int)buffer.getFromLast(2)); | ||
|
||
Assert.assertEquals(7, (int)buffer.getFromFirst(0)); | ||
Assert.assertEquals(6, (int)buffer.getFromFirst(1)); | ||
Assert.assertEquals(5, (int)buffer.getFromFirst(2)); | ||
} | ||
|
||
@Test | ||
public void populateFromBothEnds() { | ||
RingBuffer<Integer> buffer = new RingBuffer<>(); | ||
buffer.addFirst(6); // [6] | ||
buffer.addFirst(5); // [5, 6] | ||
buffer.addLast(7); // [5, 6, 7] | ||
buffer.addLast(8); // [5, 6, 7, 8] | ||
|
||
Assert.assertEquals(8, (int)buffer.getFromLast(0)); | ||
Assert.assertEquals(7, (int)buffer.getFromLast(1)); | ||
Assert.assertEquals(6, (int)buffer.getFromLast(2)); | ||
Assert.assertEquals(5, (int)buffer.getFromLast(3)); | ||
|
||
Assert.assertEquals(5, (int)buffer.getFromFirst(0)); | ||
Assert.assertEquals(6, (int)buffer.getFromFirst(1)); | ||
Assert.assertEquals(7, (int)buffer.getFromFirst(2)); | ||
Assert.assertEquals(8, (int)buffer.getFromFirst(3)); | ||
} | ||
|
||
@Test | ||
public void outOfBoundsAccessThrows() { | ||
RingBuffer<Integer> buffer = new RingBuffer<>(); | ||
buffer.addLast(5); // [5] | ||
buffer.addLast(6); // [5, 6] | ||
buffer.addLast(7); // [5, 6, 7] | ||
|
||
Assert.assertThrows(NoSuchElementException.class, () -> buffer.getFromFirst(3)); | ||
Assert.assertThrows(NoSuchElementException.class, () -> buffer.getFromFirst(4)); | ||
Assert.assertThrows(NoSuchElementException.class, () -> buffer.getFromFirst(5)); | ||
Assert.assertThrows(NoSuchElementException.class, () -> buffer.getFromLast(3)); | ||
Assert.assertThrows(NoSuchElementException.class, () -> buffer.getFromLast(4)); | ||
Assert.assertThrows(NoSuchElementException.class, () -> buffer.getFromLast(5)); | ||
|
||
Assert.assertThrows(NoSuchElementException.class, () -> buffer.getFromFirst(-1)); | ||
Assert.assertThrows(NoSuchElementException.class, () -> buffer.getFromLast(-1)); | ||
} | ||
|
||
@Test | ||
public void removingWhenEmptyThrows() { | ||
RingBuffer<Integer> buffer = new RingBuffer<>(); | ||
buffer.addLast(5); // [5] | ||
buffer.addLast(6); // [5, 6] | ||
buffer.addLast(7); // [5, 6, 7] | ||
|
||
buffer.removeFirst(); | ||
buffer.removeFirst(); | ||
buffer.removeFirst(); | ||
|
||
Assert.assertThrows(NoSuchElementException.class, buffer::removeFirst); | ||
Assert.assertThrows(NoSuchElementException.class, buffer::removeLast); | ||
} | ||
|
||
@Test | ||
public void populateAndRemove() { | ||
RingBuffer<Integer> buffer = new RingBuffer<>(); | ||
buffer.addLast(5); // [5] | ||
buffer.addLast(6); // [5, 6] | ||
buffer.addLast(7); // [5, 6, 7] | ||
|
||
buffer.removeFirst(); // Should remove the 5 | ||
|
||
Assert.assertEquals(6, (int)buffer.getFromFirst(0)); | ||
Assert.assertEquals(7, (int)buffer.getFromFirst(1)); | ||
|
||
Assert.assertEquals(6, (int)buffer.getFromLast(1)); | ||
Assert.assertEquals(7, (int)buffer.getFromLast(0)); | ||
} | ||
|
||
@Test | ||
public void populateAndRemoveManyTimes() { | ||
RingBuffer<Integer> buffer = new RingBuffer<>(); | ||
|
||
// This should result in the buffer always containing between 3 and 5 elements. | ||
buffer.addLast(-1); | ||
buffer.addLast(-1); | ||
buffer.addLast(-1); | ||
for (int i=0; i < 100; i+=2) { | ||
buffer.addLast(i); | ||
buffer.addLast(i+1); | ||
buffer.removeFirst(); | ||
buffer.removeFirst(); | ||
} | ||
|
||
// Check that the buffer has only 3 elements | ||
Assert.assertEquals(3, buffer.size()); | ||
// Check that these elements are [97, 98, 99] | ||
Assert.assertEquals(97, (int)buffer.getFromFirst(0)); | ||
Assert.assertEquals(98, (int)buffer.getFromFirst(1)); | ||
Assert.assertEquals(99, (int)buffer.getFromFirst(2)); | ||
|
||
// Check that the buffer has not grown in capacity to some absurd size | ||
// Will use 8 since my implementation will probably grow by doubling the backing array size. | ||
Assert.assertTrue(buffer.capacity() <= 8); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import org.junit.jupiter.api.Test; | ||
|
||
public class TestTest { | ||
@Test | ||
void test(){ | ||
assert true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package frc.robot; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
public class TestTest { | ||
@Test | ||
public static void test(){ | ||
assert false; | ||
} | ||
} |