new file: linear/circularBuffer.go

new file:   tests/circularBuffer_test.go
This commit is contained in:
Acid
2026-05-02 16:26:50 -04:00
parent f53776ede0
commit 1a93503112
2 changed files with 220 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
package linear
type baseTypes interface {
int | int8 | int16 | int32 | int64 |
uint | uint8 | uint16 | uint32 | uint64 |
float32 | float64 | string | bool
}
// CircularBuffer -> Circular Buffer struct only takes baseTypes
type CircularBuffer[T baseTypes] struct {
array [10]T
cHead int
cTail int
size int
}
// Add() -> adds values wrapping around to overwrite oldest
func (cb *CircularBuffer[T]) Add(val T) {
cb.array[cb.cTail] = val
cb.cTail = (cb.cTail + 1) % len(cb.array) // wrap around
if cb.size < len(cb.array) {
cb.size++
} else {
cb.cHead = (cb.cHead + 1) % len(cb.array) // overwrite oldest
}
}
// Data() -> returns the array in CircularBuffer
func (cb CircularBuffer[T]) Data() [10]T {
return cb.array
}
+188
View File
@@ -0,0 +1,188 @@
package tests
import (
"testing"
"datastructures/linear"
)
// --- helpers ---
func intBuffer(vals ...int) linear.CircularBuffer[int] {
var cb linear.CircularBuffer[int]
for _, v := range vals {
cb.Add(v)
}
return cb
}
// --- empty buffer ---
func TestCircularBuffer_EmptyData(t *testing.T) {
var cb linear.CircularBuffer[int]
data := cb.Data()
for i, v := range data {
if v != 0 {
t.Errorf("expected zero at index %d, got %d", i, v)
}
}
}
// --- basic add ---
func TestCircularBuffer_AddOne(t *testing.T) {
cb := intBuffer(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)
data := cb.Data()
for i := 0; i < 5; i++ {
if data[i] != i+1 {
t.Errorf("index %d: expected %d, got %d", i, i+1, data[i])
}
}
}
// --- fill to exact capacity (10) ---
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])
}
}
}
// --- 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])
}
}
}
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])
}
}
}
// --- 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])
}
}
}
// --- stress: 100 additions ---
func TestCircularBuffer_StressAdd(t *testing.T) {
var cb linear.CircularBuffer[int]
for i := 1; i <= 100; i++ {
cb.Add(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])
}
}
}
// --- string type ---
func TestCircularBuffer_StringType(t *testing.T) {
var cb linear.CircularBuffer[string]
cb.Add("hello")
cb.Add("world")
data := cb.Data()
if data[0] != "hello" {
t.Errorf("expected hello, got %s", data[0])
}
if data[1] != "world" {
t.Errorf("expected world, got %s", data[1])
}
}
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.Add(w)
}
// "k" overwrote slot 0 ("a")
if cb.Data()[0] != "k" {
t.Errorf("expected 'k' at slot 0, got '%s'", cb.Data()[0])
}
}
// --- float type ---
func TestCircularBuffer_FloatType(t *testing.T) {
var cb linear.CircularBuffer[float64]
cb.Add(3.14)
cb.Add(2.71)
data := cb.Data()
if data[0] != 3.14 {
t.Errorf("expected 3.14, got %f", data[0])
}
if data[1] != 2.71 {
t.Errorf("expected 2.71, got %f", data[1])
}
}
// --- idempotent Data() call ---
func TestCircularBuffer_DataDoesNotMutate(t *testing.T) {
cb := intBuffer(1, 2, 3)
first := cb.Data()
second := cb.Data()
if first != second {
t.Error("consecutive Data() calls returned different results")
}
}
// --- zero value after overflow stays zero for untouched slots ---
func TestCircularBuffer_UnusedSlotsAreZero(t *testing.T) {
cb := intBuffer(7) // only slot 0 written
data := cb.Data()
for i := 1; i < 10; i++ {
if data[i] != 0 {
t.Errorf("slot %d: expected 0, got %d", i, data[i])
}
}
}