├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── go.mod ├── ma.go ├── ma_concurrent.go ├── ma_test.go └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | .idea 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.12" 5 | 6 | before_install: 7 | - "wget https://github.com/dominikh/go-tools/releases/download/2019.1.1/staticcheck_linux_amd64 && sudo mv staticcheck_linux_amd64 /usr/bin/staticcheck && sudo chmod +x /usr/bin/staticcheck" 8 | 9 | script: "env GO111MODULE=on ./test.sh" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # golang-moving-average [![Build Status](https://travis-ci.org/RobinUS2/golang-moving-average.svg?branch=master)](https://travis-ci.org/RobinUS2/golang-moving-average) 2 | Moving average implementation for Go. View [the documentation](https://godoc.org/github.com/RobinUS2/golang-moving-average). 3 | 4 | ## Usage 5 | ``` 6 | import "github.com/RobinUS2/golang-moving-average" 7 | 8 | ma := movingaverage.New(5) // 5 is the window size 9 | ma.Add(10) 10 | ma.Add(15) 11 | ma.Add(20) 12 | ma.Add(1) 13 | ma.Add(1) 14 | ma.Add(5) // This one will effectively overwrite the first value (10 in this example) 15 | avg := ma.Avg() // Will return 8.4 16 | ``` 17 | 18 | ## Concurrency 19 | By default the library is not thread safe. It is however possible to wrap the object in a thread safe manner that can be 20 | used concurrently from many routines: 21 | ``` 22 | ma := movingaverage.Concurrent(movingaverage.New(5)) // concurrent safe version 23 | ma.Add(10) 24 | avg := ma.Avg() // Will return 10.0 25 | ``` 26 | 27 | ## Min/Max/Count 28 | Basic operations are possible: 29 | ``` 30 | ma := movingaverage.New(5) // 5 is the window size 31 | min, err := ma.Min() // min will return lowest value, error is set if there's no values yet 32 | max, err := ma.Max() // max will return highest value, error is set if there's no values yet 33 | count := ma.Count() // count will return number of filled slots 34 | ``` 35 | 36 | ## Partially used windows 37 | In case you define a window of let's say 5 and only put in 2 values, the average will be based on those 2 values. 38 | 39 | Window 5 - Values: 2, 2 - Average: 2 (not 0.8) 40 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/RobinUS2/golang-moving-average 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /ma.go: -------------------------------------------------------------------------------- 1 | package movingaverage 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | ) 7 | 8 | // @author Robin Verlangen 9 | // Moving average implementation for Go 10 | 11 | var errNoValues = errors.New("no values") 12 | 13 | type MovingAverage struct { 14 | Window int 15 | values []float64 16 | valPos int 17 | slotsFilled bool 18 | ignoreNanValues bool 19 | ignoreInfValues bool 20 | } 21 | 22 | func (ma *MovingAverage) SetIgnoreInfValues(ignoreInfValues bool) { 23 | ma.ignoreInfValues = ignoreInfValues 24 | } 25 | 26 | func (ma *MovingAverage) SetIgnoreNanValues(ignoreNanValues bool) { 27 | ma.ignoreNanValues = ignoreNanValues 28 | } 29 | 30 | func (ma *MovingAverage) Avg() float64 { 31 | var sum = float64(0) 32 | values := ma.filledValues() 33 | if values == nil { 34 | return 0 35 | } 36 | n := len(values) 37 | for _, value := range values { 38 | sum += value 39 | } 40 | 41 | // Finalize average and return 42 | avg := sum / float64(n) 43 | return avg 44 | } 45 | 46 | func (ma *MovingAverage) filledValues() []float64 { 47 | var c = ma.Window - 1 48 | 49 | // Are all slots filled? If not, ignore unused 50 | if !ma.slotsFilled { 51 | c = ma.valPos - 1 52 | if c < 0 { 53 | // Empty register 54 | return nil 55 | } 56 | } 57 | return ma.values[0 : c+1] 58 | } 59 | 60 | func (ma *MovingAverage) Add(values ...float64) { 61 | for _, val := range values { 62 | // ignore NaN? 63 | if ma.ignoreNanValues && math.IsNaN(val) { 64 | continue 65 | } 66 | 67 | // ignore Inf? 68 | if ma.ignoreInfValues && math.IsInf(val, 0) { 69 | continue 70 | } 71 | 72 | // Put into values array 73 | ma.values[ma.valPos] = val 74 | 75 | // Increment value position 76 | ma.valPos = (ma.valPos + 1) % ma.Window 77 | 78 | // Did we just go back to 0, effectively meaning we filled all registers? 79 | if !ma.slotsFilled && ma.valPos == 0 { 80 | ma.slotsFilled = true 81 | } 82 | } 83 | } 84 | 85 | func (ma *MovingAverage) SlotsFilled() bool { 86 | return ma.slotsFilled 87 | } 88 | 89 | func (ma *MovingAverage) Values() []float64 { 90 | return ma.filledValues() 91 | } 92 | 93 | func (ma *MovingAverage) Count() int { 94 | return len(ma.Values()) 95 | } 96 | 97 | func (ma *MovingAverage) Max() (float64, error) { 98 | best := math.MaxFloat64 * -1 99 | values := ma.filledValues() 100 | if values == nil { 101 | return 0, errNoValues 102 | } 103 | for _, value := range values { 104 | if value > best { 105 | best = value 106 | } 107 | } 108 | return best, nil 109 | } 110 | 111 | func (ma *MovingAverage) Min() (float64, error) { 112 | if !ma.slotsFilled && ma.valPos == 0 { 113 | return 0, errNoValues 114 | } 115 | best := math.MaxFloat64 116 | values := ma.filledValues() 117 | for _, value := range values { 118 | if value < best { 119 | best = value 120 | } 121 | } 122 | return best, nil 123 | } 124 | 125 | func New(window int) *MovingAverage { 126 | return &MovingAverage{ 127 | Window: window, 128 | values: make([]float64, window), 129 | valPos: 0, 130 | slotsFilled: false, 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /ma_concurrent.go: -------------------------------------------------------------------------------- 1 | package movingaverage 2 | 3 | import "sync" 4 | 5 | type ConcurrentMovingAverage struct { 6 | ma *MovingAverage 7 | mux sync.RWMutex 8 | } 9 | 10 | func Concurrent(ma *MovingAverage) *ConcurrentMovingAverage { 11 | return &ConcurrentMovingAverage{ 12 | ma: ma, 13 | } 14 | } 15 | 16 | func (c *ConcurrentMovingAverage) Add(values ...float64) { 17 | c.mux.Lock() 18 | c.ma.Add(values...) 19 | c.mux.Unlock() 20 | } 21 | 22 | func (c *ConcurrentMovingAverage) Avg() float64 { 23 | c.mux.RLock() 24 | defer c.mux.RUnlock() 25 | return c.ma.Avg() 26 | } 27 | 28 | func (c *ConcurrentMovingAverage) Min() (float64, error) { 29 | c.mux.RLock() 30 | defer c.mux.RUnlock() 31 | return c.ma.Min() 32 | } 33 | 34 | func (c *ConcurrentMovingAverage) Max() (float64, error) { 35 | c.mux.RLock() 36 | defer c.mux.RUnlock() 37 | return c.ma.Max() 38 | } 39 | 40 | func (c *ConcurrentMovingAverage) Count() int { 41 | c.mux.RLock() 42 | defer c.mux.RUnlock() 43 | return c.ma.Count() 44 | } 45 | 46 | func (c *ConcurrentMovingAverage) Values() []float64 { 47 | c.mux.RLock() 48 | defer c.mux.RUnlock() 49 | return c.ma.Values() 50 | } 51 | 52 | func (c *ConcurrentMovingAverage) SlotsFilled() bool { 53 | c.mux.RLock() 54 | defer c.mux.RUnlock() 55 | return c.ma.SlotsFilled() 56 | } 57 | -------------------------------------------------------------------------------- /ma_test.go: -------------------------------------------------------------------------------- 1 | package movingaverage 2 | 3 | // @author Robin Verlangen 4 | // Test the moving average for Go package 5 | 6 | import ( 7 | "math" 8 | "sync" 9 | "testing" 10 | ) 11 | 12 | func TestMovingAverage(t *testing.T) { 13 | a := New(5) 14 | if a.Avg() != 0 { 15 | t.Error("expected 0", a.Avg()) 16 | } 17 | 18 | if a.SlotsFilled() { 19 | t.Error("should not be full yet") 20 | } 21 | 22 | a.Add(2) 23 | if a.Avg() < 1.999 || a.Avg() > 2.001 { 24 | t.Fail() 25 | } 26 | a.Add(4) 27 | a.Add(2) 28 | if a.Avg() < 2.665 || a.Avg() > 2.667 { 29 | t.Fail() 30 | } 31 | a.Add(4) 32 | a.Add(2) 33 | if a.Avg() < 2.799 || a.Avg() > 2.801 { 34 | t.Fail() 35 | } 36 | 37 | if !a.SlotsFilled() { 38 | t.Error("should be full") 39 | } 40 | 41 | // This one will go into the first slot again 42 | // evicting the first value 43 | a.Add(10) 44 | if a.Avg() < 4.399 || a.Avg() > 4.401 { 45 | t.Fail() 46 | } 47 | 48 | // test variadic add 49 | a.Add(2, 4) 50 | 51 | // get values 52 | values := a.Values() 53 | if len(values) != 5 { 54 | t.Error() 55 | } 56 | } 57 | 58 | func TestNaN(t *testing.T) { 59 | a := New(5) 60 | a.Add(1) 61 | a.Add(math.NaN()) 62 | if !math.IsNaN(a.Avg()) { 63 | t.Error() 64 | } 65 | } 66 | 67 | func TestNaNIgnore(t *testing.T) { 68 | a := New(5) 69 | a.SetIgnoreNanValues(true) 70 | a.Add(1) 71 | a.Add(math.NaN()) 72 | if math.IsNaN(a.Avg()) { 73 | t.Error() 74 | } 75 | if a.Avg() != 1 { 76 | t.Error(a.Avg()) 77 | } 78 | } 79 | 80 | func TestInf(t *testing.T) { 81 | a := New(5) 82 | a.Add(1) 83 | a.Add(math.Inf(1)) 84 | if !math.IsInf(a.Avg(), 0) { 85 | t.Error() 86 | } 87 | } 88 | 89 | func TestInfIgnore(t *testing.T) { 90 | a := New(5) 91 | a.SetIgnoreInfValues(true) 92 | a.Add(1) 93 | a.Add(math.Inf(1)) 94 | if math.IsInf(a.Avg(), 0) { 95 | t.Error() 96 | } 97 | if a.Avg() != 1 { 98 | t.Error(a.Avg()) 99 | } 100 | } 101 | 102 | func TestMax(t *testing.T) { 103 | a := New(5) 104 | if max, err := a.Max(); max != 0 || err == nil { 105 | t.Error(max, err) 106 | } 107 | a.Add(10) 108 | if max, err := a.Max(); max != 10 || err != nil { 109 | t.Error(max, err) 110 | } 111 | a.Add(100, 10000, 1000) 112 | if max, err := a.Max(); max != 10000 || err != nil { 113 | t.Error(max, err) 114 | } 115 | } 116 | 117 | func TestMin(t *testing.T) { 118 | a := New(5) 119 | if min, err := a.Min(); min != 0 || err == nil { 120 | t.Error(min, err) 121 | } 122 | a.Add(10) 123 | if min, err := a.Min(); min != 10 || err != nil { 124 | t.Error(min, err) 125 | } 126 | a.Add(100, 10000, 1000) 127 | if min, err := a.Min(); min != 10 || err != nil { 128 | t.Error(min, err) 129 | } 130 | } 131 | 132 | func TestCount(t *testing.T) { 133 | a := New(5) 134 | if a.Count() != 0 { 135 | t.Error(a.Count()) 136 | } 137 | a.Add(5) 138 | if a.Count() != 1 { 139 | t.Error(a.Count()) 140 | } 141 | a.Add(3, 6) 142 | if a.Count() != 3 { 143 | t.Error(a.Count()) 144 | } 145 | a.Add(1, 2, 3, 4, 5) 146 | if a.Count() != 5 { 147 | t.Error(a.Count()) 148 | } 149 | } 150 | 151 | func TestConcurrent(t *testing.T) { 152 | // this test needs to be run with -race flag 153 | a := Concurrent(New(5)) 154 | 155 | const numRoutines = 5 156 | wg := sync.WaitGroup{} 157 | wg.Add(numRoutines) 158 | for i := 0; i < numRoutines; i++ { 159 | go func() { 160 | for n := 0; n < 10; n++ { 161 | a.Add(float64(n)) 162 | } 163 | a.Avg() 164 | a.Min() 165 | a.Max() 166 | a.Count() 167 | a.Values() 168 | a.SlotsFilled() 169 | wg.Done() 170 | }() 171 | } 172 | wg.Wait() 173 | } 174 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | go fmt ./... 4 | go vet ./... 5 | staticcheck ./... 6 | go test -race -cover ./... 7 | --------------------------------------------------------------------------------