diff options
| author | Will Hawkins <[email protected]> | 2023-01-27 22:03:40 -0500 |
|---|---|---|
| committer | Will Hawkins <[email protected]> | 2023-01-27 22:04:29 -0500 |
| commit | dabbcf98628d841667a0a5af6ecd5c10c7906c85 (patch) | |
| tree | f254966fc34fd6815bc82f09104debab410da472 | |
| parent | 8557174c5ae182b493b596d2e465457a1c73ec84 (diff) | |
[Feature] support for doing a Double-Sided Trim on Mathematical Series
| -rw-r--r-- | ms/ms.go | 95 | ||||
| -rw-r--r-- | ms/ms_test.go | 71 |
2 files changed, 153 insertions, 13 deletions
@@ -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 + } +} |
