summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWill Hawkins <[email protected]>2023-01-27 22:03:40 -0500
committerWill Hawkins <[email protected]>2023-01-27 22:04:29 -0500
commitdabbcf98628d841667a0a5af6ecd5c10c7906c85 (patch)
treef254966fc34fd6815bc82f09104debab410da472
parent8557174c5ae182b493b596d2e465457a1c73ec84 (diff)
[Feature] support for doing a Double-Sided Trim on Mathematical Series
-rw-r--r--ms/ms.go95
-rw-r--r--ms/ms_test.go71
2 files changed, 153 insertions, 13 deletions
diff --git a/ms/ms.go b/ms/ms.go
index e6a3b02..6712ab1 100644
--- a/ms/ms.go
+++ b/ms/ms.go
@@ -15,6 +15,7 @@
package ms
import (
+ "fmt"
"math"
"sort"
@@ -29,9 +30,12 @@ type MathematicalSeries[T constraints.Float | constraints.Integer] interface {
AllSequentialIncreasesLessThan(float64) (bool, float64)
StandardDeviation() (bool, T)
IsNormallyDistributed() bool
- Size() int
+ Len() int
Values() []T
Percentile(int) T
+ DoubleSidedTrim(uint32) MathematicalSeries[T]
+ Less(int, int) bool
+ Swap(int, int)
}
func calculateAverage[T constraints.Integer | constraints.Float](elements []T) float64 {
@@ -53,12 +57,45 @@ func calculatePercentile[T constraints.Integer | constraints.Float](elements []T
result = elements[pindex]
return
}
+
+type InfiniteMathematicalSeries[T constraints.Float | constraints.Integer] struct {
+ elements []T
+}
+
func NewInfiniteMathematicalSeries[T constraints.Float | constraints.Integer]() MathematicalSeries[T] {
return &InfiniteMathematicalSeries[T]{}
}
-type InfiniteMathematicalSeries[T constraints.Float | constraints.Integer] struct {
- elements []T
+func (ims *InfiniteMathematicalSeries[T]) Swap(i, j int) {
+ ims.elements[i], ims.elements[j] = ims.elements[j], ims.elements[i]
+}
+
+func (ims *InfiniteMathematicalSeries[T]) Less(i, j int) bool {
+ return ims.elements[i] < ims.elements[j]
+}
+
+func (ims *InfiniteMathematicalSeries[T]) DoubleSidedTrim(percent uint32) MathematicalSeries[T] {
+ if percent >= 100 {
+ panic(fmt.Sprintf("Cannot perform double-sided trim for an invalid percentage: %d", percent))
+ }
+
+ trimmed := &InfiniteMathematicalSeries[T]{}
+ trimmed.elements = make([]T, ims.Len())
+ copy(trimmed.elements, ims.elements)
+
+ sort.Sort(trimmed)
+
+ elementsToTrim := uint64(float32(ims.Len()) * ((float32(percent)) / float32(100.0)))
+ trimmed.elements = trimmed.elements[elementsToTrim : len(trimmed.elements)-int(elementsToTrim)]
+
+ return trimmed
+}
+
+func (ims *InfiniteMathematicalSeries[T]) Copy() MathematicalSeries[T] {
+ newIms := InfiniteMathematicalSeries[T]{}
+ newIms.elements = make([]T, ims.Len())
+ copy(newIms.elements, ims.elements)
+ return &newIms
}
func (ims *InfiniteMathematicalSeries[T]) AddElement(element T) {
@@ -130,7 +167,7 @@ func (ims *InfiniteMathematicalSeries[T]) IsNormallyDistributed() bool {
return false
}
-func (ims *InfiniteMathematicalSeries[T]) Size() int {
+func (ims *InfiniteMathematicalSeries[T]) Len() int {
return len(ims.elements)
}
@@ -143,17 +180,17 @@ func (ims *InfiniteMathematicalSeries[T]) Percentile(p int) T {
}
type CappedMathematicalSeries[T constraints.Float | constraints.Integer] struct {
- elements_count int
+ elements_count uint64
elements []T
- index int
- divisor *saturating.SaturatingInt
+ index uint64
+ divisor *saturating.Saturating[uint64]
}
-func NewCappedMathematicalSeries[T constraints.Float | constraints.Integer](instants_count int) MathematicalSeries[T] {
+func NewCappedMathematicalSeries[T constraints.Float | constraints.Integer](instants_count uint64) MathematicalSeries[T] {
return &CappedMathematicalSeries[T]{
elements: make([]T, instants_count),
elements_count: instants_count,
- divisor: saturating.NewSaturatingInt(instants_count),
+ divisor: saturating.NewSaturating(instants_count),
index: 0,
}
}
@@ -185,7 +222,7 @@ func (ma *CappedMathematicalSeries[T]) AllSequentialIncreasesLessThan(limit floa
oldestIndex := ma.index
previous := ma.elements[oldestIndex]
maximumSequentialIncrease = 0
- for i := 1; i < ma.elements_count; i++ {
+ for i := uint64(1); i < ma.elements_count; i++ {
currentIndex := (oldestIndex + i) % ma.elements_count
current := ma.elements[currentIndex]
percentChange := utilities.SignedPercentDifference(current, previous)
@@ -263,7 +300,10 @@ func (ma *CappedMathematicalSeries[T]) Values() []T {
return ma.elements
}
-func (ma *CappedMathematicalSeries[T]) Size() int {
+func (ma *CappedMathematicalSeries[T]) Len() int {
+ if uint64(len(ma.elements)) != ma.elements_count {
+ panic(fmt.Sprintf("Error: A capped mathematical series' metadata is invalid: the length of its element array/slice does not match element_count! (%v vs %v)", ma.elements_count, len(ma.elements)))
+ }
return len(ma.elements)
}
@@ -280,3 +320,36 @@ func (ma *CappedMathematicalSeries[T]) Percentile(p int) T {
copy(kopy, ma.elements)
return calculatePercentile(kopy, p)
}
+
+func (ims *CappedMathematicalSeries[T]) Swap(i, j int) {
+ ims.elements[i], ims.elements[j] = ims.elements[j], ims.elements[i]
+}
+
+func (ims *CappedMathematicalSeries[T]) Less(i, j int) bool {
+ return ims.elements[i] < ims.elements[j]
+}
+
+func (ims *CappedMathematicalSeries[T]) DoubleSidedTrim(percent uint32) MathematicalSeries[T] {
+ if percent >= 100 {
+ panic(fmt.Sprintf("Cannot perform double-sided trim for an invalid percentage: %d", percent))
+ }
+
+ trimmed := &CappedMathematicalSeries[T]{elements_count: uint64(ims.Len())}
+ trimmed.elements = make([]T, ims.Len())
+ copy(trimmed.elements, ims.elements)
+ sort.Sort(trimmed)
+
+ elementsToTrim := uint64(float32(ims.Len()) * ((float32(percent)) / float32(100.0)))
+ trimmed.elements = trimmed.elements[elementsToTrim : len(trimmed.elements)-int(elementsToTrim)]
+
+ trimmed.elements_count -= (elementsToTrim * 2)
+
+ return trimmed
+}
+
+func (ims *CappedMathematicalSeries[T]) Copy() MathematicalSeries[T] {
+ newCms := CappedMathematicalSeries[T]{}
+ newCms.elements = make([]T, ims.Len())
+ copy(newCms.elements, ims.elements)
+ return &newCms
+}
diff --git a/ms/ms_test.go b/ms/ms_test.go
index d93102f..87f3c8a 100644
--- a/ms/ms_test.go
+++ b/ms/ms_test.go
@@ -106,6 +106,44 @@ func Test_Infinite50_percentile_jumbled(test *testing.T) {
}
}
+func Test_InfiniteDoubleSidedTrimmedMean_jumbled(test *testing.T) {
+ series := NewInfiniteMathematicalSeries[int64]()
+ series.AddElement(7)
+ series.AddElement(2)
+ series.AddElement(15)
+ series.AddElement(27)
+ series.AddElement(5)
+ series.AddElement(5)
+ series.AddElement(52)
+ series.AddElement(18)
+ series.AddElement(23)
+ series.AddElement(11)
+ series.AddElement(22)
+ series.AddElement(17)
+ series.AddElement(14)
+ series.AddElement(9)
+ series.AddElement(100)
+ series.AddElement(72)
+ series.AddElement(91)
+ series.AddElement(43)
+ series.AddElement(37)
+ series.AddElement(62)
+
+ trimmed := series.DoubleSidedTrim(10)
+
+ if trimmed.Len() != 16 {
+ test.Fatalf("Capped series is not of the proper size. Expected %v and got %v", 16, trimmed.Len())
+ }
+
+ prev := int64(0)
+ for _, v := range trimmed.Values() {
+ if !(prev <= v) {
+ test.Fatalf("Not sorted: %v is not less than or equal to %v\n", prev, v)
+ }
+ prev = v
+ }
+}
+
func Test_CappedSequentialIncreasesAlwaysLessThan(test *testing.T) {
series := NewCappedMathematicalSeries[float64](40)
previous := float64(1.0)
@@ -190,7 +228,7 @@ func Test_CappedRotatingValues(test *testing.T) {
test.Fatalf("Adding values does not properly erase earlier values.")
}
}
-func Test_CappedSize(test *testing.T) {
+func Test_CappedLen(test *testing.T) {
series := NewCappedMathematicalSeries[int](5)
series.AddElement(1)
@@ -202,7 +240,7 @@ func Test_CappedSize(test *testing.T) {
series.AddElement(6)
series.AddElement(7)
- if series.Size() != 5 {
+ if series.Len() != 5 {
test.Fatalf("Series size calculations failed.")
}
}
@@ -272,3 +310,32 @@ func Test_Capped50_percentile_jumbled(test *testing.T) {
test.Fatalf("Series 50 percentile of a jumble of numbers failed: Expected 15 got %v.", series.Percentile(50))
}
}
+
+func Test_CappedDoubleSidedTrimmedMean_jumbled(test *testing.T) {
+ series := NewCappedMathematicalSeries[int64](10)
+ series.AddElement(7)
+ series.AddElement(2)
+ series.AddElement(15)
+ series.AddElement(27)
+ series.AddElement(5)
+ series.AddElement(5)
+ series.AddElement(52)
+ series.AddElement(18)
+ series.AddElement(23)
+ series.AddElement(11)
+ series.AddElement(12)
+
+ trimmed := series.DoubleSidedTrim(10)
+
+ if trimmed.Len() != 8 {
+ test.Fatalf("Capped series is not of the proper size. Expected %v and got %v", 8, trimmed.Len())
+ }
+
+ prev := int64(0)
+ for _, v := range trimmed.Values() {
+ if !(prev <= v) {
+ test.Fatalf("Not sorted: %v is not less than or equal to %v\n", prev, v)
+ }
+ prev = v
+ }
+}