summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ms/ms.go127
-rw-r--r--ms/ms_test.go117
2 files changed, 219 insertions, 25 deletions
diff --git a/ms/ms.go b/ms/ms.go
index 0a91e04..e6a3b02 100644
--- a/ms/ms.go
+++ b/ms/ms.go
@@ -34,6 +34,114 @@ type MathematicalSeries[T constraints.Float | constraints.Integer] interface {
Percentile(int) T
}
+func calculateAverage[T constraints.Integer | constraints.Float](elements []T) float64 {
+ total := T(0)
+ for i := 0; i < len(elements); i++ {
+ total += elements[i]
+ }
+ return float64(total) / float64(len(elements))
+}
+
+func calculatePercentile[T constraints.Integer | constraints.Float](elements []T, p int) (result T) {
+ result = T(0)
+ if p < 0 || p > 100 {
+ return
+ }
+
+ sort.Slice(elements, func(l int, r int) bool { return elements[l] < elements[r] })
+ pindex := int64((float64(p) / float64(100)) * float64(len(elements)))
+ result = elements[pindex]
+ return
+}
+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]) AddElement(element T) {
+ ims.elements = append(ims.elements, element)
+}
+
+func (ims *InfiniteMathematicalSeries[T]) CalculateAverage() float64 {
+ return calculateAverage(ims.elements)
+}
+
+func (ims *InfiniteMathematicalSeries[T]) AllSequentialIncreasesLessThan(limit float64) (bool, float64) {
+ if len(ims.elements) < 2 {
+ return false, 0.0
+ }
+
+ maximumSequentialIncrease := float64(0)
+ for i := 1; i < len(ims.elements); i++ {
+ current := ims.elements[i]
+ previous := ims.elements[i-1]
+ percentChange := utilities.SignedPercentDifference(current, previous)
+ if percentChange > limit {
+ return false, percentChange
+ }
+ if percentChange > float64(maximumSequentialIncrease) {
+ maximumSequentialIncrease = percentChange
+ }
+ }
+ return true, maximumSequentialIncrease
+}
+
+/*
+ * N.B.: Overflow is possible -- use at your discretion!
+ */
+func (ims *InfiniteMathematicalSeries[T]) StandardDeviation() (bool, T) {
+
+ // From https://www.mathsisfun.com/data/standard-deviation-calculator.html
+ // Yes, for real!
+
+ // Calculate the average of the numbers ...
+ average := ims.CalculateAverage()
+
+ // Calculate the square of each of the elements' differences from the mean.
+ differences_squared := make([]float64, len(ims.elements))
+ for index, value := range ims.elements {
+ differences_squared[index] = math.Pow(float64(value-T(average)), 2)
+ }
+
+ // The variance is the average of the squared differences.
+ // So, we need to ...
+
+ // Accumulate all those squared differences.
+ sds := float64(0)
+ for _, dss := range differences_squared {
+ sds += dss
+ }
+
+ // And then divide that total by the number of elements
+ variance := sds / float64(len(ims.elements))
+
+ // Finally, the standard deviation is the square root
+ // of the variance.
+ sd := T(math.Sqrt(variance))
+ //sd := T(variance)
+
+ return true, sd
+}
+
+func (ims *InfiniteMathematicalSeries[T]) IsNormallyDistributed() bool {
+ return false
+}
+
+func (ims *InfiniteMathematicalSeries[T]) Size() int {
+ return len(ims.elements)
+}
+
+func (ims *InfiniteMathematicalSeries[T]) Values() []T {
+ return ims.elements
+}
+
+func (ims *InfiniteMathematicalSeries[T]) Percentile(p int) T {
+ return calculatePercentile(ims.elements, p)
+}
+
type CappedMathematicalSeries[T constraints.Float | constraints.Integer] struct {
elements_count int
elements []T
@@ -58,11 +166,10 @@ func (ma *CappedMathematicalSeries[T]) AddElement(measurement T) {
}
func (ma *CappedMathematicalSeries[T]) CalculateAverage() float64 {
- total := T(0)
- for i := 0; i < ma.elements_count; i++ {
- total += ma.elements[i]
- }
- return float64(total) / float64(ma.divisor.Value())
+ // If we do not yet have all the values, then we know that the values
+ // exist between 0 and ma.divisor.Value(). If we do have all the values,
+ // we know that they, too, exist between 0 and ma.divisor.Value().
+ return calculateAverage(ma.elements[0:ma.divisor.Value()])
}
func (ma *CappedMathematicalSeries[T]) AllSequentialIncreasesLessThan(limit float64) (_ bool, maximumSequentialIncrease float64) {
@@ -160,10 +267,9 @@ func (ma *CappedMathematicalSeries[T]) Size() int {
return len(ma.elements)
}
-func (ma *CappedMathematicalSeries[T]) Percentile(p int) (result T) {
- result = T(0)
+func (ma *CappedMathematicalSeries[T]) Percentile(p int) T {
if p < 0 || p > 100 {
- return
+ return 0
}
// Because we need to sort the list to perform the percentile calculation,
@@ -172,8 +278,5 @@ func (ma *CappedMathematicalSeries[T]) Percentile(p int) (result T) {
kopy := make([]T, len(ma.elements))
copy(kopy, ma.elements)
- sort.Slice(kopy, func(l int, r int) bool { return kopy[l] < kopy[r] })
- pindex := int64((float64(p) / float64(100)) * float64(ma.elements_count))
- result = kopy[pindex]
- return
+ return calculatePercentile(kopy, p)
}
diff --git a/ms/ms_test.go b/ms/ms_test.go
index 988b647..d93102f 100644
--- a/ms/ms_test.go
+++ b/ms/ms_test.go
@@ -7,15 +7,106 @@ import (
"github.com/network-quality/goresponsiveness/utilities"
)
-func Test_TooFewInstantsSequentialIncreasesLessThanAlwaysFalse(test *testing.T) {
+func Test_InfiniteValues(test *testing.T) {
+ series := NewInfiniteMathematicalSeries[float64]()
+ shouldMatch := make([]float64, 0)
+ previous := float64(1.0)
+ for _ = range utilities.Iota(1, 80) {
+ previous *= 1.059
+ series.AddElement(float64(previous))
+ shouldMatch = append(shouldMatch, previous)
+ }
+
+ if !reflect.DeepEqual(shouldMatch, series.Values()) {
+ test.Fatalf("Values() on infinite mathematical series does not work.")
+ }
+}
+func Test_InfiniteSequentialIncreasesAlwaysLessThan(test *testing.T) {
+ series := NewInfiniteMathematicalSeries[float64]()
+ previous := float64(1.0)
+ for _ = range utilities.Iota(1, 80) {
+ previous *= 1.059
+ series.AddElement(float64(previous))
+ }
+ if islt, maxSeqIncrease := series.AllSequentialIncreasesLessThan(6.0); !islt {
+ test.Fatalf("(infinite) Sequential increases are not always less than 6.0 (%f).", maxSeqIncrease)
+ }
+}
+func Test_CappedTooFewInstantsSequentialIncreasesLessThanAlwaysFalse(test *testing.T) {
series := NewCappedMathematicalSeries[float64](500)
series.AddElement(0.0)
if islt, _ := series.AllSequentialIncreasesLessThan(6.0); islt {
- test.Fatalf("Too few instants should always yield false when asking if sequential increases are less than a value.")
+ test.Fatalf("(infinite) 0 elements in a series should always yield false when asking if sequential increases are less than a value.")
+ }
+}
+
+func Test_Infinite_degenerate_percentile_too_high(test *testing.T) {
+ series := NewInfiniteMathematicalSeries[int]()
+ if series.Percentile(101) != 0 {
+ test.Fatalf("(infinite) Series percentile of 101 failed.")
+ }
+}
+func Test_Infinite_degenerate_percentile_too_low(test *testing.T) {
+ series := NewInfiniteMathematicalSeries[int]()
+ if series.Percentile(-1) != 0 {
+ test.Fatalf("(infinite) Series percentile of -1 failed.")
+ }
+}
+func Test_Infinite90_percentile(test *testing.T) {
+ series := NewInfiniteMathematicalSeries[int]()
+ series.AddElement(10)
+ series.AddElement(9)
+ series.AddElement(8)
+ series.AddElement(7)
+ series.AddElement(6)
+ series.AddElement(5)
+ series.AddElement(4)
+ series.AddElement(3)
+ series.AddElement(2)
+ series.AddElement(1)
+
+ if series.Percentile(90) != 10 {
+ test.Fatalf("(infinite) Series 90th percentile of 0 ... 10 failed: Expected 10 got %v.", series.Percentile(90))
+ }
+}
+
+func Test_Infinite90_percentile_reversed(test *testing.T) {
+ series := NewInfiniteMathematicalSeries[int64]()
+ series.AddElement(1)
+ series.AddElement(2)
+ series.AddElement(3)
+ series.AddElement(4)
+ series.AddElement(5)
+ series.AddElement(6)
+ series.AddElement(7)
+ series.AddElement(8)
+ series.AddElement(9)
+ series.AddElement(10)
+
+ if series.Percentile(90) != 10 {
+ test.Fatalf("(infinite) Series 90th percentile of 0 ... 10 failed: Expected 10 got %v.", series.Percentile(90))
+ }
+}
+
+func Test_Infinite50_percentile_jumbled(test *testing.T) {
+ series := NewInfiniteMathematicalSeries[int64]()
+ series.AddElement(7)
+ series.AddElement(2)
+ series.AddElement(15)
+ series.AddElement(27)
+ series.AddElement(5)
+ series.AddElement(52)
+ series.AddElement(18)
+ series.AddElement(23)
+ series.AddElement(11)
+ series.AddElement(12)
+
+ if series.Percentile(50) != 15 {
+ test.Fatalf("(infinite) Series 50 percentile of a jumble of numbers failed: Expected 15 got %v.", series.Percentile(50))
}
}
-func Test_SequentialIncreasesAlwaysLessThan(test *testing.T) {
+func Test_CappedSequentialIncreasesAlwaysLessThan(test *testing.T) {
series := NewCappedMathematicalSeries[float64](40)
previous := float64(1.0)
for _ = range utilities.Iota(1, 80) {
@@ -27,7 +118,7 @@ func Test_SequentialIncreasesAlwaysLessThan(test *testing.T) {
}
}
-func Test_SequentialIncreasesAlwaysLessThanWithWraparound(test *testing.T) {
+func Test_CappedSequentialIncreasesAlwaysLessThanWithWraparound(test *testing.T) {
series := NewCappedMathematicalSeries[float64](20)
previous := float64(1.0)
for range utilities.Iota(1, 20) {
@@ -47,7 +138,7 @@ func Test_SequentialIncreasesAlwaysLessThanWithWraparound(test *testing.T) {
}
}
-func Test_SequentialIncreasesAlwaysLessThanWithWraparoundInverse(test *testing.T) {
+func Test_CappedSequentialIncreasesAlwaysLessThanWithWraparoundInverse(test *testing.T) {
series := NewCappedMathematicalSeries[float64](20)
previous := float64(1.0)
for range utilities.Iota(1, 20) {
@@ -67,7 +158,7 @@ func Test_SequentialIncreasesAlwaysLessThanWithWraparoundInverse(test *testing.T
}
}
-func Test_StandardDeviationCalculation(test *testing.T) {
+func Test_CappedStandardDeviationCalculation(test *testing.T) {
series := NewCappedMathematicalSeries[float64](5)
// 5.7, 1.0, 8.6, 7.4, 2.2
series.AddElement(5.7)
@@ -83,7 +174,7 @@ func Test_StandardDeviationCalculation(test *testing.T) {
}
}
-func Test_RotatingValues(test *testing.T) {
+func Test_CappedRotatingValues(test *testing.T) {
series := NewCappedMathematicalSeries[int](5)
series.AddElement(1)
@@ -99,7 +190,7 @@ func Test_RotatingValues(test *testing.T) {
test.Fatalf("Adding values does not properly erase earlier values.")
}
}
-func Test_Size(test *testing.T) {
+func Test_CappedSize(test *testing.T) {
series := NewCappedMathematicalSeries[int](5)
series.AddElement(1)
@@ -116,19 +207,19 @@ func Test_Size(test *testing.T) {
}
}
-func Test_degenerate_percentile_too_high(test *testing.T) {
+func Test_Capped_degenerate_percentile_too_high(test *testing.T) {
series := NewCappedMathematicalSeries[int](21)
if series.Percentile(101) != 0 {
test.Fatalf("Series percentile of 101 failed.")
}
}
-func Test_degenerate_percentile_too_low(test *testing.T) {
+func Test_Capped_degenerate_percentile_too_low(test *testing.T) {
series := NewCappedMathematicalSeries[int](21)
if series.Percentile(-1) != 0 {
test.Fatalf("Series percentile of -1 failed.")
}
}
-func Test_90_percentile(test *testing.T) {
+func Test_Capped90_percentile(test *testing.T) {
series := NewCappedMathematicalSeries[int](10)
series.AddElement(10)
series.AddElement(9)
@@ -146,7 +237,7 @@ func Test_90_percentile(test *testing.T) {
}
}
-func Test_90_percentile_reversed(test *testing.T) {
+func Test_Capped90_percentile_reversed(test *testing.T) {
series := NewCappedMathematicalSeries[int64](10)
series.AddElement(1)
series.AddElement(2)
@@ -164,7 +255,7 @@ func Test_90_percentile_reversed(test *testing.T) {
}
}
-func Test_50_percentile_jumbled(test *testing.T) {
+func Test_Capped50_percentile_jumbled(test *testing.T) {
series := NewCappedMathematicalSeries[int64](10)
series.AddElement(7)
series.AddElement(2)