252 lines
5.6 KiB
Go
252 lines
5.6 KiB
Go
package chart
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
ringBufferMinimumGrow = 4
|
|
ringBufferShrinkThreshold = 32
|
|
ringBufferGrowFactor = 200
|
|
ringBufferDefaultCapacity = 4
|
|
)
|
|
|
|
var (
|
|
emptyArray = make([]interface{}, 0)
|
|
)
|
|
|
|
// NewRingBuffer creates a new, empty, RingBuffer.
|
|
func NewRingBuffer() *RingBuffer {
|
|
return &RingBuffer{
|
|
array: make([]interface{}, ringBufferDefaultCapacity),
|
|
head: 0,
|
|
tail: 0,
|
|
size: 0,
|
|
}
|
|
}
|
|
|
|
// NewRingBufferWithCapacity creates a new RingBuffer pre-allocated with the given capacity.
|
|
func NewRingBufferWithCapacity(capacity int) *RingBuffer {
|
|
return &RingBuffer{
|
|
array: make([]interface{}, capacity),
|
|
head: 0,
|
|
tail: 0,
|
|
size: 0,
|
|
}
|
|
}
|
|
|
|
// NewRingBufferFromSlice createsa ring buffer out of a slice.
|
|
func NewRingBufferFromSlice(values []interface{}) *RingBuffer {
|
|
return &RingBuffer{
|
|
array: values,
|
|
head: 0,
|
|
tail: len(values) - 1,
|
|
size: len(values),
|
|
}
|
|
}
|
|
|
|
// RingBuffer is a fifo buffer that is backed by a pre-allocated array, instead of allocating
|
|
// a whole new node object for each element (which saves GC churn).
|
|
// Enqueue can be O(n), Dequeue can be O(1).
|
|
type RingBuffer struct {
|
|
array []interface{}
|
|
head int
|
|
tail int
|
|
size int
|
|
}
|
|
|
|
// Len returns the length of the ring buffer (as it is currently populated).
|
|
// Actual memory footprint may be different.
|
|
func (rb *RingBuffer) Len() int {
|
|
return rb.size
|
|
}
|
|
|
|
// TotalLen returns the total size of the ring bufffer, including empty elements.
|
|
func (rb *RingBuffer) TotalLen() int {
|
|
return len(rb.array)
|
|
}
|
|
|
|
// Clear removes all objects from the RingBuffer.
|
|
func (rb *RingBuffer) Clear() {
|
|
if rb.head < rb.tail {
|
|
arrayClear(rb.array, rb.head, rb.size)
|
|
} else {
|
|
arrayClear(rb.array, rb.head, len(rb.array)-rb.head)
|
|
arrayClear(rb.array, 0, rb.tail)
|
|
}
|
|
|
|
rb.head = 0
|
|
rb.tail = 0
|
|
rb.size = 0
|
|
}
|
|
|
|
// Enqueue adds an element to the "back" of the RingBuffer.
|
|
func (rb *RingBuffer) Enqueue(object interface{}) {
|
|
if rb.size == len(rb.array) {
|
|
newCapacity := int(len(rb.array) * int(ringBufferGrowFactor/100))
|
|
if newCapacity < (len(rb.array) + ringBufferMinimumGrow) {
|
|
newCapacity = len(rb.array) + ringBufferMinimumGrow
|
|
}
|
|
rb.setCapacity(newCapacity)
|
|
}
|
|
|
|
rb.array[rb.tail] = object
|
|
rb.tail = (rb.tail + 1) % len(rb.array)
|
|
rb.size++
|
|
}
|
|
|
|
// Dequeue removes the first element from the RingBuffer.
|
|
func (rb *RingBuffer) Dequeue() interface{} {
|
|
if rb.size == 0 {
|
|
return nil
|
|
}
|
|
|
|
removed := rb.array[rb.head]
|
|
rb.head = (rb.head + 1) % len(rb.array)
|
|
rb.size--
|
|
return removed
|
|
}
|
|
|
|
// Peek returns but does not remove the first element.
|
|
func (rb *RingBuffer) Peek() interface{} {
|
|
if rb.size == 0 {
|
|
return nil
|
|
}
|
|
return rb.array[rb.head]
|
|
}
|
|
|
|
// PeekBack returns but does not remove the last element.
|
|
func (rb *RingBuffer) PeekBack() interface{} {
|
|
if rb.size == 0 {
|
|
return nil
|
|
}
|
|
if rb.tail == 0 {
|
|
return rb.array[len(rb.array)-1]
|
|
}
|
|
return rb.array[rb.tail-1]
|
|
}
|
|
|
|
func (rb *RingBuffer) setCapacity(capacity int) {
|
|
newArray := make([]interface{}, capacity)
|
|
if rb.size > 0 {
|
|
if rb.head < rb.tail {
|
|
arrayCopy(rb.array, rb.head, newArray, 0, rb.size)
|
|
} else {
|
|
arrayCopy(rb.array, rb.head, newArray, 0, len(rb.array)-rb.head)
|
|
arrayCopy(rb.array, 0, newArray, len(rb.array)-rb.head, rb.tail)
|
|
}
|
|
}
|
|
rb.array = newArray
|
|
rb.head = 0
|
|
if rb.size == capacity {
|
|
rb.tail = 0
|
|
} else {
|
|
rb.tail = rb.size
|
|
}
|
|
}
|
|
|
|
// TrimExcess resizes the buffer to better fit the contents.
|
|
func (rb *RingBuffer) TrimExcess() {
|
|
threshold := float64(len(rb.array)) * 0.9
|
|
if rb.size < int(threshold) {
|
|
rb.setCapacity(rb.size)
|
|
}
|
|
}
|
|
|
|
// AsSlice returns the ring buffer, in order, as a slice.
|
|
func (rb *RingBuffer) AsSlice() []interface{} {
|
|
newArray := make([]interface{}, rb.size)
|
|
|
|
if rb.size == 0 {
|
|
return newArray
|
|
}
|
|
|
|
if rb.head < rb.tail {
|
|
arrayCopy(rb.array, rb.head, newArray, 0, rb.size)
|
|
} else {
|
|
arrayCopy(rb.array, rb.head, newArray, 0, len(rb.array)-rb.head)
|
|
arrayCopy(rb.array, 0, newArray, len(rb.array)-rb.head, rb.tail)
|
|
}
|
|
|
|
return newArray
|
|
}
|
|
|
|
// Each calls the consumer for each element in the buffer.
|
|
func (rb *RingBuffer) Each(consumer func(value interface{})) {
|
|
if rb.size == 0 {
|
|
return
|
|
}
|
|
|
|
if rb.head < rb.tail {
|
|
for cursor := rb.head; cursor < rb.tail; cursor++ {
|
|
consumer(rb.array[cursor])
|
|
}
|
|
} else {
|
|
for cursor := rb.head; cursor < len(rb.array); cursor++ {
|
|
consumer(rb.array[cursor])
|
|
}
|
|
for cursor := 0; cursor < rb.tail; cursor++ {
|
|
consumer(rb.array[cursor])
|
|
}
|
|
}
|
|
}
|
|
|
|
func (rb *RingBuffer) String() string {
|
|
var values []string
|
|
for _, elem := range rb.AsSlice() {
|
|
values = append(values, fmt.Sprintf("%v", elem))
|
|
}
|
|
return strings.Join(values, " <= ")
|
|
}
|
|
|
|
// Average returns the float average of the values in the buffer.
|
|
func (rb *RingBuffer) Average() float64 {
|
|
var accum float64
|
|
rb.Each(func(v interface{}) {
|
|
if typed, isTyped := v.(float64); isTyped {
|
|
accum += typed
|
|
}
|
|
})
|
|
return accum / float64(rb.Len())
|
|
}
|
|
|
|
// Variance computes the variance of the buffer.
|
|
func (rb *RingBuffer) Variance() float64 {
|
|
if rb.Len() == 0 {
|
|
return 0
|
|
}
|
|
|
|
var variance float64
|
|
m := rb.Average()
|
|
|
|
rb.Each(func(v interface{}) {
|
|
if n, isTyped := v.(float64); isTyped {
|
|
variance += (float64(n) - m) * (float64(n) - m)
|
|
}
|
|
})
|
|
|
|
return variance / float64(rb.Len())
|
|
}
|
|
|
|
// StdDev returns the standard deviation.
|
|
func (rb *RingBuffer) StdDev() float64 {
|
|
return math.Pow(rb.Variance(), 0.5)
|
|
}
|
|
|
|
func arrayClear(source []interface{}, index, length int) {
|
|
for x := 0; x < length; x++ {
|
|
absoluteIndex := x + index
|
|
source[absoluteIndex] = nil
|
|
}
|
|
}
|
|
|
|
func arrayCopy(source []interface{}, sourceIndex int, destination []interface{}, destinationIndex, length int) {
|
|
for x := 0; x < length; x++ {
|
|
from := sourceIndex + x
|
|
to := destinationIndex + x
|
|
|
|
destination[to] = source[from]
|
|
}
|
|
}
|