sequence => seq
This commit is contained in:
parent
182c4eeed2
commit
9ca1b0466a
28 changed files with 205 additions and 141 deletions
19
seq/array.go
Normal file
19
seq/array.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package seq
|
||||
|
||||
// NewArray creates a new array.
|
||||
func NewArray(values ...float64) Array {
|
||||
return Array(values)
|
||||
}
|
||||
|
||||
// Array is a wrapper for an array of floats that implements `ValuesProvider`.
|
||||
type Array []float64
|
||||
|
||||
// Len returns the value provider length.
|
||||
func (a Array) Len() int {
|
||||
return len(a)
|
||||
}
|
||||
|
||||
// GetValue returns the value at a given index.
|
||||
func (a Array) GetValue(index int) float64 {
|
||||
return a[index]
|
||||
}
|
||||
226
seq/buffer.go
Normal file
226
seq/buffer.go
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
package seq
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
util "github.com/wcharczuk/go-chart/util"
|
||||
)
|
||||
|
||||
const (
|
||||
bufferMinimumGrow = 4
|
||||
bufferShrinkThreshold = 32
|
||||
bufferGrowFactor = 200
|
||||
bufferDefaultCapacity = 4
|
||||
)
|
||||
|
||||
var (
|
||||
emptyArray = make([]float64, 0)
|
||||
)
|
||||
|
||||
// NewBuffer creates a new value buffer with an optional set of values.
|
||||
func NewBuffer(values ...float64) *Buffer {
|
||||
var tail int
|
||||
array := make([]float64, util.Math.MaxInt(len(values), bufferDefaultCapacity))
|
||||
if len(values) > 0 {
|
||||
copy(array, values)
|
||||
tail = len(values)
|
||||
}
|
||||
return &Buffer{
|
||||
array: array,
|
||||
head: 0,
|
||||
tail: tail,
|
||||
size: len(values),
|
||||
}
|
||||
}
|
||||
|
||||
// NewBufferWithCapacity creates a new ValueBuffer pre-allocated with the given capacity.
|
||||
func NewBufferWithCapacity(capacity int) *Buffer {
|
||||
return &Buffer{
|
||||
array: make([]float64, capacity),
|
||||
head: 0,
|
||||
tail: 0,
|
||||
size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer is a fifo datastructure that is backed by a pre-allocated array.
|
||||
// Instead of allocating a whole new node object for each element, array elements are re-used (which saves GC churn).
|
||||
// Enqueue can be O(n), Dequeue is generally O(1).
|
||||
// Buffer implements `seq.Provider`
|
||||
type Buffer struct {
|
||||
array []float64
|
||||
head int
|
||||
tail int
|
||||
size int
|
||||
}
|
||||
|
||||
// Len returns the length of the Buffer (as it is currently populated).
|
||||
// Actual memory footprint may be different.
|
||||
func (b *Buffer) Len() int {
|
||||
return b.size
|
||||
}
|
||||
|
||||
// GetValue implements seq provider.
|
||||
func (b *Buffer) GetValue(index int) float64 {
|
||||
effectiveIndex := (b.head + index) % len(b.array)
|
||||
return b.array[effectiveIndex]
|
||||
}
|
||||
|
||||
// Capacity returns the total size of the Buffer, including empty elements.
|
||||
func (b *Buffer) Capacity() int {
|
||||
return len(b.array)
|
||||
}
|
||||
|
||||
// SetCapacity sets the capacity of the Buffer.
|
||||
func (b *Buffer) SetCapacity(capacity int) {
|
||||
newArray := make([]float64, capacity)
|
||||
if b.size > 0 {
|
||||
if b.head < b.tail {
|
||||
arrayCopy(b.array, b.head, newArray, 0, b.size)
|
||||
} else {
|
||||
arrayCopy(b.array, b.head, newArray, 0, len(b.array)-b.head)
|
||||
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
|
||||
}
|
||||
}
|
||||
b.array = newArray
|
||||
b.head = 0
|
||||
if b.size == capacity {
|
||||
b.tail = 0
|
||||
} else {
|
||||
b.tail = b.size
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all objects from the Buffer.
|
||||
func (b *Buffer) Clear() {
|
||||
b.array = make([]float64, bufferDefaultCapacity)
|
||||
b.head = 0
|
||||
b.tail = 0
|
||||
b.size = 0
|
||||
}
|
||||
|
||||
// Enqueue adds an element to the "back" of the Buffer.
|
||||
func (b *Buffer) Enqueue(value float64) {
|
||||
if b.size == len(b.array) {
|
||||
newCapacity := int(len(b.array) * int(bufferGrowFactor/100))
|
||||
if newCapacity < (len(b.array) + bufferMinimumGrow) {
|
||||
newCapacity = len(b.array) + bufferMinimumGrow
|
||||
}
|
||||
b.SetCapacity(newCapacity)
|
||||
}
|
||||
|
||||
b.array[b.tail] = value
|
||||
b.tail = (b.tail + 1) % len(b.array)
|
||||
b.size++
|
||||
}
|
||||
|
||||
// Dequeue removes the first element from the RingBuffer.
|
||||
func (b *Buffer) Dequeue() float64 {
|
||||
if b.size == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
removed := b.array[b.head]
|
||||
b.head = (b.head + 1) % len(b.array)
|
||||
b.size--
|
||||
return removed
|
||||
}
|
||||
|
||||
// Peek returns but does not remove the first element.
|
||||
func (b *Buffer) Peek() float64 {
|
||||
if b.size == 0 {
|
||||
return 0
|
||||
}
|
||||
return b.array[b.head]
|
||||
}
|
||||
|
||||
// PeekBack returns but does not remove the last element.
|
||||
func (b *Buffer) PeekBack() float64 {
|
||||
if b.size == 0 {
|
||||
return 0
|
||||
}
|
||||
if b.tail == 0 {
|
||||
return b.array[len(b.array)-1]
|
||||
}
|
||||
return b.array[b.tail-1]
|
||||
}
|
||||
|
||||
// TrimExcess resizes the capacity of the buffer to better fit the contents.
|
||||
func (b *Buffer) TrimExcess() {
|
||||
threshold := float64(len(b.array)) * 0.9
|
||||
if b.size < int(threshold) {
|
||||
b.SetCapacity(b.size)
|
||||
}
|
||||
}
|
||||
|
||||
// Array returns the ring buffer, in order, as an array.
|
||||
func (b *Buffer) Array() Array {
|
||||
newArray := make([]float64, b.size)
|
||||
|
||||
if b.size == 0 {
|
||||
return newArray
|
||||
}
|
||||
|
||||
if b.head < b.tail {
|
||||
arrayCopy(b.array, b.head, newArray, 0, b.size)
|
||||
} else {
|
||||
arrayCopy(b.array, b.head, newArray, 0, len(b.array)-b.head)
|
||||
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
|
||||
}
|
||||
|
||||
return Array(newArray)
|
||||
}
|
||||
|
||||
// Each calls the consumer for each element in the buffer.
|
||||
func (b *Buffer) Each(mapfn func(int, float64)) {
|
||||
if b.size == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var index int
|
||||
if b.head < b.tail {
|
||||
for cursor := b.head; cursor < b.tail; cursor++ {
|
||||
mapfn(index, b.array[cursor])
|
||||
index++
|
||||
}
|
||||
} else {
|
||||
for cursor := b.head; cursor < len(b.array); cursor++ {
|
||||
mapfn(index, b.array[cursor])
|
||||
index++
|
||||
}
|
||||
for cursor := 0; cursor < b.tail; cursor++ {
|
||||
mapfn(index, b.array[cursor])
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation for value buffers.
|
||||
func (b *Buffer) String() string {
|
||||
var values []string
|
||||
for _, elem := range b.Array() {
|
||||
values = append(values, fmt.Sprintf("%v", elem))
|
||||
}
|
||||
return strings.Join(values, " <= ")
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// Util methods
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
func arrayClear(source []float64, index, length int) {
|
||||
for x := 0; x < length; x++ {
|
||||
absoluteIndex := x + index
|
||||
source[absoluteIndex] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func arrayCopy(source []float64, sourceIndex int, destination []float64, destinationIndex, length int) {
|
||||
for x := 0; x < length; x++ {
|
||||
from := sourceIndex + x
|
||||
to := destinationIndex + x
|
||||
|
||||
destination[to] = source[from]
|
||||
}
|
||||
}
|
||||
192
seq/buffer_test.go
Normal file
192
seq/buffer_test.go
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
package seq
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/blendlabs/go-assert"
|
||||
)
|
||||
|
||||
func TestBuffer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
buffer := NewBuffer()
|
||||
|
||||
buffer.Enqueue(1)
|
||||
assert.Equal(1, buffer.Len())
|
||||
assert.Equal(1, buffer.Peek())
|
||||
assert.Equal(1, buffer.PeekBack())
|
||||
|
||||
buffer.Enqueue(2)
|
||||
assert.Equal(2, buffer.Len())
|
||||
assert.Equal(1, buffer.Peek())
|
||||
assert.Equal(2, buffer.PeekBack())
|
||||
|
||||
buffer.Enqueue(3)
|
||||
assert.Equal(3, buffer.Len())
|
||||
assert.Equal(1, buffer.Peek())
|
||||
assert.Equal(3, buffer.PeekBack())
|
||||
|
||||
buffer.Enqueue(4)
|
||||
assert.Equal(4, buffer.Len())
|
||||
assert.Equal(1, buffer.Peek())
|
||||
assert.Equal(4, buffer.PeekBack())
|
||||
|
||||
buffer.Enqueue(5)
|
||||
assert.Equal(5, buffer.Len())
|
||||
assert.Equal(1, buffer.Peek())
|
||||
assert.Equal(5, buffer.PeekBack())
|
||||
|
||||
buffer.Enqueue(6)
|
||||
assert.Equal(6, buffer.Len())
|
||||
assert.Equal(1, buffer.Peek())
|
||||
assert.Equal(6, buffer.PeekBack())
|
||||
|
||||
buffer.Enqueue(7)
|
||||
assert.Equal(7, buffer.Len())
|
||||
assert.Equal(1, buffer.Peek())
|
||||
assert.Equal(7, buffer.PeekBack())
|
||||
|
||||
buffer.Enqueue(8)
|
||||
assert.Equal(8, buffer.Len())
|
||||
assert.Equal(1, buffer.Peek())
|
||||
assert.Equal(8, buffer.PeekBack())
|
||||
|
||||
value := buffer.Dequeue()
|
||||
assert.Equal(1, value)
|
||||
assert.Equal(7, buffer.Len())
|
||||
assert.Equal(2, buffer.Peek())
|
||||
assert.Equal(8, buffer.PeekBack())
|
||||
|
||||
value = buffer.Dequeue()
|
||||
assert.Equal(2, value)
|
||||
assert.Equal(6, buffer.Len())
|
||||
assert.Equal(3, buffer.Peek())
|
||||
assert.Equal(8, buffer.PeekBack())
|
||||
|
||||
value = buffer.Dequeue()
|
||||
assert.Equal(3, value)
|
||||
assert.Equal(5, buffer.Len())
|
||||
assert.Equal(4, buffer.Peek())
|
||||
assert.Equal(8, buffer.PeekBack())
|
||||
|
||||
value = buffer.Dequeue()
|
||||
assert.Equal(4, value)
|
||||
assert.Equal(4, buffer.Len())
|
||||
assert.Equal(5, buffer.Peek())
|
||||
assert.Equal(8, buffer.PeekBack())
|
||||
|
||||
value = buffer.Dequeue()
|
||||
assert.Equal(5, value)
|
||||
assert.Equal(3, buffer.Len())
|
||||
assert.Equal(6, buffer.Peek())
|
||||
assert.Equal(8, buffer.PeekBack())
|
||||
|
||||
value = buffer.Dequeue()
|
||||
assert.Equal(6, value)
|
||||
assert.Equal(2, buffer.Len())
|
||||
assert.Equal(7, buffer.Peek())
|
||||
assert.Equal(8, buffer.PeekBack())
|
||||
|
||||
value = buffer.Dequeue()
|
||||
assert.Equal(7, value)
|
||||
assert.Equal(1, buffer.Len())
|
||||
assert.Equal(8, buffer.Peek())
|
||||
assert.Equal(8, buffer.PeekBack())
|
||||
|
||||
value = buffer.Dequeue()
|
||||
assert.Equal(8, value)
|
||||
assert.Equal(0, buffer.Len())
|
||||
assert.Zero(buffer.Peek())
|
||||
assert.Zero(buffer.PeekBack())
|
||||
}
|
||||
|
||||
func TestBufferClear(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
buffer := NewBuffer()
|
||||
buffer.Enqueue(1)
|
||||
buffer.Enqueue(1)
|
||||
buffer.Enqueue(1)
|
||||
buffer.Enqueue(1)
|
||||
buffer.Enqueue(1)
|
||||
buffer.Enqueue(1)
|
||||
buffer.Enqueue(1)
|
||||
buffer.Enqueue(1)
|
||||
|
||||
assert.Equal(8, buffer.Len())
|
||||
|
||||
buffer.Clear()
|
||||
assert.Equal(0, buffer.Len())
|
||||
assert.Zero(buffer.Peek())
|
||||
assert.Zero(buffer.PeekBack())
|
||||
}
|
||||
|
||||
func TestBufferArray(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
buffer := NewBuffer()
|
||||
buffer.Enqueue(1)
|
||||
buffer.Enqueue(2)
|
||||
buffer.Enqueue(3)
|
||||
buffer.Enqueue(4)
|
||||
buffer.Enqueue(5)
|
||||
|
||||
contents := buffer.Array()
|
||||
assert.Len(contents, 5)
|
||||
assert.Equal(1, contents[0])
|
||||
assert.Equal(2, contents[1])
|
||||
assert.Equal(3, contents[2])
|
||||
assert.Equal(4, contents[3])
|
||||
assert.Equal(5, contents[4])
|
||||
}
|
||||
|
||||
func TestBufferEach(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
buffer := NewBuffer()
|
||||
|
||||
for x := 1; x < 17; x++ {
|
||||
buffer.Enqueue(float64(x))
|
||||
}
|
||||
|
||||
called := 0
|
||||
buffer.Each(func(_ int, v float64) {
|
||||
if v == float64(called+1) {
|
||||
called++
|
||||
}
|
||||
})
|
||||
|
||||
assert.Equal(16, called)
|
||||
}
|
||||
|
||||
func TestNewBuffer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
empty := NewBuffer()
|
||||
assert.NotNil(empty)
|
||||
assert.Zero(empty.Len())
|
||||
assert.Equal(bufferDefaultCapacity, empty.Capacity())
|
||||
assert.Zero(empty.Peek())
|
||||
assert.Zero(empty.PeekBack())
|
||||
}
|
||||
|
||||
func TestNewBufferWithValues(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := NewBuffer(1, 2, 3, 4, 5)
|
||||
assert.NotNil(values)
|
||||
assert.Equal(5, values.Len())
|
||||
assert.Equal(1, values.Peek())
|
||||
assert.Equal(5, values.PeekBack())
|
||||
}
|
||||
|
||||
func TestBufferGrowth(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := NewBuffer(1, 2, 3, 4, 5)
|
||||
for i := 0; i < 1<<10; i++ {
|
||||
values.Enqueue(float64(i))
|
||||
}
|
||||
|
||||
assert.Equal(1<<10-1, values.PeekBack())
|
||||
}
|
||||
73
seq/linear.go
Normal file
73
seq/linear.go
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package seq
|
||||
|
||||
// Range returns the array values of a linear seq with a given start, end and optional step.
|
||||
func Range(start, end float64) []float64 {
|
||||
return Seq{NewLinear().WithStart(start).WithEnd(end).WithStep(1.0)}.Array()
|
||||
}
|
||||
|
||||
// RangeWithStep returns the array values of a linear seq with a given start, end and optional step.
|
||||
func RangeWithStep(start, end, step float64) []float64 {
|
||||
return Seq{NewLinear().WithStart(start).WithEnd(end).WithStep(step)}.Array()
|
||||
}
|
||||
|
||||
// NewLinear returns a new linear generator.
|
||||
func NewLinear() *Linear {
|
||||
return &Linear{step: 1.0}
|
||||
}
|
||||
|
||||
// Linear is a stepwise generator.
|
||||
type Linear struct {
|
||||
start float64
|
||||
end float64
|
||||
step float64
|
||||
}
|
||||
|
||||
// Start returns the start value.
|
||||
func (lg Linear) Start() float64 {
|
||||
return lg.start
|
||||
}
|
||||
|
||||
// End returns the end value.
|
||||
func (lg Linear) End() float64 {
|
||||
return lg.end
|
||||
}
|
||||
|
||||
// Step returns the step value.
|
||||
func (lg Linear) Step() float64 {
|
||||
return lg.step
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the seq.
|
||||
func (lg Linear) Len() int {
|
||||
if lg.start < lg.end {
|
||||
return int((lg.end-lg.start)/lg.step) + 1
|
||||
}
|
||||
return int((lg.start-lg.end)/lg.step) + 1
|
||||
}
|
||||
|
||||
// GetValue returns the value at a given index.
|
||||
func (lg Linear) GetValue(index int) float64 {
|
||||
fi := float64(index)
|
||||
if lg.start < lg.end {
|
||||
return lg.start + (fi * lg.step)
|
||||
}
|
||||
return lg.start - (fi * lg.step)
|
||||
}
|
||||
|
||||
// WithStart sets the start and returns the linear generator.
|
||||
func (lg *Linear) WithStart(start float64) *Linear {
|
||||
lg.start = start
|
||||
return lg
|
||||
}
|
||||
|
||||
// WithEnd sets the end and returns the linear generator.
|
||||
func (lg *Linear) WithEnd(end float64) *Linear {
|
||||
lg.end = end
|
||||
return lg
|
||||
}
|
||||
|
||||
// WithStep sets the step and returns the linear generator.
|
||||
func (lg *Linear) WithStep(step float64) *Linear {
|
||||
lg.step = step
|
||||
return lg
|
||||
}
|
||||
48
seq/linear_test.go
Normal file
48
seq/linear_test.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package seq
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
assert "github.com/blendlabs/go-assert"
|
||||
)
|
||||
|
||||
func TestRange(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := Range(1, 100)
|
||||
assert.Len(values, 100)
|
||||
assert.Equal(1, values[0])
|
||||
assert.Equal(100, values[99])
|
||||
}
|
||||
|
||||
func TestRangeWithStep(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := RangeWithStep(0, 100, 5)
|
||||
assert.Equal(100, values[20])
|
||||
assert.Len(values, 21)
|
||||
}
|
||||
|
||||
func TestRangeReversed(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := Range(10.0, 1.0)
|
||||
assert.Equal(10, len(values))
|
||||
assert.Equal(10.0, values[0])
|
||||
assert.Equal(1.0, values[9])
|
||||
}
|
||||
|
||||
func TestValuesRegression(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// note; this assumes a 1.0 step is implicitly set in the constructor.
|
||||
linearProvider := NewLinear().WithStart(1.0).WithEnd(100.0)
|
||||
assert.Equal(1, linearProvider.Start())
|
||||
assert.Equal(100, linearProvider.End())
|
||||
assert.Equal(100, linearProvider.Len())
|
||||
|
||||
values := Seq{Provider: linearProvider}.Array()
|
||||
assert.Len(values, 100)
|
||||
assert.Equal(1.0, values[0])
|
||||
assert.Equal(100, values[99])
|
||||
}
|
||||
78
seq/random.go
Normal file
78
seq/random.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package seq
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RandomValues returns an array of random values.
|
||||
func RandomValues(count int) []float64 {
|
||||
return Seq{NewRandom().WithLen(count)}.Array()
|
||||
}
|
||||
|
||||
// RandomValuesWithAverage returns an array of random values with a given average.
|
||||
func RandomValuesWithAverage(average float64, count int) []float64 {
|
||||
return Seq{NewRandom().WithAverage(average).WithLen(count)}.Array()
|
||||
}
|
||||
|
||||
// NewRandom creates a new random seq.
|
||||
func NewRandom() *Random {
|
||||
return &Random{
|
||||
rnd: rand.New(rand.NewSource(time.Now().Unix())),
|
||||
}
|
||||
}
|
||||
|
||||
// Random is a random number seq generator.
|
||||
type Random struct {
|
||||
rnd *rand.Rand
|
||||
scale *float64
|
||||
average *float64
|
||||
len *int
|
||||
}
|
||||
|
||||
// Len returns the number of elements that will be generated.
|
||||
func (r *Random) Len() int {
|
||||
if r.len != nil {
|
||||
return *r.len
|
||||
}
|
||||
return math.MaxInt32
|
||||
}
|
||||
|
||||
// GetValue returns the value.
|
||||
func (r *Random) GetValue(_ int) float64 {
|
||||
if r.average != nil && r.scale != nil {
|
||||
return *r.average + *r.scale - (r.rnd.Float64() * (2 * *r.scale))
|
||||
} else if r.scale != nil {
|
||||
return r.rnd.Float64() * *r.scale
|
||||
}
|
||||
return r.rnd.Float64()
|
||||
}
|
||||
|
||||
// WithLen sets a maximum len
|
||||
func (r *Random) WithLen(length int) *Random {
|
||||
r.len = &length
|
||||
return r
|
||||
}
|
||||
|
||||
// Scale returns the scale.
|
||||
func (r Random) Scale() *float64 {
|
||||
return r.scale
|
||||
}
|
||||
|
||||
// WithScale sets the scale and returns the Random.
|
||||
func (r *Random) WithScale(scale float64) *Random {
|
||||
r.scale = &scale
|
||||
return r
|
||||
}
|
||||
|
||||
// Average returns the average.
|
||||
func (r Random) Average() *float64 {
|
||||
return r.average
|
||||
}
|
||||
|
||||
// WithAverage sets the average and returns the Random.
|
||||
func (r *Random) WithAverage(average float64) *Random {
|
||||
r.average = &average
|
||||
return r
|
||||
}
|
||||
18
seq/random_test.go
Normal file
18
seq/random_test.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package seq
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
assert "github.com/blendlabs/go-assert"
|
||||
)
|
||||
|
||||
func TestRandomRegression(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
randomProvider := NewRandom().WithLen(100).WithAverage(256)
|
||||
assert.Equal(100, randomProvider.Len())
|
||||
assert.Equal(256, *randomProvider.Average())
|
||||
|
||||
randomValues := New(randomProvider).Array()
|
||||
assert.Len(randomValues, 100)
|
||||
}
|
||||
259
seq/sequence.go
Normal file
259
seq/sequence.go
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
package seq
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// New wraps a provider with a seq.
|
||||
func New(provider Provider) Seq {
|
||||
return Seq{Provider: provider}
|
||||
}
|
||||
|
||||
// Values returns a new seq composed of a given set of values.
|
||||
func Values(values ...float64) Seq {
|
||||
return Seq{Provider: Array(values)}
|
||||
}
|
||||
|
||||
// Provider is a provider for values for a seq.
|
||||
type Provider interface {
|
||||
Len() int
|
||||
GetValue(int) float64
|
||||
}
|
||||
|
||||
// Seq is a utility wrapper for seq providers.
|
||||
type Seq struct {
|
||||
Provider
|
||||
}
|
||||
|
||||
// Array enumerates the seq into a slice.
|
||||
func (s Seq) Array() (output []float64) {
|
||||
if s.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
output = make([]float64, s.Len())
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
output[i] = s.GetValue(i)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Each applies the `mapfn` to all values in the value provider.
|
||||
func (s Seq) Each(mapfn func(int, float64)) {
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
mapfn(i, s.GetValue(i))
|
||||
}
|
||||
}
|
||||
|
||||
// Map applies the `mapfn` to all values in the value provider,
|
||||
// returning a new seq.
|
||||
func (s Seq) Map(mapfn func(i int, v float64) float64) Seq {
|
||||
output := make([]float64, s.Len())
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
mapfn(i, s.GetValue(i))
|
||||
}
|
||||
return Seq{Array(output)}
|
||||
}
|
||||
|
||||
// FoldLeft collapses a seq from left to right.
|
||||
func (s Seq) FoldLeft(mapfn func(i int, v0, v float64) float64) (v0 float64) {
|
||||
if s.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if s.Len() == 1 {
|
||||
return s.GetValue(0)
|
||||
}
|
||||
|
||||
v0 = s.GetValue(0)
|
||||
for i := 1; i < s.Len(); i++ {
|
||||
v0 = mapfn(i, v0, s.GetValue(i))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FoldRight collapses a seq from right to left.
|
||||
func (s Seq) FoldRight(mapfn func(i int, v0, v float64) float64) (v0 float64) {
|
||||
if s.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if s.Len() == 1 {
|
||||
return s.GetValue(0)
|
||||
}
|
||||
|
||||
v0 = s.GetValue(s.Len() - 1)
|
||||
for i := s.Len() - 2; i >= 0; i-- {
|
||||
v0 = mapfn(i, v0, s.GetValue(i))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Min returns the minimum value in the seq.
|
||||
func (s Seq) Min() float64 {
|
||||
if s.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
min := s.GetValue(0)
|
||||
var value float64
|
||||
for i := 1; i < s.Len(); i++ {
|
||||
value = s.GetValue(i)
|
||||
if value < min {
|
||||
min = value
|
||||
}
|
||||
}
|
||||
return min
|
||||
}
|
||||
|
||||
// Max returns the maximum value in the seq.
|
||||
func (s Seq) Max() float64 {
|
||||
if s.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
max := s.GetValue(0)
|
||||
var value float64
|
||||
for i := 1; i < s.Len(); i++ {
|
||||
value = s.GetValue(i)
|
||||
if value > max {
|
||||
max = value
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
// MinMax returns the minimum and the maximum in one pass.
|
||||
func (s Seq) MinMax() (min, max float64) {
|
||||
if s.Len() == 0 {
|
||||
return
|
||||
}
|
||||
min = s.GetValue(0)
|
||||
max = min
|
||||
var value float64
|
||||
for i := 1; i < s.Len(); i++ {
|
||||
value = s.GetValue(i)
|
||||
if value < min {
|
||||
min = value
|
||||
}
|
||||
if value > max {
|
||||
max = value
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Sort returns the seq sorted in ascending order.
|
||||
// This fully enumerates the seq.
|
||||
func (s Seq) Sort() Seq {
|
||||
if s.Len() == 0 {
|
||||
return s
|
||||
}
|
||||
values := s.Array()
|
||||
sort.Float64s(values)
|
||||
return Seq{Provider: Array(values)}
|
||||
}
|
||||
|
||||
// Median returns the median or middle value in the sorted seq.
|
||||
func (s Seq) Median() (median float64) {
|
||||
l := s.Len()
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
sorted := s.Sort()
|
||||
if l%2 == 0 {
|
||||
v0 := sorted.GetValue(l/2 - 1)
|
||||
v1 := sorted.GetValue(l/2 + 1)
|
||||
median = (v0 + v1) / 2
|
||||
} else {
|
||||
median = float64(sorted.GetValue(l << 1))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Sum adds all the elements of a series together.
|
||||
func (s Seq) Sum() (accum float64) {
|
||||
if s.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
accum += s.GetValue(i)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Average returns the float average of the values in the buffer.
|
||||
func (s Seq) Average() float64 {
|
||||
if s.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return s.Sum() / float64(s.Len())
|
||||
}
|
||||
|
||||
// Variance computes the variance of the buffer.
|
||||
func (s Seq) Variance() float64 {
|
||||
if s.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
m := s.Average()
|
||||
var variance, v float64
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
v = s.GetValue(i)
|
||||
variance += (v - m) * (v - m)
|
||||
}
|
||||
|
||||
return variance / float64(s.Len())
|
||||
}
|
||||
|
||||
// StdDev returns the standard deviation.
|
||||
func (s Seq) StdDev() float64 {
|
||||
if s.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return math.Pow(s.Variance(), 0.5)
|
||||
}
|
||||
|
||||
//Percentile finds the relative standing in a slice of floats.
|
||||
// `percent` should be given on the interval [0,1.0).
|
||||
func (s Seq) Percentile(percent float64) (percentile float64) {
|
||||
l := s.Len()
|
||||
if l == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if percent < 0 || percent > 1.0 {
|
||||
panic("percent out of range [0.0, 1.0)")
|
||||
}
|
||||
|
||||
sorted := s.Sort()
|
||||
index := percent * float64(l)
|
||||
if index == float64(int64(index)) {
|
||||
i := f64i(index)
|
||||
ci := sorted.GetValue(i - 1)
|
||||
c := sorted.GetValue(i)
|
||||
percentile = (ci + c) / 2.0
|
||||
} else {
|
||||
i := f64i(index)
|
||||
percentile = sorted.GetValue(i)
|
||||
}
|
||||
|
||||
return percentile
|
||||
}
|
||||
|
||||
// Normalize maps every value to the interval [0, 1.0].
|
||||
func (s Seq) Normalize() Seq {
|
||||
min, max := s.MinMax()
|
||||
|
||||
delta := max - min
|
||||
output := make([]float64, s.Len())
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
output[i] = (s.GetValue(i) - min) / delta
|
||||
}
|
||||
|
||||
return Seq{Provider: Array(output)}
|
||||
}
|
||||
95
seq/sequence_test.go
Normal file
95
seq/sequence_test.go
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
package seq
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
assert "github.com/blendlabs/go-assert"
|
||||
)
|
||||
|
||||
func TestSequenceEach(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := Seq{NewArray(1, 2, 3, 4)}
|
||||
values.Each(func(i int, v float64) {
|
||||
assert.Equal(i, v-1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSequenceMap(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := Seq{NewArray(1, 2, 3, 4)}
|
||||
mapped := values.Map(func(i int, v float64) float64 {
|
||||
assert.Equal(i, v-1)
|
||||
return v * 2
|
||||
})
|
||||
assert.Equal(4, mapped.Len())
|
||||
}
|
||||
|
||||
func TestSequenceFoldLeft(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := Seq{NewArray(1, 2, 3, 4)}
|
||||
ten := values.FoldLeft(func(_ int, vp, v float64) float64 {
|
||||
return vp + v
|
||||
})
|
||||
assert.Equal(10, ten)
|
||||
|
||||
orderTest := Seq{NewArray(10, 3, 2, 1)}
|
||||
four := orderTest.FoldLeft(func(_ int, vp, v float64) float64 {
|
||||
return vp - v
|
||||
})
|
||||
assert.Equal(4, four)
|
||||
}
|
||||
|
||||
func TestSequenceFoldRight(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := Seq{NewArray(1, 2, 3, 4)}
|
||||
ten := values.FoldRight(func(_ int, vp, v float64) float64 {
|
||||
return vp + v
|
||||
})
|
||||
assert.Equal(10, ten)
|
||||
|
||||
orderTest := Seq{NewArray(10, 3, 2, 1)}
|
||||
notFour := orderTest.FoldRight(func(_ int, vp, v float64) float64 {
|
||||
return vp - v
|
||||
})
|
||||
assert.Equal(-14, notFour)
|
||||
}
|
||||
|
||||
func TestSequenceSum(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := Seq{NewArray(1, 2, 3, 4)}
|
||||
assert.Equal(10, values.Sum())
|
||||
}
|
||||
|
||||
func TestSequenceAverage(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := Seq{NewArray(1, 2, 3, 4)}
|
||||
assert.Equal(2.5, values.Average())
|
||||
|
||||
valuesOdd := Seq{NewArray(1, 2, 3, 4, 5)}
|
||||
assert.Equal(3, valuesOdd.Average())
|
||||
}
|
||||
|
||||
func TestSequenceVariance(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := Seq{NewArray(1, 2, 3, 4, 5)}
|
||||
assert.Equal(2, values.Variance())
|
||||
}
|
||||
|
||||
func TestSequenceNormalize(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
normalized := Values(1, 2, 3, 4, 5).Normalize().Array()
|
||||
|
||||
assert.NotEmpty(normalized)
|
||||
assert.Len(normalized, 5)
|
||||
assert.Equal(0, normalized[0])
|
||||
assert.Equal(0.25, normalized[1])
|
||||
assert.Equal(1, normalized[4])
|
||||
}
|
||||
171
seq/time.go
Normal file
171
seq/time.go
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
package seq
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/wcharczuk/go-chart/util"
|
||||
)
|
||||
|
||||
// Time is a utility singleton with helper functions for time seq generation.
|
||||
var Time timeSequence
|
||||
|
||||
type timeSequence struct{}
|
||||
|
||||
// Days generates a seq of timestamps by day, from -days to today.
|
||||
func (ts timeSequence) Days(days int) []time.Time {
|
||||
var values []time.Time
|
||||
for day := days; day >= 0; day-- {
|
||||
values = append(values, time.Now().AddDate(0, 0, -day))
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func (ts timeSequence) MarketHours(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := util.Date.On(marketOpen, from)
|
||||
toClose := util.Date.On(marketClose, to)
|
||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||
todayOpen := util.Date.On(marketOpen, cursor)
|
||||
todayClose := util.Date.On(marketClose, cursor)
|
||||
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
||||
|
||||
if (cursor.Equal(todayOpen) || cursor.After(todayOpen)) && (cursor.Equal(todayClose) || cursor.Before(todayClose)) && isValidTradingDay {
|
||||
times = append(times, cursor)
|
||||
}
|
||||
if cursor.After(todayClose) {
|
||||
cursor = util.Date.NextMarketOpen(cursor, marketOpen, isHoliday)
|
||||
} else {
|
||||
cursor = util.Date.NextHour(cursor)
|
||||
}
|
||||
}
|
||||
return times
|
||||
}
|
||||
|
||||
func (ts timeSequence) MarketHourQuarters(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := util.Date.On(marketOpen, from)
|
||||
toClose := util.Date.On(marketClose, to)
|
||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||
|
||||
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
||||
|
||||
if isValidTradingDay {
|
||||
todayOpen := util.Date.On(marketOpen, cursor)
|
||||
todayNoon := util.Date.NoonOn(cursor)
|
||||
today2pm := util.Date.On(util.Date.Time(14, 0, 0, 0, cursor.Location()), cursor)
|
||||
todayClose := util.Date.On(marketClose, cursor)
|
||||
times = append(times, todayOpen, todayNoon, today2pm, todayClose)
|
||||
}
|
||||
|
||||
cursor = util.Date.NextDay(cursor)
|
||||
}
|
||||
return times
|
||||
}
|
||||
|
||||
func (ts timeSequence) MarketDayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := util.Date.On(marketOpen, from)
|
||||
toClose := util.Date.On(marketClose, to)
|
||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
||||
if isValidTradingDay {
|
||||
todayClose := util.Date.On(marketClose, cursor)
|
||||
times = append(times, todayClose)
|
||||
}
|
||||
|
||||
cursor = util.Date.NextDay(cursor)
|
||||
}
|
||||
return times
|
||||
}
|
||||
|
||||
func (ts timeSequence) MarketDayAlternateCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := util.Date.On(marketOpen, from)
|
||||
toClose := util.Date.On(marketClose, to)
|
||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
||||
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
||||
if isValidTradingDay {
|
||||
todayClose := util.Date.On(marketClose, cursor)
|
||||
times = append(times, todayClose)
|
||||
}
|
||||
|
||||
cursor = cursor.AddDate(0, 0, 2)
|
||||
}
|
||||
return times
|
||||
}
|
||||
|
||||
func (ts timeSequence) MarketDayMondayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
||||
var times []time.Time
|
||||
cursor := util.Date.On(marketClose, from)
|
||||
toClose := util.Date.On(marketClose, to)
|
||||
|
||||
for cursor.Equal(toClose) || cursor.Before(toClose) {
|
||||
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
||||
if isValidTradingDay {
|
||||
times = append(times, cursor)
|
||||
}
|
||||
cursor = util.Date.NextDayOfWeek(cursor, time.Monday)
|
||||
}
|
||||
return times
|
||||
}
|
||||
|
||||
func (ts timeSequence) Hours(start time.Time, totalHours int) []time.Time {
|
||||
times := make([]time.Time, totalHours)
|
||||
|
||||
last := start
|
||||
for i := 0; i < totalHours; i++ {
|
||||
times[i] = last
|
||||
last = last.Add(time.Hour)
|
||||
}
|
||||
|
||||
return times
|
||||
}
|
||||
|
||||
// HoursFilled adds zero values for the data bounded by the start and end of the xdata array.
|
||||
func (ts timeSequence) HoursFilled(xdata []time.Time, ydata []float64) ([]time.Time, []float64) {
|
||||
start := Time.Start(xdata)
|
||||
end := Time.End(xdata)
|
||||
|
||||
totalHours := util.Math.AbsInt(util.Date.DiffHours(start, end))
|
||||
|
||||
finalTimes := ts.Hours(start, totalHours+1)
|
||||
finalValues := make([]float64, totalHours+1)
|
||||
|
||||
var hoursFromStart int
|
||||
for i, xd := range xdata {
|
||||
hoursFromStart = util.Date.DiffHours(start, xd)
|
||||
finalValues[hoursFromStart] = ydata[i]
|
||||
}
|
||||
|
||||
return finalTimes, finalValues
|
||||
}
|
||||
|
||||
// Start returns the earliest (min) time in a list of times.
|
||||
func (ts timeSequence) Start(times []time.Time) time.Time {
|
||||
if len(times) == 0 {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
start := times[0]
|
||||
for _, t := range times[1:] {
|
||||
if t.Before(start) {
|
||||
start = t
|
||||
}
|
||||
}
|
||||
return start
|
||||
}
|
||||
|
||||
// Start returns the earliest (min) time in a list of times.
|
||||
func (ts timeSequence) End(times []time.Time) time.Time {
|
||||
if len(times) == 0 {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
end := times[0]
|
||||
for _, t := range times[1:] {
|
||||
if t.After(end) {
|
||||
end = t
|
||||
}
|
||||
}
|
||||
return end
|
||||
}
|
||||
111
seq/time_test.go
Normal file
111
seq/time_test.go
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
package seq
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
assert "github.com/blendlabs/go-assert"
|
||||
"github.com/wcharczuk/go-chart/util"
|
||||
)
|
||||
|
||||
func TestTimeMarketHours(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
today := time.Date(2016, 07, 01, 12, 0, 0, 0, util.Date.Eastern())
|
||||
mh := Time.MarketHours(today, today, util.NYSEOpen(), util.NYSEClose(), util.Date.IsNYSEHoliday)
|
||||
assert.Len(mh, 8)
|
||||
assert.Equal(util.Date.Eastern(), mh[0].Location())
|
||||
}
|
||||
|
||||
func TestTimeMarketHourQuarters(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
today := time.Date(2016, 07, 01, 12, 0, 0, 0, util.Date.Eastern())
|
||||
mh := Time.MarketHourQuarters(today, today, util.NYSEOpen(), util.NYSEClose(), util.Date.IsNYSEHoliday)
|
||||
assert.Len(mh, 4)
|
||||
assert.Equal(9, mh[0].Hour())
|
||||
assert.Equal(30, mh[0].Minute())
|
||||
assert.Equal(util.Date.Eastern(), mh[0].Location())
|
||||
|
||||
assert.Equal(12, mh[1].Hour())
|
||||
assert.Equal(00, mh[1].Minute())
|
||||
assert.Equal(util.Date.Eastern(), mh[1].Location())
|
||||
|
||||
assert.Equal(14, mh[2].Hour())
|
||||
assert.Equal(00, mh[2].Minute())
|
||||
assert.Equal(util.Date.Eastern(), mh[2].Location())
|
||||
}
|
||||
|
||||
func TestTimeHours(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
today := time.Date(2016, 07, 01, 12, 0, 0, 0, time.UTC)
|
||||
seq := Time.Hours(today, 24)
|
||||
|
||||
end := Time.End(seq)
|
||||
assert.Len(seq, 24)
|
||||
assert.Equal(2016, end.Year())
|
||||
assert.Equal(07, int(end.Month()))
|
||||
assert.Equal(02, end.Day())
|
||||
assert.Equal(11, end.Hour())
|
||||
}
|
||||
|
||||
func TestSequenceHoursFill(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
xdata := []time.Time{
|
||||
time.Date(2016, 07, 01, 12, 0, 0, 0, time.UTC),
|
||||
time.Date(2016, 07, 01, 13, 0, 0, 0, time.UTC),
|
||||
time.Date(2016, 07, 01, 14, 0, 0, 0, time.UTC),
|
||||
time.Date(2016, 07, 02, 4, 0, 0, 0, time.UTC),
|
||||
time.Date(2016, 07, 02, 5, 0, 0, 0, time.UTC),
|
||||
time.Date(2016, 07, 03, 12, 0, 0, 0, time.UTC),
|
||||
time.Date(2016, 07, 03, 14, 0, 0, 0, time.UTC),
|
||||
}
|
||||
|
||||
ydata := []float64{
|
||||
1.1,
|
||||
1.2,
|
||||
1.4,
|
||||
0.8,
|
||||
2.1,
|
||||
0.4,
|
||||
0.6,
|
||||
}
|
||||
|
||||
filledTimes, filledValues := Time.HoursFilled(xdata, ydata)
|
||||
assert.Len(filledTimes, util.Date.DiffHours(Time.Start(xdata), Time.End(xdata))+1)
|
||||
assert.Equal(len(filledValues), len(filledTimes))
|
||||
|
||||
assert.NotZero(filledValues[0])
|
||||
assert.NotZero(filledValues[len(filledValues)-1])
|
||||
|
||||
assert.NotZero(filledValues[16])
|
||||
}
|
||||
|
||||
func TestTimeStart(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
times := []time.Time{
|
||||
time.Now().AddDate(0, 0, -4),
|
||||
time.Now().AddDate(0, 0, -2),
|
||||
time.Now().AddDate(0, 0, -1),
|
||||
time.Now().AddDate(0, 0, -3),
|
||||
time.Now().AddDate(0, 0, -5),
|
||||
}
|
||||
|
||||
assert.InTimeDelta(Time.Start(times), times[4], time.Millisecond)
|
||||
}
|
||||
|
||||
func TestTimeEnd(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
times := []time.Time{
|
||||
time.Now().AddDate(0, 0, -4),
|
||||
time.Now().AddDate(0, 0, -2),
|
||||
time.Now().AddDate(0, 0, -1),
|
||||
time.Now().AddDate(0, 0, -3),
|
||||
time.Now().AddDate(0, 0, -5),
|
||||
}
|
||||
|
||||
assert.InTimeDelta(Time.End(times), times[2], time.Millisecond)
|
||||
}
|
||||
32
seq/util.go
Normal file
32
seq/util.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package seq
|
||||
|
||||
import "math"
|
||||
|
||||
func round(input float64, places int) (rounded float64) {
|
||||
if math.IsNaN(input) {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
sign := 1.0
|
||||
if input < 0 {
|
||||
sign = -1
|
||||
input *= -1
|
||||
}
|
||||
|
||||
precision := math.Pow(10, float64(places))
|
||||
digit := input * precision
|
||||
_, decimal := math.Modf(digit)
|
||||
|
||||
if decimal >= 0.5 {
|
||||
rounded = math.Ceil(digit)
|
||||
} else {
|
||||
rounded = math.Floor(digit)
|
||||
}
|
||||
|
||||
return rounded / precision * sign
|
||||
}
|
||||
|
||||
func f64i(value float64) int {
|
||||
r := round(value, 0)
|
||||
return int(r)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue