From 4992210bdbedfd0e5aed93e1af8526e2cd3a38e3 Mon Sep 17 00:00:00 2001 From: bloeys Date: Fri, 15 Jul 2022 18:48:45 +0400 Subject: [PATCH] Finish ring buffer --- ring/ring.go | 55 +++++++++++++++++++++++++++++++++-------------- ring/ring_test.go | 50 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 80 insertions(+), 25 deletions(-) diff --git a/ring/ring.go b/ring/ring.go index 41390ec..793aa46 100755 --- a/ring/ring.go +++ b/ring/ring.go @@ -1,6 +1,8 @@ package ring -import "golang.org/x/exp/constraints" +import ( + "golang.org/x/exp/constraints" +) type Buffer[T any] struct { Data []T @@ -9,13 +11,13 @@ type Buffer[T any] struct { Cap int64 } -func (b *Buffer[T]) Append(x ...T) { +func (b *Buffer[T]) Write(x ...T) { inLen := int64(len(x)) for len(x) > 0 { - copied := copy(b.Data[b.Head():], x) + copied := copy(b.Data[b.WriteHead():], x) x = x[copied:] if b.Len == b.Cap { @@ -26,33 +28,54 @@ func (b *Buffer[T]) Append(x ...T) { } } -func (b *Buffer[T]) Head() int64 { +//WriteHead is the absolute position within the buffer where new writes will happen +func (b *Buffer[T]) WriteHead() int64 { return (b.Start + b.Len) % b.Cap } +//Clear resets Len and Start to zero but elements within Data aren't touched. +//This gives you empty Views and new writes/inserts will overwrite old data +func (b *Buffer[T]) Clear() { + b.Len = 0 + b.Start = 0 +} + +func (b *Buffer[T]) IsFull() bool { + return b.Len == b.Cap +} + +//Insert inserts the given elements starting at the provided index. +// +//Note: Insert is a no-op if the buffer is full or doesn't have enough place for the elements func (b *Buffer[T]) Insert(index uint64, x ...T) { delta := int64(len(x)) newLen := b.Len + delta - if newLen <= b.Cap { - - copy(b.Data[b.Start+int64(index)+delta:], b.Data[index:]) - copy(b.Data[index:], x) - - b.Len = newLen + if newLen > b.Cap { return } + + copy(b.Data[b.Start+int64(index)+delta:], b.Data[index:]) + copy(b.Data[index:], x) + b.Len = newLen } -func (b *Buffer[T]) Delete(delStartIndex, elementsToDel uint64, x ...T) { +//DeleteN removes 'n' elements starting at the provided index. +// +//Note DeleteN is a no-op if Len==0 or if buffer is full with start>0 +func (b *Buffer[T]) DeleteN(delStartIndex, n uint64) { if b.Len == 0 { return } - copy(b.Data[uint64(b.Start)+delStartIndex:], b.Data[uint64(b.Start)+delStartIndex+elementsToDel:]) + if b.Len == b.Cap && b.Start > 0 { + return + } - // p.cmdBufLen-- + relStartIndex := b.Start + int64(delStartIndex) + copy(b.Data[relStartIndex:], b.Data[relStartIndex+int64(n):]) + b.Len = clamp(b.Len-int64(n), 0, b.Cap) } func clamp[T constraints.Ordered](x, min, max T) T { @@ -69,17 +92,17 @@ func clamp[T constraints.Ordered](x, min, max T) T { } // Views returns two slices that have 'Len' elements in total between them. -// The first slice is from Start till min(Start+Len, Cap). If Start+Len<=Cap then the first slice contains all the data and the second is nil. +// The first slice is from Start till min(Start+Len, Cap). If Start+Len<=Cap then the first slice contains all the data and the second is empty. // If Start+Len>Cap then the first slice contains the data from Start till Cap, and the second slice contains data from Zero till Start+Len-Cap (basically the remaining elements to reach Len in total) // // This function does NOT copy. Any changes on the returned slices will reflect on the buffer Data func (b *Buffer[T]) Views() (v1, v2 []T) { if b.Start+b.Len <= b.Cap { - return b.Data[b.Start : b.Start+b.Len], nil + return b.Data[b.Start : b.Start+b.Len], []T{} } - v1 = b.Data[b.Start:b.Cap] + v1 = b.Data[b.Start:] v2 = b.Data[:b.Start+b.Len-b.Cap] return } diff --git a/ring/ring_test.go b/ring/ring_test.go index 755c1c8..aec305e 100755 --- a/ring/ring_test.go +++ b/ring/ring_test.go @@ -10,7 +10,7 @@ func TestRing(t *testing.T) { // Basics b := ring.NewBuffer[rune](4) - b.Append('a', 'b', 'c', 'd') + b.Write('a', 'b', 'c', 'd') CheckArr(t, []rune{'a', 'b', 'c', 'd'}, b.Data) v1, v2 := b.Views() @@ -19,7 +19,7 @@ func TestRing(t *testing.T) { Check(t, 0, b.Start) Check(t, 4, b.Len) - b.Append('e', 'f') + b.Write('e', 'f') Check(t, 2, b.Start) CheckArr(t, []rune{'e', 'f', 'c', 'd'}, b.Data) @@ -27,7 +27,7 @@ func TestRing(t *testing.T) { CheckArr(t, []rune{'c', 'd'}, v1) CheckArr(t, []rune{'e', 'f'}, v2) - b.Append('g') + b.Write('g') Check(t, 3, b.Start) v1, v2 = b.Views() @@ -35,26 +35,58 @@ func TestRing(t *testing.T) { CheckArr(t, []rune{'d'}, v1) CheckArr(t, []rune{'e', 'f', 'g'}, v2) + b = ring.NewBuffer[rune](4) + b.Write('a', 'b', 'c', 'd', 'e') + Check(t, 1, b.Start) + // Input over 2x bigger than buffer b = ring.NewBuffer[rune](4) - b.Append('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i') + b.Write('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i') CheckArr(t, []rune{'i', 'f', 'g', 'h'}, b.Data) // Input starting in the middle and having to loop back b2 := ring.NewBuffer[int](4) - b2.Append(1, 2, 3) + b2.Write(1, 2, 3) - b2.Append(4, 5) + b2.Write(4, 5) CheckArr(t, []int{5, 2, 3, 4}, b2.Data) - b2.Append(6) + b2.Write(6) CheckArr(t, []int{5, 6, 3, 4}, b2.Data) - b2.Append(7) + b2.Write(7) CheckArr(t, []int{5, 6, 7, 4}, b2.Data) - b2.Append(8) + b2.Write(8) CheckArr(t, []int{5, 6, 7, 8}, b2.Data) + + // Insert + b2 = ring.NewBuffer[int](4) + b2.Write(1, 2) + + b2.Insert(0, 3) + CheckArr(t, []int{3, 1, 2, 0}, b2.Data) + + b2.Insert(3, 4) + CheckArr(t, []int{3, 1, 2, 4}, b2.Data) + + b2.Insert(2, 5, 6) + CheckArr(t, []int{3, 1, 2, 4}, b2.Data) + + // Delete + b2 = ring.NewBuffer[int](4) + b2.Write(1, 2, 3, 4) + + b2.DeleteN(0, 4) + Check(t, 0, b2.Start) + Check(t, 0, b2.Len) + CheckArr(t, []int{1, 2, 3, 4}, b2.Data) + + b2.Write(5, 6, 7, 8) + Check(t, 4, b2.Len) + b2.DeleteN(2, 1) + Check(t, 3, b2.Len) + CheckArr(t, []int{5, 6, 8, 8}, b2.Data) } func Check[T comparable](t *testing.T, expected, got T) {