circularBuffer can take a fixed size

This commit is contained in:
Acid
2026-06-20 23:50:35 -04:00
parent a25babd3b1
commit 399f01c13d
3 changed files with 76 additions and 90 deletions
+7 -5
View File
@@ -10,16 +10,18 @@ type BaseTypes interface {
// CircularBuffer -> Circular Buffer struct only takes baseTypes
// .Current is a pointer to tail
type CircularBuffer[T BaseTypes] struct {
array [10]T
array []T
cHead int
cTail int
size int
Current *T
}
// NewCircularBuffer[type]() -> Creates a circular buffer (size of 10)
func NewCircularBuffer[T BaseTypes]() *CircularBuffer[T] {
return &CircularBuffer[T]{}
// NewCircularBuffer[type](size) -> Creates a circular buffer of given size
func NewCircularBuffer[T BaseTypes](size int) *CircularBuffer[T] {
return &CircularBuffer[T]{
array: make([]T, size),
}
}
// Push() -> adds values wrapping around to overwrite oldest
@@ -36,6 +38,6 @@ func (cb *CircularBuffer[T]) Push(val T) {
}
// Data() -> returns the entire array in CircularBuffer
func (cb CircularBuffer[T]) Data() [10]T {
func (cb CircularBuffer[T]) Data() []T {
return cb.array
}
+12 -19
View File
@@ -6,17 +6,10 @@ import (
"datastructures/linear"
)
// --- Current on an untouched buffer ---
func TestCircularBuffer_CurrentNilWhenEmpty(t *testing.T) {
var cb linear.CircularBuffer[int]
if cb.Current != nil {
t.Errorf("expected Current to be nil on empty buffer, got %v", *cb.Current)
}
}
// --- Current on a freshly constructed buffer ---
func TestCircularBuffer_CurrentNilFromConstructor(t *testing.T) {
cb := linear.NewCircularBuffer[int]()
cb := linear.NewCircularBuffer[int](10)
if cb.Current != nil {
t.Errorf("expected Current to be nil from NewCircularBuffer, got %v", *cb.Current)
}
@@ -25,7 +18,7 @@ func TestCircularBuffer_CurrentNilFromConstructor(t *testing.T) {
// --- Current points at the most recently pushed value ---
func TestCircularBuffer_CurrentAfterOnePush(t *testing.T) {
var cb linear.CircularBuffer[int]
cb := linear.NewCircularBuffer[int](4)
cb.Push(42)
if cb.Current == nil {
t.Fatal("expected Current to be set after Push, got nil")
@@ -36,7 +29,7 @@ func TestCircularBuffer_CurrentAfterOnePush(t *testing.T) {
}
func TestCircularBuffer_CurrentTracksLatest(t *testing.T) {
var cb linear.CircularBuffer[int]
cb := linear.NewCircularBuffer[int](10)
for _, v := range []int{1, 2, 3, 4, 5} {
cb.Push(v)
if *cb.Current != v {
@@ -48,11 +41,11 @@ func TestCircularBuffer_CurrentTracksLatest(t *testing.T) {
// --- Current aliases the live backing slot ---
func TestCircularBuffer_CurrentAliasesWrittenSlot(t *testing.T) {
var cb linear.CircularBuffer[int]
cb := linear.NewCircularBuffer[int](10)
cb.Push(10)
cb.Push(20)
cb.Push(30)
// Last write landed in slot 2; Data() is a snapshot of the backing array.
// Last write landed in slot 2.
if got := cb.Data()[2]; *cb.Current != got {
t.Errorf("*Current (%d) should match Data()[2] (%d)", *cb.Current, got)
}
@@ -61,13 +54,13 @@ func TestCircularBuffer_CurrentAliasesWrittenSlot(t *testing.T) {
// --- Current follows the write head through a wrap-around ---
func TestCircularBuffer_CurrentAfterOverflow(t *testing.T) {
var cb linear.CircularBuffer[int]
for i := 1; i <= 11; i++ {
cb := linear.NewCircularBuffer[int](5)
for i := 1; i <= 6; i++ {
cb.Push(i)
}
// The 11th value (11) overwrote slot 0 and is the most recent write.
if *cb.Current != 11 {
t.Errorf("expected *Current == 11 after overflow, got %d", *cb.Current)
// The 6th value (6) overwrote slot 0 and is the most recent write.
if *cb.Current != 6 {
t.Errorf("expected *Current == 6 after overflow, got %d", *cb.Current)
}
if got := cb.Data()[0]; *cb.Current != got {
t.Errorf("*Current (%d) should match overwritten slot 0 (%d)", *cb.Current, got)
@@ -77,7 +70,7 @@ func TestCircularBuffer_CurrentAfterOverflow(t *testing.T) {
// --- Current works for non-int types ---
func TestCircularBuffer_CurrentStringType(t *testing.T) {
var cb linear.CircularBuffer[string]
cb := linear.NewCircularBuffer[string](4)
cb.Push("hello")
cb.Push("world")
if cb.Current == nil || *cb.Current != "world" {
+57 -66
View File
@@ -1,6 +1,7 @@
package tests
import (
"slices"
"testing"
"datastructures/linear"
@@ -8,19 +9,22 @@ import (
// --- helpers ---
func intBuffer(vals ...int) linear.CircularBuffer[int] {
var cb linear.CircularBuffer[int]
func intBuffer(capacity int, vals ...int) *linear.CircularBuffer[int] {
cb := linear.NewCircularBuffer[int](capacity)
for _, v := range vals {
cb.Push(v)
}
return cb
}
// --- empty buffer ---
// --- construction ---
func TestCircularBuffer_EmptyData(t *testing.T) {
var cb linear.CircularBuffer[int]
cb := linear.NewCircularBuffer[int](10)
data := cb.Data()
if len(data) != 10 {
t.Fatalf("expected backing slice of length 10, got %d", len(data))
}
for i, v := range data {
if v != 0 {
t.Errorf("expected zero at index %d, got %d", i, v)
@@ -28,17 +32,26 @@ func TestCircularBuffer_EmptyData(t *testing.T) {
}
}
func TestCircularBuffer_RespectsCapacity(t *testing.T) {
for _, capacity := range []int{1, 3, 5, 16} {
cb := linear.NewCircularBuffer[int](capacity)
if got := len(cb.Data()); got != capacity {
t.Errorf("capacity %d: expected backing slice length %d, got %d", capacity, capacity, got)
}
}
}
// --- basic add ---
func TestCircularBuffer_AddOne(t *testing.T) {
cb := intBuffer(42)
cb := intBuffer(4, 42)
if cb.Data()[0] != 42 {
t.Errorf("expected 42 at index 0, got %d", cb.Data()[0])
}
}
func TestCircularBuffer_AddMultiple(t *testing.T) {
cb := intBuffer(1, 2, 3, 4, 5)
cb := intBuffer(5, 1, 2, 3, 4, 5)
data := cb.Data()
for i := 0; i < 5; i++ {
if data[i] != i+1 {
@@ -47,85 +60,64 @@ func TestCircularBuffer_AddMultiple(t *testing.T) {
}
}
// --- fill to exact capacity (10) ---
// --- fill to exact capacity ---
func TestCircularBuffer_FillExact(t *testing.T) {
cb := intBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
data := cb.Data()
for i := 0; i < 10; i++ {
if data[i] != i+1 {
t.Errorf("index %d: expected %d, got %d", i, i+1, data[i])
}
cb := intBuffer(5, 1, 2, 3, 4, 5)
want := []int{1, 2, 3, 4, 5}
if got := cb.Data(); !slices.Equal(got, want) {
t.Errorf("expected %v, got %v", want, got)
}
}
// --- overflow: oldest value must be overwritten ---
func TestCircularBuffer_OverflowByOne(t *testing.T) {
// fill with 1..10 then add 11; slot 0 gets overwritten
cb := intBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
data := cb.Data()
if data[0] != 11 {
t.Errorf("expected slot 0 to be overwritten with 11, got %d", data[0])
}
// slots 1-9 should still hold 2-10
for i := 1; i < 10; i++ {
if data[i] != i+1 {
t.Errorf("slot %d: expected %d, got %d", i, i+1, data[i])
}
// fill 1..5 into a cap-5 buffer, then add 6; slot 0 gets overwritten.
cb := intBuffer(5, 1, 2, 3, 4, 5, 6)
want := []int{6, 2, 3, 4, 5}
if got := cb.Data(); !slices.Equal(got, want) {
t.Errorf("expected %v, got %v", want, got)
}
}
func TestCircularBuffer_OverflowBy5(t *testing.T) {
// add 15 values: last 10 should be 6..15
cb := intBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
data := cb.Data()
// slots 0-4 are overwritten with 11-15
expected := [10]int{11, 12, 13, 14, 15, 6, 7, 8, 9, 10}
for i, want := range expected {
if data[i] != want {
t.Errorf("slot %d: expected %d, got %d", i, want, data[i])
}
func TestCircularBuffer_OverflowBy3(t *testing.T) {
cb := intBuffer(5, 1, 2, 3, 4, 5, 6, 7, 8)
want := []int{6, 7, 8, 4, 5}
if got := cb.Data(); !slices.Equal(got, want) {
t.Errorf("expected %v, got %v", want, got)
}
}
// --- double wrap: add exactly 2x capacity ---
func TestCircularBuffer_DoubleWrap(t *testing.T) {
// add 20 values; all original slots fully replaced by 11-20
cb := intBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
data := cb.Data()
expected := [10]int{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
for i, want := range expected {
if data[i] != want {
t.Errorf("slot %d: expected %d, got %d", i, want, data[i])
}
// add 10 values to a cap-5 buffer; all original slots replaced by 6..10.
cb := intBuffer(5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
want := []int{6, 7, 8, 9, 10}
if got := cb.Data(); !slices.Equal(got, want) {
t.Errorf("expected %v, got %v", want, got)
}
}
// --- stress: 100 additions ---
// --- stress: 100 additions into a cap-10 buffer ---
func TestCircularBuffer_StressAdd(t *testing.T) {
var cb linear.CircularBuffer[int]
cb := linear.NewCircularBuffer[int](10)
for i := 1; i <= 100; i++ {
cb.Push(i)
}
// last 10 values added were 91-100
// after 100 adds: tail wraps, slots should hold 91-100
data := cb.Data()
// slot index = (i-1) % 10, value = i for i in 91..100
expected := [10]int{91, 92, 93, 94, 95, 96, 97, 98, 99, 100}
for i, want := range expected {
if data[i] != want {
t.Errorf("slot %d: expected %d, got %d", i, want, data[i])
}
// last 10 values added were 91..100, landing in slots 0..9 in order.
want := []int{91, 92, 93, 94, 95, 96, 97, 98, 99, 100}
if got := cb.Data(); !slices.Equal(got, want) {
t.Errorf("expected %v, got %v", want, got)
}
}
// --- string type ---
func TestCircularBuffer_StringType(t *testing.T) {
var cb linear.CircularBuffer[string]
cb := linear.NewCircularBuffer[string](4)
cb.Push("hello")
cb.Push("world")
data := cb.Data()
@@ -138,21 +130,20 @@ func TestCircularBuffer_StringType(t *testing.T) {
}
func TestCircularBuffer_StringOverflow(t *testing.T) {
var cb linear.CircularBuffer[string]
words := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"}
for _, w := range words {
cb := linear.NewCircularBuffer[string](5)
for _, w := range []string{"a", "b", "c", "d", "e", "f"} {
cb.Push(w)
}
// "k" overwrote slot 0 ("a")
if cb.Data()[0] != "k" {
t.Errorf("expected 'k' at slot 0, got '%s'", cb.Data()[0])
// "f" overwrote slot 0 ("a").
if cb.Data()[0] != "f" {
t.Errorf("expected 'f' at slot 0, got '%s'", cb.Data()[0])
}
}
// --- float type ---
func TestCircularBuffer_FloatType(t *testing.T) {
var cb linear.CircularBuffer[float64]
cb := linear.NewCircularBuffer[float64](4)
cb.Push(3.14)
cb.Push(2.71)
data := cb.Data()
@@ -167,18 +158,18 @@ func TestCircularBuffer_FloatType(t *testing.T) {
// --- idempotent Data() call ---
func TestCircularBuffer_DataDoesNotMutate(t *testing.T) {
cb := intBuffer(1, 2, 3)
cb := intBuffer(5, 1, 2, 3)
first := cb.Data()
second := cb.Data()
if first != second {
t.Error("consecutive Data() calls returned different results")
if !slices.Equal(first, second) {
t.Errorf("consecutive Data() calls differ: %v vs %v", first, second)
}
}
// --- zero value after overflow stays zero for untouched slots ---
// --- untouched slots stay zero ---
func TestCircularBuffer_UnusedSlotsAreZero(t *testing.T) {
cb := intBuffer(7) // only slot 0 written
cb := intBuffer(10, 7) // only slot 0 written
data := cb.Data()
for i := 1; i < 10; i++ {
if data[i] != 0 {