mirror of
https://github.com/bloeys/nterm.git
synced 2025-12-29 06:28:20 +00:00
Starting ring buffer implementation
This commit is contained in:
95
ring/ring.go
Executable file
95
ring/ring.go
Executable file
@ -0,0 +1,95 @@
|
||||
package ring
|
||||
|
||||
import "golang.org/x/exp/constraints"
|
||||
|
||||
type Buffer[T any] struct {
|
||||
Data []T
|
||||
Start int64
|
||||
Len int64
|
||||
Cap int64
|
||||
}
|
||||
|
||||
func (b *Buffer[T]) Append(x ...T) {
|
||||
|
||||
inLen := int64(len(x))
|
||||
|
||||
for len(x) > 0 {
|
||||
|
||||
copied := copy(b.Data[b.Head():], x)
|
||||
x = x[copied:]
|
||||
|
||||
if b.Len == b.Cap {
|
||||
b.Start = (b.Start + int64(copied)) % (b.Cap)
|
||||
} else {
|
||||
b.Len = clamp(b.Len+inLen, 0, b.Cap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer[T]) Head() int64 {
|
||||
return (b.Start + b.Len) % b.Cap
|
||||
}
|
||||
|
||||
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
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer[T]) Delete(delStartIndex, elementsToDel uint64, x ...T) {
|
||||
|
||||
if b.Len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
copy(b.Data[uint64(b.Start)+delStartIndex:], b.Data[uint64(b.Start)+delStartIndex+elementsToDel:])
|
||||
|
||||
// p.cmdBufLen--
|
||||
}
|
||||
|
||||
func clamp[T constraints.Ordered](x, min, max T) T {
|
||||
|
||||
if x < min {
|
||||
return min
|
||||
}
|
||||
|
||||
if x > max {
|
||||
return max
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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
|
||||
}
|
||||
|
||||
v1 = b.Data[b.Start:b.Cap]
|
||||
v2 = b.Data[:b.Start+b.Len-b.Cap]
|
||||
return
|
||||
}
|
||||
|
||||
func NewBuffer[T any](capacity uint64) *Buffer[T] {
|
||||
|
||||
return &Buffer[T]{
|
||||
Data: make([]T, capacity),
|
||||
Start: 0,
|
||||
Len: 0,
|
||||
Cap: int64(capacity),
|
||||
}
|
||||
}
|
||||
80
ring/ring_test.go
Executable file
80
ring/ring_test.go
Executable file
@ -0,0 +1,80 @@
|
||||
package ring_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bloeys/nterm/ring"
|
||||
)
|
||||
|
||||
func TestRing(t *testing.T) {
|
||||
|
||||
// Basics
|
||||
b := ring.NewBuffer[rune](4)
|
||||
b.Append('a', 'b', 'c', 'd')
|
||||
CheckArr(t, []rune{'a', 'b', 'c', 'd'}, b.Data)
|
||||
|
||||
v1, v2 := b.Views()
|
||||
CheckArr(t, []rune{'a', 'b', 'c', 'd'}, v1)
|
||||
CheckArr(t, nil, v2)
|
||||
Check(t, 0, b.Start)
|
||||
Check(t, 4, b.Len)
|
||||
|
||||
b.Append('e', 'f')
|
||||
Check(t, 2, b.Start)
|
||||
CheckArr(t, []rune{'e', 'f', 'c', 'd'}, b.Data)
|
||||
|
||||
v1, v2 = b.Views()
|
||||
CheckArr(t, []rune{'c', 'd'}, v1)
|
||||
CheckArr(t, []rune{'e', 'f'}, v2)
|
||||
|
||||
b.Append('g')
|
||||
Check(t, 3, b.Start)
|
||||
|
||||
v1, v2 = b.Views()
|
||||
CheckArr(t, []rune{'e', 'f', 'g', 'd'}, b.Data)
|
||||
CheckArr(t, []rune{'d'}, v1)
|
||||
CheckArr(t, []rune{'e', 'f', 'g'}, v2)
|
||||
|
||||
// Input over 2x bigger than buffer
|
||||
b = ring.NewBuffer[rune](4)
|
||||
b.Append('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.Append(4, 5)
|
||||
CheckArr(t, []int{5, 2, 3, 4}, b2.Data)
|
||||
|
||||
b2.Append(6)
|
||||
CheckArr(t, []int{5, 6, 3, 4}, b2.Data)
|
||||
|
||||
b2.Append(7)
|
||||
CheckArr(t, []int{5, 6, 7, 4}, b2.Data)
|
||||
|
||||
b2.Append(8)
|
||||
CheckArr(t, []int{5, 6, 7, 8}, b2.Data)
|
||||
}
|
||||
|
||||
func Check[T comparable](t *testing.T, expected, got T) {
|
||||
if got != expected {
|
||||
t.Fatalf("Expected %v but got %v\n", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckArr[T comparable](t *testing.T, expected, got []T) {
|
||||
|
||||
if len(expected) != len(got) {
|
||||
t.Fatalf("Expected %v but got %v\n", expected, got)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(expected); i++ {
|
||||
|
||||
if expected[i] != got[i] {
|
||||
t.Fatalf("Expected %v but got %v\n", expected, got)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user