circularBuffer can take a fixed size
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user