-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathmedian.go
135 lines (111 loc) · 2.54 KB
/
median.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
Copyright 2020 Binh Nguyen
Licensed under terms of MIT license (see LICENSE)
*/
package tago
import (
"fmt"
"log"
"math/rand"
"time"
)
/*
Median returns the median value of the last n values.
Where:
* _n_ - number of probes in observation.
# Parameters
* _n_ - number of periods (integer greater than 0)
# Example
```
```
*/
type Median struct {
// number of periods (must be an integer greater than 0)
n int
// internal parameters for calculations
index int
// slice of data needed for calculation
data []float64
}
// NewMedian creates a new Median with the given number of periods
// Example: NewMedian(9)
func NewMedian(n int) (*Median, error) {
if n <= 0 {
return nil, ErrInvalidParameters
}
return &Median{
n: n,
index: 0,
data: make([]float64, 0, n),
}, nil
}
// Next takes the next input and returns the next Median value
func (m *Median) Next(input float64) float64 {
// add input to data
if len(m.data) < m.n {
m.data = append(m.data, input)
} else {
m.index = m.index % m.n
m.data[m.index] = input
}
m.index++
return quickselectMedian(m.data, defaultPivotFunc)
}
// Reset resets the indicators to a clean state
func (m *Median) Reset() {
m.index = 0
m.data = make([]float64, 0, m.n)
}
func (m *Median) String() string {
return fmt.Sprintf("Median(%d)", m.n)
}
func quickselect(l []float64, k int, pivotFn func([]float64) float64) float64 {
/*
Select the kth element in l (0 based)
:param l: List of numerics
:param k: Index
:param pivot_fn: Function to choose a pivot, defaults to random.choice
:return: The kth element of l
*/
if len(l) == 1 {
if k != 0 {
// only here for completeness. should never happen
log.Fatal("invalid")
}
return l[0]
}
pivot := pivotFn(l)
var lows, highs, pivots []float64
for _, v := range l {
if v < pivot {
lows = append(lows, v)
continue
}
if v > pivot {
highs = append(highs, v)
continue
}
if v == pivot {
pivots = append(pivots, v)
continue
}
}
if k < len(lows) {
return quickselect(lows, k, pivotFn)
} else if k < len(lows)+len(pivots) {
// We got lucky and guessed the median
return pivots[0]
} else {
return quickselect(highs, k-len(lows)-len(pivots), pivotFn)
}
}
func quickselectMedian(l []float64, pivotFn func([]float64) float64) float64 {
if len(l)%2 == 1 {
return quickselect(l, len(l)/2, pivotFn)
}
return 0.5 * (quickselect(l, len(l)/2-1, pivotFn) + quickselect(l, len(l)/2, pivotFn))
}
func defaultPivotFunc(l []float64) float64 {
rand.Seed(time.Now().Unix())
return l[rand.Intn(len(l))]
}