
Computing thesimple moving average of a series of numbers.
Create astateful function/class/instance that takes a period and returns a routine that takes a number as argument and returns a simple moving average of its arguments so far.
A simple moving average is a method for computing an average of a stream of numbers by only averaging the last P numbers from the stream, where P is known as the period.
It can be implemented by calling an initialing routine with P as its argument, I(P), which should then return a routine that when called with individual, successive members of a stream of numbers, computes the mean of (up to), the last P of them, lets call this SMA().
The word stateful in the task description refers to the need for SMA() to remember certain information between calls to it:
Stateful also means that successive calls to I(), the initializer, should return separate routines that do not share saved state so they could be used on two independent streams of data.
Pseudo-code for an implementation of SMA is:
function SMA(number: N): stateful integer: P stateful list: stream number: average stream.append_last(N) if stream.length() > P: # Only average the last P elements of the stream stream.delete_first() if stream.length() == 0: average = 0 else: average = sum( stream.values() ) / stream.length() return average
| ||||||||||||||||||||||||||||||||||||||||||||||
T SMA [Float] data sum = 0.0 index = 0 n_filled = 0 Int period F (period) .period = period .data = [0.0] * period F add(v) .sum += v - .data[.index] .data[.index] = v .index = (.index + 1) % .period .n_filled = min(.period, .n_filled + 1) R .sum / .n_filledV sma3 = SMA(3)V sma5 = SMA(5)L(e) [1, 2, 3, 4, 5, 5, 4, 3, 2, 1] print(‘Added #., sma(3) = #.6, sma(5) = #.6’.format(e, sma3.add(e), sma5.add(e)))
Added 1, sma(3) = 1.000000, sma(5) = 1.000000Added 2, sma(3) = 1.500000, sma(5) = 1.500000Added 3, sma(3) = 2.000000, sma(5) = 2.000000Added 4, sma(3) = 3.000000, sma(5) = 2.500000Added 5, sma(3) = 4.000000, sma(5) = 3.000000Added 5, sma(3) = 4.666667, sma(5) = 3.800000Added 4, sma(3) = 4.666667, sma(5) = 4.200000Added 3, sma(3) = 4.000000, sma(5) = 4.200000Added 2, sma(3) = 3.000000, sma(5) = 3.800000Added 1, sma(3) = 2.000000, sma(5) = 3.000000
* Averages/Simple moving average 26/08/2015AVGSMA CSECT USING AVGSMA,R12 LR R12,R15 ST R14,SAVER14 ZAP II,=P'0' ii=0 LA R7,1 LH R3,NA SRA R3,1 na/2LOOPA CR R7,R3 do i=1 to na/2 BH ELOOPA AP II,=P'1000' ii=ii+1000 LR R1,R7 i MH R1,=H'6' LA R4,A-6(R1) MVC 0(6,R4),II a(i)=ii LH R1,NA na SR R1,R7 -i MH R1,=H'6' LA R4,A(R1) MVC 0(6,R4),II a(na+1-i)=ii LA R7,1(R7) B LOOPAELOOPA XPRNT =CL30' n sma3 sma5 ',30 XPRNT =CL30' ----- ----------- -----------',30 LA R7,1 i=1LOOP CH R7,NA do i=1 to na BH RETURN STH R7,N n=i XDECO R7,C i MVC BUF+1(5),C+7 MVC P,=H'3' p=3 BAL R14,SMA MVC C(13),EDMASK ED C(13),SS sma(3,i) MVC BUF+7(11),C+2 MVC P,=H'5' p=5 BAL R14,SMA MVC C(13),EDMASK ED C(13),SS sma(5,i) MVC BUF+19(11),C+2 XPRNT BUF,30 output i,sma3,sma5 LA R7,1(R7) B LOOP* ***** sub sma(p,n) returns(PL6)SMA LH R5,N SH R5,P A R5,=F'1' ia=n-p+1 C R5,=F'1' BH OKIA LA R5,1 ia=1OKIA LH R6,NA ib=na CH R6,N BL OKIB LH R6,N ib=nOKIB ZAP II,=P'0' ii=0 ZAP SS,=P'0' ss=0 LR R3,R5 k=iaLOOPK CR R3,R6 do k=ia to ib BH ELOOPK AP II,=P'1' ii=ii+1 LR R1,R3 MH R1,=H'6' LA R4,A-6(R1) MVC C(6),0(R4) ss=ss+a(k) AP SS,C(6) LA R3,1(R3) B LOOPKELOOPK ZAP C,SS DP C,II ZAP SS,C(10) ss=ss/ii BR R14RETURN L R14,SAVER14 restore caller address XR R15,R15 BR R14SAVER14 DS FNN EQU 10NA DC AL2(NN)A DS (NN)PL6II DS PL6SS DS PL6P DS HN DS HC DS CL16BUF DC CL30' ' bufferEDMASK DC X'4020202020202021204B202020' CL13 YREGS END AVGSMA
n sma3 sma5 ----- ----------- ----------- 1 1.000 1.000 2 1.500 1.500 3 2.000 2.000 4 3.000 2.500 5 4.000 3.000 6 4.666 3.800 7 4.666 4.200 8 4.000 4.200 9 3.000 3.800 10 2.000 3.000
moving.ads:
genericMax_Elements:Positive;typeNumberisdigits<>;packageMovingisprocedureAdd_Number(N:Number);functionMoving_Average(N:Number)returnNumber;functionGet_AveragereturnNumber;endMoving;
moving.adb:
withAda.Containers.Vectors;packagebodyMovingisuseAda.Containers;packageNumber_Vectorsis newAda.Containers.Vectors(Element_Type=> Number,Index_Type=> Natural);Current_List:Number_Vectors.Vector:=Number_Vectors.Empty_Vector;procedureAdd_Number(N:Number)isbeginifNatural(Current_List.Length)>=Max_ElementsthenCurrent_List.Delete_First;endif;Current_List.Append(N);endAdd_Number;functionGet_AveragereturnNumberisAverage:Number:=0.0;procedureSum(Position:Number_Vectors.Cursor)isbeginAverage:=Average+Number_Vectors.Element(Position);endSum;beginCurrent_List.Iterate(Sum'Access);ifCurrent_List.Length>1thenAverage:=Average/Number(Current_List.Length);endif;returnAverage;endGet_Average;functionMoving_Average(N:Number)returnNumberisbeginAdd_Number(N);returnGet_Average;endMoving_Average;endMoving;
main.adb:
withAda.Text_IO;withMoving;procedureMainispackageThree_Averageis newMoving(Max_Elements=> 3,Number=> Float);packageFive_Averageis newMoving(Max_Elements=> 5,Number=> Float);beginforIin1..5loopAda.Text_IO.Put_Line("Inserting"&Integer'Image(I)&" into max-3: "&Float'Image(Three_Average.Moving_Average(Float(I))));Ada.Text_IO.Put_Line("Inserting"&Integer'Image(I)&" into max-5: "&Float'Image(Five_Average.Moving_Average(Float(I))));endloop;forIinreverse1..5loopAda.Text_IO.Put_Line("Inserting"&Integer'Image(I)&" into max-3: "&Float'Image(Three_Average.Moving_Average(Float(I))));Ada.Text_IO.Put_Line("Inserting"&Integer'Image(I)&" into max-5: "&Float'Image(Five_Average.Moving_Average(Float(I))));endloop;endMain;
Inserting 1 into max-3: 1.00000E+00Inserting 1 into max-5: 1.00000E+00Inserting 2 into max-3: 1.50000E+00Inserting 2 into max-5: 1.50000E+00Inserting 3 into max-3: 2.00000E+00Inserting 3 into max-5: 2.00000E+00Inserting 4 into max-3: 3.00000E+00Inserting 4 into max-5: 2.50000E+00Inserting 5 into max-3: 4.00000E+00Inserting 5 into max-5: 3.00000E+00Inserting 5 into max-3: 4.66667E+00Inserting 5 into max-5: 3.80000E+00Inserting 4 into max-3: 4.66667E+00Inserting 4 into max-5: 4.20000E+00Inserting 3 into max-3: 4.00000E+00Inserting 3 into max-5: 4.20000E+00Inserting 2 into max-3: 3.00000E+00Inserting 2 into max-5: 3.80000E+00Inserting 1 into max-3: 2.00000E+00Inserting 1 into max-5: 3.00000E+00
Note: This following code is a direct translation of theC code sample. It mimics C's var_list implementation, and so it probably isn't the most natural way of dong this actual task inALGOL 68.
MODE SMAOBJ = STRUCT( LONG REAL sma, LONG REAL sum, INT period, REF[]LONG REAL values, INT lv); MODE SMARESULT = UNION ( REF SMAOBJ # handle #, LONG REAL # sma #, REF[]LONG REAL # values #); MODE SMANEW = INT, SMAFREE = STRUCT(REF SMAOBJ free obj), SMAVALUES = STRUCT(REF SMAOBJ values obj), SMAADD = STRUCT(REF SMAOBJ add obj, LONG REAL v), SMAMEAN = STRUCT(REF SMAOBJ mean obj, REF[]LONG REAL v);MODE ACTION = UNION ( SMANEW, SMAFREE, SMAVALUES, SMAADD, SMAMEAN );PROC sma = ([]ACTION action)SMARESULT:( SMARESULT result; REF SMAOBJ obj; LONG REAL v; FOR i FROM LWB action TO UPB action DO CASE action[i] IN (SMANEW period):( # args: INT period # HEAP SMAOBJ handle; sma OF handle := 0.0; period OF handle := period; values OF handle := HEAP [period OF handle]LONG REAL; lv OF handle := 0; sum OF handle := 0.0; result := handle ), (SMAFREE args):( # args: REF SMAOBJ free obj # free obj OF args := REF SMAOBJ(NIL) # let the garbage collector do it's job # ), (SMAVALUES args):( # args: REF SMAOBJ values obj # result := values OF values obj OF args ), (SMAMEAN args):( # args: REF SMAOBJ mean obj # result := sma OF mean obj OF args ), (SMAADD args):( # args: REF SMAOBJ add obj, LONG REAL v # obj := add obj OF args; v := v OF args; IF lv OF obj < period OF obj THEN (values OF obj)[lv OF obj+:=1] := v; sum OF obj +:= v; sma OF obj := sum OF obj / lv OF obj ELSE sum OF obj -:= (values OF obj)[ 1+ lv OF obj MOD period OF obj]; sum OF obj +:= v; sma OF obj := sum OF obj / period OF obj; (values OF obj)[ 1+ lv OF obj MOD period OF obj ] := v; lv OF obj+:=1 FI; result := sma OF obj ) OUT SKIP ESAC OD; result);[]LONG REAL v = ( 1, 2, 3, 4, 5, 5, 4, 3, 2, 1 ); main: ( INT i; REF SMAOBJ h3 := ( sma(SMANEW(3)) | (REF SMAOBJ obj):obj ); REF SMAOBJ h5 := ( sma(SMANEW(5)) | (REF SMAOBJ obj):obj ); FOR i FROM LWB v TO UPB v DO printf(($"next number "g(0,6)", SMA_3 = "g(0,6)", SMA_5 = "g(0,6)l$, v[i], (sma(SMAADD(h3, v[i]))|(LONG REAL r):r), ( sma(SMAADD(h5, v[i])) | (LONG REAL r):r ) )) OD#; sma(SMAFREE(h3)); sma(SMAFREE(h5))#)
next number 1.000000, SMA_3 = 1.000000, SMA_5 = 1.000000next number 2.000000, SMA_3 = 1.500000, SMA_5 = 1.500000next number 3.000000, SMA_3 = 2.000000, SMA_5 = 2.000000next number 4.000000, SMA_3 = 3.000000, SMA_5 = 2.500000next number 5.000000, SMA_3 = 4.000000, SMA_5 = 3.000000next number 5.000000, SMA_3 = 4.666667, SMA_5 = 3.800000next number 4.000000, SMA_3 = 4.666667, SMA_5 = 4.200000next number 3.000000, SMA_3 = 4.000000, SMA_5 = 4.200000next number 2.000000, SMA_3 = 3.000000, SMA_5 = 3.800000next number 1.000000, SMA_3 = 2.000000, SMA_5 = 3.000000
ahk forum:discussionFor Integers:
MsgBox%MovingAverage(5,3) ; 5, averaging length <- 3MsgBox%MovingAverage(1) ; 3MsgBox%MovingAverage(-3) ; 1MsgBox%MovingAverage(8) ; 2MsgBox%MovingAverage(7) ; 4MovingAverage(x,len=""){ ; for integers (faster)StaticStaticsum:=0,n:=0,m:=10 ; default averaging length = 10If(len>"") ; non-blank 2nd parameter: set length, resetsum:=n:=i:=0,m:=lenIf(n<m) ; until the buffer is not fullsum+=x,n++ ; keep summing dataElse ; when buffer is fullsum+=x-v%i% ; add new, subtract oldestv%i%:=x,i:=mod(i+1,m) ; remember last m inputs, cycle insertion pointReturnsum/n}
For floating point numbers:
MovingAverage(x,len=""){ ; for floating point numbersStaticStaticn:=0,m:=10 ; default averaging length = 10If(len>"") ; non-blank 2nd parameter: set length, resetn:=i:=0,m:=lenn+=n<m,sum:=0v%i%:=x,i:=mod(i+1,m) ; remember last m inputs, cycle insertion pointLoop%n% ; recompute sum to avoid error accumulationj:=A_Index-1,sum+=v%j%Returnsum/n}
#!/usr/bin/awk -f# Moving average over the first column of a data fileBEGIN{P=5;}{x=$1;i=NR%P;MA+=(x-Z[i])/P;Z[i]=x;printMA;}
importballerina/io;functionsma(intperiod)returns(function(float)returnsfloat){inti=0;floatsum=0.0;float[]storage=[];returnfunction(floatinput)returnsfloat{ifstorage.length()<period{sum+=input;storage.push(input);}sum+=input-storage[i];storage[i]=input;i=(i+1)%period;returnsum/<float>storage.length();};}functionF(floatf,intsize,intprec)returnsstring{strings=f.toFixedString(prec);returnsize>=0?s.padStart(size):s.padEnd(size);}publicfunctionmain(){varsma3=sma(3);varsma5=sma(5);io:println(" x sma3 sma5");float[]rng=[1,2,3,4,5,5,4,3,2,1];foreachfloatxinrng{io:println(`${F(x, 5, 3)} ${F(sma3(x), 5, 3)} ${F(sma5(x), 5, 3)}`);}}
x sma3 sma51.000 1.000 1.0002.000 1.500 1.5003.000 2.000 2.0004.000 3.000 2.5005.000 4.000 3.0005.000 4.667 3.8004.000 4.667 4.2003.000 4.000 4.2002.000 3.000 3.8001.000 2.000 3.000
MAXPERIOD=10FORn=1TO5PRINT"Number = ";nTAB(12)" SMA3 = ";FNsma(n,3)TAB(30)" SMA5 = ";FNsma(n,5)NEXTFORn=5TO1STEP-1PRINT"Number = ";nTAB(12)" SMA3 = ";FNsma(n,3)TAB(30)" SMA5 = ";FNsma(n,5)NEXTENDDEFFNsma(number,period%)PRIVATEnums(),accum(),index%(),window%()DIMnums(MAXPERIOD,MAXPERIOD),accum(MAXPERIOD)DIMindex%(MAXPERIOD),window%(MAXPERIOD)accum(period%)+=number-nums(period%,index%(period%))nums(period%,index%(period%))=numberindex%(period%)=(index%(period%)+1)MODperiod%IFwindow%(period%)<period%window%(period%)+=1=accum(period%)/window%(period%)
Number = 1 SMA3 = 1 SMA5 = 1Number = 2 SMA3 = 1.5 SMA5 = 1.5Number = 3 SMA3 = 2 SMA5 = 2Number = 4 SMA3 = 3 SMA5 = 2.5Number = 5 SMA3 = 4 SMA5 = 3Number = 5 SMA3 = 4.66666667 SMA5 = 3.8Number = 4 SMA3 = 4.66666667 SMA5 = 4.2Number = 3 SMA3 = 4 SMA5 = 4.2Number = 2 SMA3 = 3 SMA5 = 3.8Number = 1 SMA3 = 2 SMA5 = 3
SMA takes moving average of a list, given the whole array.
SMA2 returns a stateful function which can be run on individual numbers of a stream.
SMA ← {(+´÷≠)¨(1↓𝕨↑↑𝕩)∾<˘𝕨↕𝕩}v ← (⊢∾⌽)1+↕5•Show 5 SMA vSMA2 ← { 𝕊 size: nums ← ⟨⟩ sum ← 0 { nums ∾↩ 𝕩 gb ← {(≠nums)≤size ? 0 ; a←⊑nums, nums↩1↓nums, a} sum +↩ 𝕩 - gb sum ÷ ≠nums }}fun ← SMA2 5Fun¨ v⟨ 1 1.5 2 2.5 3 3.8 4.2 4.2 3.8 3 ⟩⟨ 1 1.5 2 2.5 3 3.8 4.2 4.2 3.8 3 ⟩
( ( I = buffer . (new$=):?freshEmptyBuffer & ' ( buffer avg . ( avg = L S n . 0:?L:?S & whl ' ( !arg:%?n ?arg & !n+!S:?S & 1+!L:?L ) & (!L:0&0|!S*!L^-1) ) & (buffer=$freshEmptyBuffer) & !arg !(buffer.):?(buffer.) & ( !(buffer.):?(buffer.) [($arg) ? | ) & avg$!(buffer.) ) )& ( pad = len w . @(!arg:? [?len) & @(" ":? [!len ?w) & !w !arg )& I$3:(=?sma3)& I$5:(=?sma5)& 1 2 3 4 5 5 4 3 2 1:?K& whl ' ( !K:%?k ?K & out $ (str$(!k " - sma3:" pad$(sma3$!k) " sma5:" pad$(sma5$!k))) ));1 - sma3: 1 sma5: 12 - sma3: 3/2 sma5: 3/23 - sma3: 2 sma5: 24 - sma3: 3 sma5: 5/25 - sma3: 4 sma5: 35 - sma3: 14/3 sma5: 19/54 - sma3: 14/3 sma5: 21/53 - sma3: 4 sma5: 21/52 - sma3: 3 sma5: 19/51 - sma3: 2 sma5: 3
Object version
SMA = object.newSMA.init = { period | my.period = period my.list = [] my.average = 0}SMA.prototype.add = { num | true? my.list.length >= my.period { my.list.deq } my.list << num my.average = my.list.reduce(:+) / my.list.length}sma3 = SMA.new 3sma5 = SMA.new 5[1, 2, 3, 4, 5, 5, 4, 3, 2, 1].each { n | p n, " - SMA3: ", sma3.add(n), " SMA5: ", sma5.add(n)}Function version
sma = { period | list = [] { num | true? list.length >= period { list.deq } list << num list.reduce(:+) / list.length }}sma3 = sma 3sma5 = sma 5[1, 2, 3, 4, 5, 5, 4, 3, 2, 1].each { n | p n, " - SMA3: ", sma3(n), " SMA5: ", sma5(n)}1 - SMA3: 1 SMA5: 12 - SMA3: 1.5 SMA5: 1.53 - SMA3: 2 SMA5: 24 - SMA3: 3 SMA5: 2.55 - SMA3: 4 SMA5: 35 - SMA3: 4.6666666666667 SMA5: 3.84 - SMA3: 4.6666666666667 SMA5: 4.23 - SMA3: 4 SMA5: 4.22 - SMA3: 3 SMA5: 3.81 - SMA3: 2 SMA5: 3
#include<stdio.h>#include<stdlib.h>#include<stdarg.h>typedefstructsma_obj{doublesma;doublesum;intperiod;double*values;intlv;}sma_obj_t;typedefunionsma_result{sma_obj_t*handle;doublesma;double*values;}sma_result_t;enumAction{SMA_NEW,SMA_FREE,SMA_VALUES,SMA_ADD,SMA_MEAN};sma_result_tsma(enumActionaction,...){va_listvl;sma_result_tr;sma_obj_t*o;doublev;va_start(vl,action);switch(action){caseSMA_NEW:// args: int periodr.handle=malloc(sizeof(sma_obj_t));r.handle->sma=0.0;r.handle->period=va_arg(vl,int);r.handle->values=malloc(r.handle->period*sizeof(double));r.handle->lv=0;r.handle->sum=0.0;break;caseSMA_FREE:// args: sma_obj_t *handler.handle=va_arg(vl,sma_obj_t*);free(r.handle->values);free(r.handle);r.handle=NULL;break;caseSMA_VALUES:// args: sma_obj_t *handleo=va_arg(vl,sma_obj_t*);r.values=o->values;break;caseSMA_MEAN:// args: sma_obj_t *handleo=va_arg(vl,sma_obj_t*);r.sma=o->sma;break;caseSMA_ADD:// args: sma_obj_t *handle, double valueo=va_arg(vl,sma_obj_t*);v=va_arg(vl,double);if(o->lv<o->period){o->values[o->lv++]=v;o->sum+=v;o->sma=o->sum/o->lv;}else{o->sum-=o->values[o->lv%o->period];o->sum+=v;o->sma=o->sum/o->period;o->values[o->lv%o->period]=v;o->lv++;}r.sma=o->sma;break;}va_end(vl);returnr;}
doublev[]={1,2,3,4,5,5,4,3,2,1};intmain(){inti;sma_obj_t*h3=sma(SMA_NEW,3).handle;sma_obj_t*h5=sma(SMA_NEW,5).handle;for(i=0;i<sizeof(v)/sizeof(double);i++){printf("next number %lf, SMA_3 = %lf, SMA_5 = %lf\n",v[i],sma(SMA_ADD,h3,v[i]).sma,sma(SMA_ADD,h5,v[i]).sma);}sma(SMA_FREE,h3);sma(SMA_FREE,h5);return0;}
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;namespaceSMA{classProgram{staticvoidMain(string[]args){varnums=Enumerable.Range(1,5).Select(n=>(double)n);nums=nums.Concat(nums.Reverse());varsma3=SMA(3);varsma5=SMA(5);foreach(varninnums){Console.WriteLine("{0} (sma3) {1,-16} (sma5) {2,-16}",n,sma3(n),sma5(n));}}staticFunc<double,double>SMA(intp){Queue<double>s=newQueue<double>(p);return(x)=>{if(s.Count>=p){s.Dequeue();}s.Enqueue(x);returns.Average();};}}}
1 (sma3) 1 (sma5) 12 (sma3) 1.5 (sma5) 1.53 (sma3) 2 (sma5) 24 (sma3) 3 (sma5) 2.55 (sma3) 4 (sma5) 35 (sma3) 4.66666666666667 (sma5) 3.84 (sma3) 4.66666666666667 (sma5) 4.23 (sma3) 4 (sma5) 4.22 (sma3) 3 (sma5) 3.81 (sma3) 2 (sma5) 3
#include<iostream>#include<stddef.h>#include<assert.h>usingstd::cout;usingstd::endl;classSMA{public:SMA(unsignedintperiod):period(period),window(newdouble[period]),head(NULL),tail(NULL),total(0){assert(period>=1);}~SMA(){delete[]window;}// Adds a value to the average, pushing one out if nescessaryvoidadd(doubleval){// Special case: Initializationif(head==NULL){head=window;*head=val;tail=head;inc(tail);total=val;return;}// Were we already full?if(head==tail){// Fix total-cachetotal-=*head;// Make roominc(head);}// Write the value in the next spot.*tail=val;inc(tail);// Update our total-cachetotal+=val;}// Returns the average of the last P elements added to this SMA.// If no elements have been added yet, returns 0.0doubleavg()const{ptrdiff_tsize=this->size();if(size==0){return0;// No entries => 0 average}returntotal/(double)size;// Cast to double for floating point arithmetic}private:unsignedintperiod;double*window;// Holds the values to calculate the average of.// Logically, head is before taildouble*head;// Points at the oldest element we've stored.double*tail;// Points at the newest element we've stored.doubletotal;// Cache the total so we don't sum everything each time.// Bumps the given pointer up by one.// Wraps to the start of the array if needed.voidinc(double*&p){if(++p>=window+period){p=window;}}// Returns how many numbers we have stored.ptrdiff_tsize()const{if(head==NULL)return0;if(head==tail)returnperiod;return(period+tail-head)%period;}};intmain(intargc,char**argv){SMAfoo(3);SMAbar(5);intdata[]={1,2,3,4,5,5,4,3,2,1};for(int*itr=data;itr<data+10;itr++){foo.add(*itr);cout<<"Added "<<*itr<<" avg: "<<foo.avg()<<endl;}cout<<endl;for(int*itr=data;itr<data+10;itr++){bar.add(*itr);cout<<"Added "<<*itr<<" avg: "<<bar.avg()<<endl;}return0;}
This version uses a persistent queue to hold the most recentp values. Each function returned frominit-moving-average has its state in an atom holding a queue value.
(import'[clojure.langPersistentQueue])(defnenqueue-max[qpn](let[q(conjqn)](if(<=(countq)p)q(popq))))(defnavg[coll](/(reduce +coll)(countcoll)))(defninit-moving-avg[p](let[state(atomPersistentQueue/EMPTY)](fn[n](avg(swap!stateenqueue-maxpn)))))
I=(P) -># The cryptic name "I" follows the problem description;# it returns a function that computes a moving average# of successive values over the period P, using closure# variables to maintain state.cq=circular_queue(P)num_elems=0sum=0SMA=(n) ->sum+=nifnum_elems<Pcq.add(n)num_elems+=1sum/num_elemselseold=cq.replace(n)sum-=oldsum/Pcircular_queue=(n) -># queue that only ever stores up to n values;# Caller shouldn't call replace until n values# have been added.i=0arr=[]add:(elem) ->arr.pushelemreplace:(elem) -># return value whose age is "n"old_val=arr[i]arr[i]=elemi=(i+1)%nold_val# The output of the code below should convince you that# calling I multiple times returns functions with independent# state.sma3=I(3)sma7=I(7)sma11=I(11)foriin[1..10]console.logi,sma3(i),sma7(i),sma11(i)
> coffee moving_average.coffee 1 1 1 12 1.5 1.5 1.53 2 2 24 3 2.5 2.55 4 3 36 5 3.5 3.57 6 4 48 7 5 4.59 8 6 510 9 7 5.5
This implementation uses a circular list to store the numbers within the window; at the beginning of each iterationpointer refers to the list cell which holds the value just moving out of the window and to be replaced with the just-added value.
(defunsimple-moving-average(period&aux(sum0)(count0)(values(make-listperiod))(pointervalues))(setf(rest(lastvalues))values); construct circularity(lambda(n)(when(firstpointer)(decfsum(firstpointer))); subtract old value(incfsumn); add new value(incfcount)(setf(firstpointer)n)(setfpointer(restpointer)); advance pointer(/sum(mincountperiod))))
Use
(mapcar'(simple-moving-averageperiod)list-of-values)
defsma(n)Proc(Float64,Float64)a=Array(Float64).new->(x:Float64){a.shiftifa.size==na.pushxa.sum/a.size.to_f}endsma3,sma5=sma(3),sma(5)# Copied from the Ruby solution.(1.upto(5).to_a+5.downto(1).to_a).eachdo|n|printf"%d: sma3 = %.3f - sma5 = %.3f\n",n,sma3.call(n.to_f),sma5.call(n.to_f)end
1: sma3 = 1.000 - sma5 = 1.0002: sma3 = 1.500 - sma5 = 1.5003: sma3 = 2.000 - sma5 = 2.0004: sma3 = 3.000 - sma5 = 2.5005: sma3 = 4.000 - sma5 = 3.0005: sma3 = 4.667 - sma5 = 3.8004: sma3 = 4.667 - sma5 = 4.2003: sma3 = 4.000 - sma5 = 4.2002: sma3 = 3.000 - sma5 = 3.8001: sma3 = 2.000 - sma5 = 3.000
Currently thissma can't be @nogc because it allocates a closure on the heap. Some escape analysis could remove the heap allocation.
importstd.stdio,std.traits,std.algorithm;autosma(T,intperiod)()purenothrow@safe{T[period]data=0;Tsum=0;intindex,nFilled;return(inTv)nothrow@safe@nogc{sum+=-data[index]+v;data[index]=v;index=(index+1)%period;nFilled=min(period,nFilled+1);returnCommonType!(T,float)(sum)/nFilled;};}voidmain(){immutables3=sma!(int,3);immutables5=sma!(double,5);foreach(immutablee;[1,2,3,4,5,5,4,3,2,1])writefln("Added %d, sma(3) = %f, sma(5) = %f",e,s3(e),s5(e));}
Added 1, sma(3) = 1.000000, sma(5) = 1.000000Added 2, sma(3) = 1.500000, sma(5) = 1.500000Added 3, sma(3) = 2.000000, sma(5) = 2.000000Added 4, sma(3) = 3.000000, sma(5) = 2.500000Added 5, sma(3) = 4.000000, sma(5) = 3.000000Added 5, sma(3) = 4.666667, sma(5) = 3.800000Added 4, sma(3) = 4.666667, sma(5) = 4.200000Added 3, sma(3) = 4.000000, sma(5) = 4.200000Added 2, sma(3) = 3.000000, sma(5) = 3.800000Added 1, sma(3) = 2.000000, sma(5) = 3.000000
This version avoids the heap allocation of the closure keeping the data in the stack frame of the main function. Same output:
importstd.stdio,std.traits,std.algorithm;structSMA(T,intperiod){T[period]data=0;Tsum=0;intindex,nFilled;autoopCall(inTv)purenothrow@safe@nogc{sum+=-data[index]+v;data[index]=v;index=(index+1)%period;nFilled=min(period,nFilled+1);returnCommonType!(T,float)(sum)/nFilled;}}voidmain(){SMA!(int,3)s3;SMA!(double,5)s5;foreach(immutablee;[1,2,3,4,5,5,4,3,2,1])writefln("Added %d, sma(3) = %f, sma(5) = %f",e,s3(e),s5(e));}
To avoid the floating point approximations keep piling up and growing, the code could perform a periodic sum on the whole circular queue array.
Small variation of#Pascal.
programSimple_moving_average;{$APPTYPE CONSOLE}typeTMovingAverage=recordprivatebuffer:TArray<Double>;head:Integer;Capacity:Integer;Count:Integer;sum,fValue:Double;publicconstructorCreate(aCapacity:Integer);functionAdd(Value:Double):Double;procedureReset;propertyValue:DoublereadfValue;end;{ TMovingAverage }functionTMovingAverage.Add(Value:Double):Double;beginhead:=(head+1)modCapacity;sum:=sum+Value-buffer[head];buffer[head]:=Value;ifcount<capacitythenbegininc(Count);fValue:=sum/count;exit(fValue);end;fValue:=sum/Capacity;Result:=fValue;end;constructorTMovingAverage.Create(aCapacity:Integer);beginCapacity:=aCapacity;SetLength(buffer,aCapacity);Reset;end;procedureTMovingAverage.Reset;vari:integer;beginhead:=-1;Count:=0;sum:=0;fValue:=0;fori:=0toHigh(buffer)dobuffer[i]:=0;end;varavg3,avg5:TMovingAverage;i:Integer;beginavg3:=TMovingAverage.Create(3);avg5:=TMovingAverage.Create(5);fori:=1to5dobeginwrite('Inserting ',i,' into avg3 ',avg3.Add(i):0:4);writeln(' Inserting ',i,' into avg5 ',avg5.Add(i):0:4);end;fori:=5downto1dobeginwrite('Inserting ',i,' into avg3 ',avg3.Add(i):0:4);writeln(' Inserting ',i,' into avg5 ',avg5.Add(i):0:4);end;avg3.Reset;fori:=1to100000000doavg3.Add(i);writeln('100''000''000 insertions ',avg3.Value:0:4);Readln;end.
Inserting 1 into avg3 1.0000 Inserting 1 into avg5 1.0000Inserting 2 into avg3 1.5000 Inserting 2 into avg5 1.5000Inserting 3 into avg3 2.0000 Inserting 3 into avg5 2.0000Inserting 4 into avg3 3.0000 Inserting 4 into avg5 2.5000Inserting 5 into avg3 4.0000 Inserting 5 into avg5 3.0000Inserting 5 into avg3 4.6667 Inserting 5 into avg5 3.8000Inserting 4 into avg3 4.6667 Inserting 4 into avg5 4.2000Inserting 3 into avg3 4.0000 Inserting 3 into avg5 4.2000Inserting 2 into avg3 3.0000 Inserting 2 into avg5 3.8000Inserting 1 into avg3 2.0000 Inserting 1 into avg5 3.0000100'000'000 insertions 99999999.0000
func avg(xs) { var acc = 0.0 var c = 0 for x in xs { c += 1 acc += x } acc / c} func sma(p) { var s = [] x => { if s.Length() >= p { s.RemoveAt(0) } s.Insert(s.Length(), x) avg(s) };} var nums = Iterator.Concat(1.0..5.0, 5.0^-1.0..1.0)var sma3 = sma(3)var sma5 = sma(5) for n in nums { print("\(n)\t(sma3) \(sma3(n))\t(sma5) \(sma5(n))")}This implementation produces two (function) objects sharing state. It is idiomatic in E to separate input from output (read from write) rather than combining them into one object.
The structure is the same as the implementation ofStandard Deviation#E.
pragma.enable("accumulator")def makeMovingAverage(period) { def values := ([null] * period).diverge() var index := 0 var count := 0 def insert(v) { values[index] := v index := (index + 1) %% period count += 1 } /** Returns the simple moving average of the inputs so far, or null if there have been no inputs. */ def average() { if (count > 0) { return accum 0 for x :notNull in values { _ + x } / count.min(period) } } return [insert, average]}? for period in [3, 5] {> def [insert, average] := makeMovingAverage(period)> println(`Period $period:`)> for value in [1,2,3,4,5,5,4,3,2,1] {> insert(value)> println(value, "\t", average())> }> println()> }Period 3:11.021.532.043.054.054.66666666666666744.66666666666666734.023.012.0Period 5:11.021.532.042.553.053.844.234.223.813.0prefix sma_global p[] ind[] sum[] smpl[][] .func new p . p[] &= p ind[] &= 0 sum[] &= 0 smpl[][] &= [ ] return len p[].func get id x . ind[id] = (ind[id] + 1) mod1 p[id] ind = ind[id] if len smpl[id][] < ind len smpl[id][] ind else sum[id] -= smpl[id][ind] . sum[id] += x smpl[id][ind] = x return sum[id] / len smpl[id][].prefix#sma5 = sma_new 5sma3 = sma_new 3numfmt 4 2for v in [ 1 2 3 4 5 5 4 3 2 1 ] print sma_get sma3 v & " " & sma_get sma5 v.
1 11.50 1.50 2 2 3 2.50 4 34.67 3.804.67 4.20 4 4.20 3 3.80 2 3
(lib'tree);; queues operations(define(make-smap)(defineQ(queue(gensym)))(lambda(item)(q-pushQitem)(when(>(queue-lengthQ)p)(q-popQ))(//(for/sum((x(queue->listQ)))x)(queue-lengthQ))))
(define serie '(1 2 3 4 5 5 4 3 2 1))(define sma-3 (make-sma 3))(define sma-5 (make-sma 5))(for ((x serie)) (printf "%3d %10d %10d" x (sma-3 x) (sma-5 x))) 1 1 1 2 1.5 1.5 3 2 2 4 3 2.5 5 4 3 5 4.6667 3.8 4 4.6667 4.2 3 4 4.2 2 3 3.8 1 2 3
ELENA 6.x :
import system'routines;import system'collections;import extensions;class SMA{ object thePeriod; object theList; constructor new(period) { thePeriod := period; theList :=new List(); } append(n) { theList.append(n); var count := theList.Length; count => 0 : { ^0.0r } ! : { if (count > thePeriod) { theList.removeAt(0); count := thePeriod }; var sum := theList.summarize(Real.new()); ^ sum / count } }}public Program(){ var SMA3 := SMA.new(3); var SMA5 := SMA.new(5); for (int i := 1; i <= 5; i += 1) { Console.printPaddingRight(30, "sma3 + ", i, " = ", SMA3.append(i)); Console.printLine("sma5 + ", i, " = ", SMA5.append(i)) }; for (int i := 5; i >= 1; i -= 1) { Console.printPaddingRight(30, "sma3 + ", i, " = ", SMA3.append(i)); Console.printLine("sma5 + ", i, " = ", SMA5.append(i)) }; Console.readChar()}sma3 + 1 = 1.0 sma5 + 1 = 1.0sma3 + 2 = 1.5 sma5 + 2 = 1.5sma3 + 3 = 2.0 sma5 + 3 = 2.0sma3 + 4 = 3.0 sma5 + 4 = 2.5sma3 + 5 = 4.0 sma5 + 5 = 3.0sma3 + 5 = 4.666666666667 sma5 + 5 = 3.8sma3 + 4 = 4.666666666667 sma5 + 4 = 4.2sma3 + 3 = 4.0 sma5 + 3 = 4.2sma3 + 2 = 3.0 sma5 + 2 = 3.8sma3 + 1 = 2.0 sma5 + 1 = 3.0
The elixir program below generates an anonymous function with an embedded period `p`, which is used as the period of the simple moving average. The `run` function reads numeric input and passes it to the newly created anonymous function, and then "inspects" the result to STDOUT.
$catsimple-moving-avg.exs#!/usr/bin/env elixirdefmoduleMathdodefaverage([]),do:nildefaverage(enum)doEnum.sum(enum)/length(enum)endenddefmoduleSMAdodefsma(l,p\\10)doIO.puts("\nSimple moving average(period=#{p}):")Enum.chunk(l,p,1)|>Enum.map(&(%{"input":&1,"avg":Float.round(Math.average(&1),3)}))enddefmacrogen_func(p)doquotedofnl->SMA.sma(l,unquote(p))endendenddefread_numeric_inputdoIO.stream(:stdio,:line)|>Enum.map(&(String.split(&1,~r{\s+})))|>List.flatten()|>Enum.reject(&(is_nil(&1)||String.length(&1)==0))|>Enum.map(&(Integer.parse(&1)|>elem(0)))enddefrundosma_func_10=gen_func(10)sma_func_15=gen_func(15)numbers=read_numeric_inputsma_func_10.(numbers)|>IO.inspectsma_func_15.(numbers)|>IO.inspectendendSMA.run
#!/bin/bashelixir./simple-moving-avg.exs<<EOF1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 12 4 6 8 10 12 14 12 10 8 6 4 2EOF
The output is shown below, with the average, followed by the grouped input, forming the basis of each moving average.
$ ./simple-moving-avg.shSimple moving average(period=10):[%{avg: 5.3, input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 8]}, %{avg: 5.9, input: [2, 3, 4, 5, 6, 7, 8, 9, 8, 7]}, %{avg: 6.3, input: [3, 4, 5, 6, 7, 8, 9, 8, 7, 6]}, %{avg: 6.5, input: [4, 5, 6, 7, 8, 9, 8, 7, 6, 5]}, %{avg: 6.5, input: [5, 6, 7, 8, 9, 8, 7, 6, 5, 4]}, %{avg: 6.3, input: [6, 7, 8, 9, 8, 7, 6, 5, 4, 3]}, %{avg: 5.9, input: [7, 8, 9, 8, 7, 6, 5, 4, 3, 2]}, %{avg: 5.3, input: [8, 9, 8, 7, 6, 5, 4, 3, 2, 1]}, %{avg: 4.7, input: [9, 8, 7, 6, 5, 4, 3, 2, 1, 2]}, %{avg: 4.2, input: [8, 7, 6, 5, 4, 3, 2, 1, 2, 4]}, %{avg: 4.0, input: [7, 6, 5, 4, 3, 2, 1, 2, 4, 6]}, %{avg: 4.1, input: [6, 5, 4, 3, 2, 1, 2, 4, 6, 8]}, %{avg: 4.5, input: [5, 4, 3, 2, 1, 2, 4, 6, 8, 10]}, %{avg: 5.2, input: [4, 3, 2, 1, 2, 4, 6, 8, 10, 12]}, %{avg: 6.2, input: [3, 2, 1, 2, 4, 6, 8, 10, 12, 14]}, %{avg: 7.1, input: [2, 1, 2, 4, 6, 8, 10, 12, 14, 12]}, %{avg: 7.9, input: [1, 2, 4, 6, 8, 10, 12, 14, 12, 10]}, %{avg: 8.6, input: [2, 4, 6, 8, 10, 12, 14, 12, 10, 8]}, %{avg: 9.0, input: [4, 6, 8, 10, 12, 14, 12, 10, 8, 6]}, %{avg: 9.0, input: [6, 8, 10, 12, 14, 12, 10, 8, 6, 4]}, %{avg: 8.6, input: [8, 10, 12, 14, 12, 10, 8, 6, 4, 2]}]Simple moving average(period=15):[%{avg: 5.2, input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3]}, %{avg: 5.267, input: [2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2]}, %{avg: 5.2, input: [3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1]}, %{avg: 5.133, input: [4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1, 2]}, %{avg: 5.133, input: [5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1, 2, 4]}, %{avg: 5.2, input: [6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1, 2, 4, 6]}, %{avg: 5.333, input: [7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1, 2, 4, 6, 8]}, %{avg: 5.533, input: [8, 9, 8, 7, 6, 5, 4, 3, 2, 1, 2, 4, 6, 8, 10]}, %{avg: 5.8, input: [9, 8, 7, 6, 5, 4, 3, 2, 1, 2, 4, 6, 8, 10, 12]}, %{avg: 6.133, input: [8, 7, 6, 5, 4, 3, 2, 1, 2, 4, 6, 8, 10, 12, 14]}, %{avg: 6.4, input: [7, 6, 5, 4, 3, 2, 1, 2, 4, 6, 8, 10, 12, 14, 12]}, %{avg: 6.6, input: [6, 5, 4, 3, 2, 1, 2, 4, 6, 8, 10, 12, 14, 12, 10]}, %{avg: 6.733, input: [5, 4, 3, 2, 1, 2, 4, 6, 8, 10, 12, 14, 12, 10, 8]}, %{avg: 6.8, input: [4, 3, 2, 1, 2, 4, 6, 8, 10, 12, 14, 12, 10, 8, 6]}, %{avg: 6.8, input: [3, 2, 1, 2, 4, 6, 8, 10, 12, 14, 12, 10, 8, 6, 4]}, %{avg: 6.733, input: [2, 1, 2, 4, 6, 8, 10, 12, 14, 12, 10, 8, 6, 4, 2]}]main()->SMA3=sma(3),SMA5=sma(5),Ns=[1,2,3,4,5,5,4,3,2,1],lists:foreach(fun(N)->io:format("Added~b, sma(3) ->~f, sma(5) ->~f~n",[N,next(SMA3,N),next(SMA5,N)])end,Ns),stop(SMA3),stop(SMA5).sma(W)->{sma,spawn(?MODULE,loop,[W,[]])}.loop(Window,Numbers)->receive{_Pid,stop}->ok;{Pid,N}whenis_number(N)->caselength(Numbers)<Windowoftrue->Next=Numbers++[N];false->Next=tl(Numbers)++[N]end,Pid!{average,lists:sum(Next)/length(Next)},loop(Window,Next);_->okend.stop({sma,Pid})->Pid!{self(),stop},ok.next({sma,Pid},N)->Pid!{self(),N},receive{average,Ave}->Aveend.
9>sma:main().Added1,sma(3)->1.000000,sma(5)->1.000000Added2,sma(3)->1.500000,sma(5)->1.500000Added3,sma(3)->2.000000,sma(5)->2.000000Added4,sma(3)->3.000000,sma(5)->2.500000Added5,sma(3)->4.000000,sma(5)->3.000000Added5,sma(3)->4.666667,sma(5)->3.800000Added4,sma(3)->4.666667,sma(5)->4.200000Added3,sma(3)->4.000000,sma(5)->4.200000Added2,sma(3)->3.000000,sma(5)->3.800000Added1,sma(3)->2.000000,sma(5)->3.000000ok
Erlang has closures, but immutable variables. A solution then is to use processes and a simple message passing based API.
Matrix languages have routines to compute the gliding avarages for a given sequence of items.
>n=1000; m=100; x=random(1,n);>x10=fold(x,ones(1,m)/m);>x10=fftfold(x,ones(1,m)/m)[m:n]; // more efficient
It is less efficient to loop as in the following commands.
>function store (x:number, v:vector, n:index) ...$if cols(v)<n then return v|x;$else$ v=rotleft(v); v[-1]=x;$ return v;$endif;$endfunction>v=zeros(1,0); for k=1:20; v=store(k,v,10); mean(v), end; 1 1.5 2 2.5 3 3.5 4 4.5 5 5.5 6.5 7.5 8.5 9.5 10.5 11.5 12.5 13.5 14.5 15.5>v [ 11 12 13 14 15 16 17 18 19 20 ]
letsmaperiodf(list:floatlist)=letsma_auxqueuev=letq=Seq.truncateperiod(v::queue)Seq.averageq,Seq.toListqList.fold(funsv->letavg,state=sma_auxsvfavgstate)[]listprintf"sma3: "[1.;2.;3.;4.;5.;5.;4.;3.;2.;1.]|>sma3(printf"%.2f ")printf"\nsma5: "[1.;2.;3.;4.;5.;5.;4.;3.;2.;1.]|>sma5(printf"%.2f ")printfn""
sma3: 1.00 1.50 2.00 3.00 4.00 4.67 4.67 4.00 3.00 2.00sma5: 1.00 1.50 2.00 2.50 3.00 3.80 4.20 4.20 3.80 3.00
TheI word creates a quotation (anonymous function) that closes over a sequence and a period. This quotation handles adding/removing numbers to the simple moving average (SMA). We can then add a number to the SMA usingsma-add and get the SMA's sequence and mean withsma-query. Quotations adhere to thesequence protocol so we can obtain the sequence of numbers simply by callingfirst on the SMA quotation.
USING:kernelinterpolateiolocalsmath.statisticsprettyprintrandomsequences;IN:rosetta-code.simple-moving-avg::I(P--quot)V{}clone:>v![vswapsuffix!Pshorttail*v!];:sma-add(quotn--quot')swaptuckcall(xx--x);:sma-query(quot--avgv)firstconcatdupmeanswap;:simple-moving-average-demo(--)5I10<iota>[oversma-queryunparse[IAfter${2}numbersSequenceis${0}Meanis${1}I]nl100randomsma-add]eachdrop;MAIN:simple-moving-average-demo
After 0 numbers Sequence is V{ } Mean is 0After 1 numbers Sequence is V{ 41 } Mean is 41After 2 numbers Sequence is V{ 41 31 } Mean is 36After 3 numbers Sequence is V{ 41 31 2 } Mean is 24+2/3After 4 numbers Sequence is V{ 41 31 2 24 } Mean is 24+1/2After 5 numbers Sequence is V{ 41 31 2 24 70 } Mean is 33+3/5After 6 numbers Sequence is V{ 31 2 24 70 80 } Mean is 41+2/5After 7 numbers Sequence is V{ 2 24 70 80 96 } Mean is 54+2/5After 8 numbers Sequence is V{ 24 70 80 96 84 } Mean is 70+4/5After 9 numbers Sequence is V{ 70 80 96 84 7 } Mean is 67+2/5class MovingAverage { Int period Int[] stream new make (Int period) { this.period = period stream = [,] } // add number to end of stream and remove numbers from start if // stream is larger than period public Void addNumber (Int number) { stream.add (number) while (stream.size > period) { stream.removeAt (0) } } // compute average of numbers in stream public Float average () { if (stream.isEmpty) return 0.0f else 1.0f * (Int)(stream.reduce(0, |a,b| { (Int) a + b })) / stream.size }}class Main{ public static Void main () { // test by adding random numbers and printing average after each number av := MovingAverage (5) 10.times |i| { echo ("After $i numbers list is ${av.stream} average is ${av.average}") av.addNumber (Int.random(0..100)) } }}for a period of 5
After 0 numbers list is [,] average is 0.0After 1 numbers list is [64] average is 64.0After 2 numbers list is [64, 50] average is 57.0After 3 numbers list is [64, 50, 26] average is 46.666666666666664After 4 numbers list is [64, 50, 26, 77] average is 54.25After 5 numbers list is [64, 50, 26, 77, 82] average is 59.8After 6 numbers list is [50, 26, 77, 82, 95] average is 66.0After 7 numbers list is [26, 77, 82, 95, 11] average is 58.2After 8 numbers list is [77, 82, 95, 11, 23] average is 57.6After 9 numbers list is [82, 95, 11, 23, 50] average is 52.2
:f+!( f addr -- )dupf@f+f!;:,f0s( n -- )falign0do0ef,loop;:period@;:usedcell+;:head2cells+;:sum3cells+faligned;:ring( addr -- faddr )dupsumfloat+swaphead@floats+;:update( fvalue addr -- addr )dupringf@fnegatedupsumf+!fdupdupringf!dupsumf+!duphead@1+overperiodmodoverhead!;:moving-averagecreate( period -- )dup,0,0,1+,f0sdoes>( fvalue -- avg )updatedupused@overperiod<if1overused+!thendupsumf@used@0d>ff/;3moving-averagesma1esmaf.\ 1.2esmaf.\ 1.53esmaf.\ 2.4esmaf.\ 3.
programMovavgimplicit noneinteger::iwrite(*,"(a)")"SIMPLE MOVING AVERAGE: PERIOD = 3"doi=1,5write(*,"(a, i2, a, f8.6)")"Next number:",i," sma = ",sma(real(i))end do doi=5,1,-1write(*,"(a, i2, a, f8.6)")"Next number:",i," sma = ",sma(real(i))end docontainsfunctionsma(n)real::smareal,intent(in)::nreal,save::a(3)=0integer,save::count=0if(count<3)thencount=count+1a(count)=nelsea=eoshift(a,1,n)end ifsma=sum(a(1:count))/real(count)end functionend programMovavg
' FB 1.05.0 Win64TypeFuncTypeAsFunction(AsDouble)AsDouble' These 'shared' variables are available to all functions defined belowDimSharedpAsUIntegerDimSharedlist()AsDoubleFunctionsma(nAsDouble)AsDoubleRedimPreservelist(0ToUBound(list)+1)list(UBound(list))=nDimstartAsInteger=0DimlengthAsInteger=UBound(list)+1Iflength>pThenstart=UBound(list)-p+1length=pEndIfDimsumAsDouble=0.0ForiAsInteger=startToUBound(list)sum+=list(i)NextReturnsum/lengthEndFunctionFunctioninitSma(periodAsUinteger)AsFuncTypep=periodEraselist'' ensure the array is empty on each initializationReturn@smaEndFunctionDimAsFuncTypema=initSma(3)Print"Period = ";pPrintForiAsInteger=0To9Print"Add";i;" => moving average =";ma(i)NextPrintma=initSma(5)Print"Period = ";pPrintForiAsInteger=9To0Step-1Print"Add";i;" => moving average =";ma(i)NextPrintPrint"Press any key to quit"Sleep
Period = 3Add 0 => moving average = 0Add 1 => moving average = 0.5Add 2 => moving average = 1Add 3 => moving average = 2Add 4 => moving average = 3Add 5 => moving average = 4Add 6 => moving average = 5Add 7 => moving average = 6Add 8 => moving average = 7Add 9 => moving average = 8Period = 5Add 9 => moving average = 9Add 8 => moving average = 8.5Add 7 => moving average = 8Add 6 => moving average = 7.5Add 5 => moving average = 7Add 4 => moving average = 6Add 3 => moving average = 5Add 2 => moving average = 4Add 1 => moving average = 3Add 0 => moving average = 2
MovingAverage:=function(n)localsma,buffer,pos,sum,len;buffer:=List([1..n],i->0);pos:=0;len:=0;sum:=0;sma:=function(x)pos:=RemInt(pos,n)+1;sum:=sum+x-buffer[pos];buffer[pos]:=x;len:=Minimum(len+1,n);returnsum/len;end;returnsma;end;f:=MovingAverage(3);f(1);# 1f(2);# 3/2f(3);# 2f(4);# 3f(5);# 4f(5);# 14/3f(4);# 14/3f(3);# 4f(2);# 3f(1);# 2
packagemainimport"fmt"funcsma(periodint)func(float64)float64{variintvarsumfloat64varstorage=make([]float64,0,period)returnfunc(inputfloat64)(avrgfloat64){iflen(storage)<period{sum+=inputstorage=append(storage,input)}sum+=input-storage[i]storage[i],i=input,(i+1)%periodavrg=sum/float64(len(storage))return}}funcmain(){sma3:=sma(3)sma5:=sma(5)fmt.Println("x sma3 sma5")for_,x:=range[]float64{1,2,3,4,5,5,4,3,2,1}{fmt.Printf("%5.3f %5.3f %5.3f\n",x,sma3(x),sma5(x))}}
x sma3 sma51.000 1.000 1.0002.000 1.500 1.5003.000 2.000 2.0004.000 3.000 2.5005.000 4.000 3.0005.000 4.667 3.8004.000 4.667 4.2003.000 4.000 4.2002.000 3.000 3.8001.000 2.000 3.000
defsimple_moving_average={size->defnums=[]doubletotal=0.0return{newElement->nums+=newElementoldestElement=nums.size()>size?nums.remove(0):0total+=newElement-oldestElementtotal/nums.size()}}ma5=simple_moving_average(5)(1..5).each{printf("%1.1f ",ma5(it))}(5..1).each{printf("%1.1f ",ma5(it))}
1.0 1.5 2.0 2.5 3.0 3.8 4.2 4.2 3.8 3.0
Conform version to the requirement, function SMA called multiple times with just a number:
{-# LANGUAGE BangPatterns #-}importControl.MonadimportData.ListimportData.IORefdataPairab=Pair!a!bmean::Fractionala=>[a]->amean=divl.foldl'(\(Pairsl)x->Pair(s+x)(l+1))(Pair0.00)wheredivl(_,0)=0.0divl(s,l)=s/fromIntegrallseries=[1,2,3,4,5,5,4,3,2,1]mkSMA::Int->IO(Double->IODouble)mkSMAperiod=avgr<$>newIORef[]whereavgrnsrefx=readIORefnsref>>=(\ns->letxs=takeperiod(x:ns)inwriteIORefnsrefxs$>meanxs)main=mkSMA3>>=(\sma3->mkSMA5>>=(\sma5->mapM_(str<$>puren<*>sma3<*>sma5)series))wherestrnmm3mm5=concat["Next number = ",shown,", SMA_3 = ",showmm3,", SMA_5 = ",showmm5]
Next number = 1.0, SMA_3 = 1.0, SMA_5 = 1.0Next number = 2.0, SMA_3 = 1.5, SMA_5 = 1.5Next number = 3.0, SMA_3 = 2.0, SMA_5 = 2.0Next number = 4.0, SMA_3 = 3.0, SMA_5 = 2.5Next number = 5.0, SMA_3 = 4.0, SMA_5 = 3.0Next number = 5.0, SMA_3 = 4.666666666666667, SMA_5 = 3.8Next number = 4.0, SMA_3 = 4.666666666666667, SMA_5 = 4.2Next number = 3.0, SMA_3 = 4.0, SMA_5 = 4.2Next number = 2.0, SMA_3 = 3.0, SMA_5 = 3.8Next number = 1.0, SMA_3 = 2.0, SMA_5 = 3.0
importData.ListimportControl.ArrowimportControl.MonadsMAp=map(head***head).tail.scanl(\(y,_)->(id&&&return.av).(:iflengthy==ptheninityelsey))([],[])whereav=liftM2(/)sum(fromIntegral.length)printSMAnp=mapM_(\(n,a)->putStrLn$"Next number: "++shown++" Average: "++showa).taken.sMAp$[1..5]++[5,4..1]++[3..]
Stateful function using the state monad to keep track of state
importControl.MonadimportControl.Monad.Stateperiod::Intperiod=3typeSMAState=[Float]computeSMA::Float->StateSMAStateFloatcomputeSMAx=dopreviousValues<-getletvalues=previousValues++[x]letnewAverage=iflengthvalues<=periodthen(sumvalues)/(fromIntegral$lengthremainingValues::Float)else(sumremainingValues)/(fromIntegral$lengthremainingValues::Float)whereremainingValues=dropIfperiodvaluesput$dropIfperiodvaluesreturnnewAveragedropIf::Int->[a]->[a]dropIfxxs=drop((lengthxs)-x)xsdemostrateSMA::StateSMAState[Float]demostrateSMA=mapMcomputeSMA[1,2,3,4,5,5,4,3,2,1]main::IO()main=print$evalStatedemostrateSMA[]
[1.0,1.5,2.0,3.0,4.0,4.6666665,4.6666665,4.0,3.0,2.0]
REAL :: n=10, nums(n)nums = (1,2,3,4,5, 5,4,3,2,1)DO i = 1, n WRITE() "num=", i, "SMA3=", SMA(3,nums(i)), "SMA5=",SMA(5,nums(i))ENDDOEND ! of "main"FUNCTION SMA(period, num) ! maxID independent streams REAL :: maxID=10, now(maxID), Periods(maxID), Offsets(maxID), Pool(1000) ID = INDEX(Periods, period) IF( ID == 0) THEN ! initialization IDs = IDs + 1 ID = IDs Offsets(ID) = SUM(Periods) + 1 Periods(ID) = period ENDIF now(ID) = now(ID) + 1 ALIAS(Pool,Offsets(ID), Past,Periods(ID)) ! renames relevant part of data pool Past = Past($+1) ! shift left Past(Periods(ID)) = num SMA = SUM(Past) / MIN( now(ID), Periods(ID) ) END
num=1 SMA3=1 SMA5=1num=2 SMA3=1.5 SMA5=1.5num=3 SMA3=2 SMA5=2num=4 SMA3=3 SMA5=2.5num=5 SMA3=4 SMA5=3num=6 SMA3=4.666666667 SMA5=3.8num=7 SMA3=4.666666667 SMA5=4.2num=8 SMA3=4 SMA5=4.2num=9 SMA3=3 SMA5=3.8num=10 SMA3=2 SMA5=3
proceduremain(A)sma:=buildSMA(3)# Use better name than "I".everywrite(sma(!A))endprocedurebuildSMA(P)localstreamc:=create{stream:=[]whilen:=(avg@&source)[1]do{put(stream,n)if*stream>Pthenpop(stream)every(avg:=0.0)+:=!streamavg:=avg/*stream}}return(@c,c)end
Note: This program uses Unicon specific co-expression calling syntax. It can be easily modified to run under Icon.
and a sample run:
->ravg 3 1 4 1 5 9 2 6 3 83.02.02.6666666666666672.03.3333333333333335.05.3333333333333335.6666666666666673.6666666666666675.666666666666667->
If theUtils package is imported from theUnicon code library then a (Unicon only) solution is:
importUtilsproceduremain(A)sma1:=closure(SMA,[],3)sma2:=closure(SMA,[],4)everyeveryn:=!Adowrite(left(sma1(n),20),sma2(n))endprocedureSMA(stream,P,n)put(stream,n)if*stream>Pthenpop(stream)every(avg:=0.0)+:=!streamreturnavg/*streamend
with the sample run:
->ravg 3 1 4 1 5 9 2 6 3 83.0 3.02.0 2.02.666666666666667 2.6666666666666672.0 2.253.333333333333333 2.755.0 4.755.333333333333333 4.255.666666666666667 5.53.666666666666667 5.05.666666666666667 4.75->
Note: J is block-oriented, not stream oriented. That is, J expresses algorithms with the semantics that all the data is available at once (rather than maintaining state and waiting for the next item).
In that context, moving average is expressed very concisely in J as(+/%#)\, though it is worth noting that this approach does not provide averages for the initial cases where not all data would be available yet:
5(+/%#)\1234554321NB. not a solution for this task33.84.24.23.83
In the context of the task, we need to produce a stateful function to consume streams. Since J does not have native lexical closure, we need toimplement it. Thus thestreaming solution is more complex:
lex=:1:'(a[n__a=.m#_.[a=.18!:3$~0)&(4 :''(+/%#)(#~1-128!:5)n__x=.1|.!.y n__x'')'
Example:
sma=:5lexsma&>123455432111.522.533.84.24.23.83
Here, the&> is analogous to the "for each" of other languages.
Or, a more traditional approach could be used:
avg=:+/%#SEQ=:''moveAvg=:4 :0"0SEQ=:SEQ,yavg({.~x-@<.#)SEQ)5moveAvg123455432111.522.533.84.24.23.83
importjava.util.LinkedList;importjava.util.Queue;publicclassMovingAverage{privatefinalQueue<Double>window=newLinkedList<Double>();privatefinalintperiod;privatedoublesum;publicMovingAverage(intperiod){assertperiod>0:"Period must be a positive integer";this.period=period;}publicvoidnewNum(doublenum){sum+=num;window.add(num);if(window.size()>period){sum-=window.remove();}}publicdoublegetAvg(){if(window.isEmpty())return0.0;// technically the average is undefinedreturnsum/window.size();}publicstaticvoidmain(String[]args){double[]testData={1,2,3,4,5,5,4,3,2,1};int[]windowSizes={3,5};for(intwindSize:windowSizes){MovingAveragema=newMovingAverage(windSize);for(doublex:testData){ma.newNum(x);System.out.println("Next number = "+x+", SMA = "+ma.getAvg());}System.out.println();}}}
Next number = 1.0, SMA = 1.0Next number = 2.0, SMA = 1.5Next number = 3.0, SMA = 2.0Next number = 4.0, SMA = 3.0Next number = 5.0, SMA = 4.0Next number = 5.0, SMA = 4.666666666666667Next number = 4.0, SMA = 4.666666666666667Next number = 3.0, SMA = 4.0Next number = 2.0, SMA = 3.0Next number = 1.0, SMA = 2.0Next number = 1.0, SMA = 1.0Next number = 2.0, SMA = 1.5Next number = 3.0, SMA = 2.0Next number = 4.0, SMA = 2.5Next number = 5.0, SMA = 3.0Next number = 5.0, SMA = 3.8Next number = 4.0, SMA = 4.2Next number = 3.0, SMA = 4.2Next number = 2.0, SMA = 3.8Next number = 1.0, SMA = 3.0
functionsimple_moving_averager(period){varnums=[];returnfunction(num){nums.push(num);if(nums.length>period)nums.splice(0,1);// remove the first element of the arrayvarsum=0;for(variinnums)sum+=nums[i];varn=period;if(nums.length<period)n=nums.length;return(sum/n);}}varsma3=simple_moving_averager(3);varsma5=simple_moving_averager(5);vardata=[1,2,3,4,5,5,4,3,2,1];for(variindata){varn=data[i];// using WSHWScript.Echo("Next number = "+n+", SMA_3 = "+sma3(n)+", SMA_5 = "+sma5(n));}
Next number = 1, SMA_3 = 1, SMA_5 = 1Next number = 2, SMA_3 = 1.5, SMA_5 = 1.5Next number = 3, SMA_3 = 2, SMA_5 = 2Next number = 4, SMA_3 = 3, SMA_5 = 2.5Next number = 5, SMA_3 = 4, SMA_5 = 3Next number = 5, SMA_3 = 4.666666666666667, SMA_5 = 3.8Next number = 4, SMA_3 = 4.666666666666667, SMA_5 = 4.2Next number = 3, SMA_3 = 4, SMA_5 = 4.2Next number = 2, SMA_3 = 3, SMA_5 = 3.8Next number = 1, SMA_3 = 2, SMA_5 = 3
// single-sidedArray.prototype.simpleSMA=function(N){returnthis.map(function(el,index,_arr){return_arr.filter(function(x2,i2){returni2<=index&&i2>index-N;}).reduce(function(current,last,index,arr){return(current+last);})/index||1;});};g=[0,1,2,3,4,5,6,7,8,9,10];console.log(g.simpleSMA(3));console.log(g.simpleSMA(5));console.log(g.simpleSMA(g.length));
[1, 1, 1.5, 2, 2.25, 2.4, 2.5, 2.5714285714285716, 2.625, 2.6666666666666665, 2.7][1, 1, 1.5, 2, 2.5, 3, 3.3333333333333335, 3.5714285714285716, 3.75, 3.888888888888889, 4][1, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5]
Works with jq, the C implementation of jq
Works with gojq, the Go implementation of jq
Works with jaq, the Rust implementation of jq
jq functions are stateless, so in this entry, sma($x) is definedas a parameterized jq filter that takes as input the relevant state as a JSON object.This should initially include a key named "period" specifying the period,which may be infinite, i.e. the jq value `infinite` corresponding to positive infinity.
For example the initial call to sma/1 might look like:
{period: infinite} | sma(100)Two examples are given, one with a finite and the other with an infinite period.Both compute the average of the 11 numbers 0, 1, ... 10, by calling sma(0) and then sma(1), and so on.
# The input should be a JSON object with a key named "period".# The output is a JSON object with a key named "average" giving the SMA.def sma($x): def average: .n as $n | if $n == null or $n == 0 then . + {n: 1, average: $x} else .average |= (. * $n + $x) / ($n + 1) | .n += 1 end; if . == null or (.period and .period < 1) then "The initial call to sma/1 must specify the period properly" | error elif .n and .n < 0 then "Invalid value of .n" | error elif (.period | isinfinite) then average elif .n == null or .n == 0 then . + {n: 1, average: $x, array: [$x]} else .n as $n | if $n < .period then .array += [$x] | .n += 1 else .array |= .[1:] + [$x] end | .average = (.array | (add/length)) end;# Call sma($x) for the 11 numbers 0, 1, ... 10.def example($period): reduce range(0;11) as $x({period: $period}; sma($x)) | .average ;example(11), example(infinite)55
usingStatistics
The function wants specified the type of the data in the buffer and, if you want, the limit of the buffer.
functionmovingaverage(::Type{T}=Float64;lim::Integer=-1)whereT<:Realbuffer=Vector{T}(0)iflim==-1# unlimited bufferreturn(y::T)->beginpush!(buffer,y)returnmean(buffer)endelse# limited size bufferreturn(y)->beginpush!(buffer,y)iflength(buffer)>limshift!(buffer)endreturnmean(buffer)endendendtest=movingaverage()@showtest(1.0)# mean([1])@showtest(2.0)# mean([1, 2])@showtest(3.0)# mean([1, 2, 3])
test(1.0) = 1.0test(2.0) = 1.5test(3.0) = 2.0
Non-stateful:
v:v,|v:1+!5v1234554321avg:{(+/x)%#x}sma:{avg'x@(,\!y),(1+!y)+\:!y}sma[v;5]11.522.533.84.24.23.83
Stateful:
sma:{n::x#_n;{n::1_n,x;{avgx@&~_n~'x}n}}sma[5]'v11.522.533.84.24.23.83
// version 1.0.6funinitMovingAverage(p:Int):(Double)->Double{if(p<1)throwIllegalArgumentException("Period must be a positive integer")vallist=mutableListOf<Double>()return{list.add(it)if(list.size>p)list.removeAt(0)list.average()}}funmain(args:Array<String>){valsma4=initMovingAverage(4)valsma5=initMovingAverage(5)valnumbers=listOf(1.0,2.0,3.0,4.0,5.0,5.0,4.0,3.0,2.0,1.0)println("num\tsma4\tsma5\n")for(numberinnumbers)println("${number}\t${sma4(number)}\t${sma5(number)}")}
num sma4 sma51.0 1.0 1.02.0 1.5 1.53.0 2.0 2.04.0 2.5 2.55.0 3.5 3.05.0 4.25 3.84.0 4.5 4.23.0 4.25 4.22.0 3.5 3.81.0 2.5 3.0
This example isincorrect. Please fix the code and remove this message.
|
definesimple_moving_average(a::array,s::integer)::decimal=>{#a->size==0?return0.00#s==0?return0.00#a->size==1?returndecimal(#a->first)#s==1?returndecimal(#a->last)local(na=array)if(#a->size<=#s)=>{#na=#aelselocal(ar=#a->ascopy)#ar->reverseloop(#s)=>{#na->insert(#ar->get(loop_count))}}#s>#na->size?#s=#na->sizereturn(withein#nasum#e)/decimal(#s)}// tests:'SMA 3 on array(1,2,3,4,5,5,4,3,2,1): 'simple_moving_average(array(1,2,3,4,5,5,4,3,2,1),3)'\rSMA 5 on array(1,2,3,4,5,5,4,3,2,1): 'simple_moving_average(array(1,2,3,4,5,5,4,3,2,1),5)'\r\rFurther example:\r'local(mynumbers=array,sma_num=5)loop(10)=>{^#mynumbers->insert(integer_random(1,100))#mynumbers->size+' numbers: '+#mynumbers' SMA3 is: '+simple_moving_average(#mynumbers,3)', SMA5 is: '+simple_moving_average(#mynumbers,5)'\r'^}
SMA 3 on array(1,2,3,4,5,5,4,3,2,1): 2.000000SMA 5 on array(1,2,3,4,5,5,4,3,2,1): 3.000000Further example: 1 numbers: array(91) SMA3 is: 91.000000, SMA5 is: 91.0000002 numbers: array(91, 30) SMA3 is: 60.500000, SMA5 is: 60.5000003 numbers: array(91, 30, 99) SMA3 is: 73.333333, SMA5 is: 73.3333334 numbers: array(91, 30, 99, 73) SMA3 is: 67.333333, SMA5 is: 73.2500005 numbers: array(91, 30, 99, 73, 22) SMA3 is: 64.666667, SMA5 is: 63.0000006 numbers: array(91, 30, 99, 73, 22, 35) SMA3 is: 43.333333, SMA5 is: 51.8000007 numbers: array(91, 30, 99, 73, 22, 35, 93) SMA3 is: 50.000000, SMA5 is: 64.4000008 numbers: array(91, 30, 99, 73, 22, 35, 93, 24) SMA3 is: 50.666667, SMA5 is: 49.4000009 numbers: array(91, 30, 99, 73, 22, 35, 93, 24, 8) SMA3 is: 41.666667, SMA5 is: 36.40000010 numbers: array(91, 30, 99, 73, 22, 35, 93, 24, 8, 80) SMA3 is: 37.333333, SMA5 is: 48.000000
The interesting thing here is how to implement an equivalent of a stateful function.For sample output seehttp://libertybasic.conforums.com/index.cgi?board=open&action=display&num=1322956720
dim v$( 100) ' Each array term stores a particular SMA of period p in p*10 bytes nomainwin WindowWidth =1080 WindowHeight = 780 graphicbox #w.gb1, 20, 20, 1000, 700 open "Running averages to smooth data" for window as #w #w "trapclose quit" #w.gb1 "down" old.x = 0 old.y.orig =500 ' black old.y.3.SMA =350 ' red old.y.20.SMA =300 ' green for i =0 to 999 step 1 scan v =1.1 +sin( i /1000 *2 *3.14159265) + 0.2 *rnd( 1) ' sin wave with added random noise x =i /6.28318 *1000 y.orig =500 -v /2.5 *500 #w.gb1 "color black ; down ; line "; i-1; " "; old.y.orig; " "; i; " "; y.orig; " ; up" y.3.SMA =500 -SMA( 1, v, 3) /2.5 *500 ' SMA given ID of 1 is to do 3-term running average #w.gb1 "color red ; down ; line "; i-1; " "; old.y.3.SMA +50; " "; i; " "; y.3.SMA +50; " ; up" y.20.SMA =500 -SMA( 2, v, 20) /2.5 *500 ' SMA given ID of 2 is to do 20-term running average #w.gb1 "color green ; down ; line "; i-1; " "; old.y.20.SMA +100; " "; i; " "; y.20.SMA +100; " ; up" 'print "Supplied with "; v; ", so SMAs are now "; using( "###.###", SMA( 1, v, 3)); " over 3 terms or "; using( "###.###", SMA( 2, v, 5)) ; " over 5 terms." ' ID, latest data, period old.y.orig =y.orig old.y.3.SMA =y.3.SMA old.y.20.SMA =y.20.SMA next i waitsub quit j$ close #w endend subfunction SMA( ID, Number, Period) v$( ID) =right$( " " +str$( Number), 10) +v$( ID) ' add new number at left, lose last number on right v$( ID) =left$( v$( ID), Period *10) 'print "{"; v$( ID); "}", k =0 ' number of terms read total =0 ' sum of terms read do p$ =mid$( v$( ID), 1 +k *10, 10) if p$ ="" then exit do vv =val( p$) total =total +vv k =k +1 loop until p$ ="" if k <Period then SMA =total / k else SMA =total /Periodend functionAlthough Logo does not support closures, some varieties of Logo support enough metaprogramming to accomplish this task.
UCB Logo has a DEFINE primitive to construct functions from structured instruction lists. In addition, UCB Logo supports a compact template syntax for quoting lists (backquote "`") and replacing components of quoted lists (comma ","). These facilities can be used together in order to create templated function-defining-functions.
to average :l output quotient apply "sum :l count :lendto make.sma :name :period localmake "qn word :name ".queue make :qn [] define :name `[ [n] ; parameter list [if equal? count :,:qn ,:period [ignore dequeue ",:qn]] [queue ",:qn :n] [output average :,:qn] ]endmake.sma "avg3 3show map "avg3 [1 2 3 4 5] ; [1 1.5 2 3 4]show text "avg3 ; examine what substitutions took place[[n] [if equal? count :avg3.queue 3 [ignore dequeue "avg3.queue]] [queue "avg3.queue :n] [output average :avg3.queue]]; the internal queue is in the global namespace, easy to inspectshow :avg3.queue ; [3 4 5]
If namespace pollution is a concern, UCB Logo supplies a GENSYM command to obtain unique names in order to avoid collisions.
... localmake "qn word :name gensym ...; list user-defined functions and variablesshow procedures ; [average avg3 make.sma]show names ; [[[] [avg3.g1]]
functionsma(period)localt={}functionsum(t)sum=0for_,vinipairs(t)dosum=sum+vendreturnsumendfunctionaverage(n)if#t==periodthentable.remove(t,1)endt[#t+1]=nreturnsum(t)/#tendreturnaverageendsma5=sma(5)sma10=sma(10)print("SMA 5")forv=1,15doprint(sma5(v))endprint("\nSMA 10")forv=1,15doprint(sma10(v))end
moduleSimple_moving_average{smaMAKER=lambda(m)->{s=stack=lambdam,s(N)->{stacks{iflen(s)=mthendropdataN}=array(stack(s))#sum()/len(s)}}Print"Period = 3"ma=smaMaker(3)test(0,9)Print"Period = 5"ma=smaMaker(5)test(9,0)endsubtest(A,B)localiPrintfori=AtoBPrint"Add ";i;" => moving average ";ma(i)nextPrintendsub}Simple_moving_average
moduleSimple_moving_average{classsmaMaker{private:s=stackmpublic:set{readthis}value(N){stack.s{iflen(.s)=.mthendropdataN}=array(stack(.s))#sum()/len(.s)}class:modulesmaMaker(.m){}}Print"Period = 3"ma=smaMaker(3)test(0,9)Print"Period = 5"ma=smaMaker(5)test(9,0)endsubtest(A,B)localiPrintfori=AtoBPrint"Add ";i;" => moving average ";ma(i)nextPrintendsub}Simple_moving_average
Period = 3Add 0 => moving average = 0Add 1 => moving average = 0.5Add 2 => moving average = 1Add 3 => moving average = 2Add 4 => moving average = 3Add 5 => moving average = 4Add 6 => moving average = 5Add 7 => moving average = 6Add 8 => moving average = 7Add 9 => moving average = 8Period = 5Add 9 => moving average = 9Add 8 => moving average = 8.5Add 7 => moving average = 8Add 6 => moving average = 7.5Add 5 => moving average = 7Add 4 => moving average = 6Add 3 => moving average = 5Add 2 => moving average = 4Add 1 => moving average = 3Add 0 => moving average = 2
This version uses a list entry so it can use the built-in function.
MA[x_List,r_]:=Join[Table[Mean[x[[1;;y]]],{y,r-1}],MovingAverage[x,r]]
This version is stateful instead.
MAData={{},0};MAS[x_,t_:Null]:=With[{r=If[t===Null,MAData[[2]],t]},Mean[MAData[[1]]=If[Length[#]>(MAData[[2]]=r),#[[-r;;-1]],#]&@Append[MAData[[1]],x]]]
Tests:
MA[{1, 2, 3, 4, 5, 5, 4, 3, 2, 1}, 5]=> {1, 3/2, 2, 5/2, 3, 19/5, 21/5, 21/5, 19/5, 3}MAS[1, 5] => 1MAS[2] => 3/2MAS[3] => 2MAS[4] => 5/2MAS[5] => 3MAS[5] => 19/5MAS[4] => 21/5MAS[3] => 21/5MAS[2] => 19/5MAS[1] => 3Matlab and Octave provide very efficient and fast functions, that can be applied to vectors (i.e. series of data samples)
[m,z]=filter(ones(1,P),P,x);
m is the moving average, z returns the state at the end of the data series, which can be used to continue the moving average.
[m,z]=filter(ones(1,P),P,x,z);
In Mercury, an idiomatic "moving averages" function would be 'stateless' - or rather, it would haveexplicit state that its callers would have to thread through uses of it:
% state(period, list of floats from [newest, ..., oldest]):- type state ---> state(int, list(float)).:- func init(int) = state.init(Period) = state(Period, []).:- pred sma(float::in, float::out, state::in, state::out) is det.sma(N, Average, state(P, L0), state(P, L)) :- take_upto(P, [N|L0], L), Average = foldl((+), L, 0.0) / float(length(L)).
Some notes about this solution: unless P = 0, length(L) can never be 0, as L always incorporates at least N (a step that is accomplished in the arguments to list.take_upto/3). If the implementation of the 'state' type is hidden, and if init/1 checks for P = 0, users of this code can never cause a division-by-zero error in sma/4. Although this solution doesn't try to be as stateful as the task description would like, explicit state is by far simpler and more natural and more straightforward than the alternative in Mercury. Finally,state variables (and higher-order functions that anticipate threaded state) remove much of the potential ugliness or error in threading the same state through many users.
We define an SMA class, which can be configured with the desired window size (P).
SMA={}SMA.P=5// (a default; may be overridden)SMA.buffer=nullSMA.next=function(n)ifself.buffer==nullthenself.buffer=[]self.buffer.pushnifself.buffer.len>self.Pthenself.buffer.pullreturnself.buffer.sum/self.buffer.lenendfunctionsma3=newSMAsma3.P=3sma5=newSMAforiinrange(10)num=round(rnd*100)print"num: "+num+" sma3: "+sma3.next(num)+" sma5: "+sma5.next(num)endfor
num: 81 sma3: 81 sma5: 81num: 82 sma3: 81.5 sma5: 81.5num: 78 sma3: 80.333333 sma5: 80.333333num: 54 sma3: 71.333333 sma5: 73.75num: 94 sma3: 75.333333 sma5: 77.8num: 8 sma3: 52 sma5: 63.2num: 40 sma3: 47.333333 sma5: 54.8num: 98 sma3: 48.666667 sma5: 58.8num: 48 sma3: 62 sma5: 57.6num: 41 sma3: 62.333333 sma5: 47num: 94 sma3: 61 sma5: 64.2
/* NetRexx */optionsreplaceformatcommentsjavacrossrefsymbolsnobinarynumericdigits20classRAvgSimpleMovingpublicpropertiesprivatewindow=java.util.QueueperiodsumpropertiesconstantexMsg='Period must be a positive integer'--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~methodRAvgSimpleMoving(period_)publicif\period_.datatype('w')thensignalRuntimeException(exMsg)ifperiod_<=0thensignalRuntimeException(exMsg)window=LinkedList()period=period_sum=0return--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~methodnewNum(num)publicsum=sum+numwindow.add(num)ifwindow.size()>periodthendormv=(Rexxwindow.remove())sum=sum-rmvendreturn--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~methodgetAvg()publicreturnsRexxifwindow.isEmpty()thendoavg=0endelsedoavg=sum/window.size()endreturnavg--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~methodrun_samples(args=String[])publicstatictestData=[Rexx1,2,3,4,5,5,4,3,2,1]windowSizes=[Rexx3,5]loopwindSizeoverwindowSizesma=RAvgSimpleMoving(windSize)loopxValovertestDatama.newNum(xVal)say'Next number ='xVal.right(5)', SMA ='ma.getAvg().format(10,9)endxValsayendwindSizereturn--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~methodmain(args=String[])publicstaticrun_samples(args)return
Next number = 1.0, SMA = 1.000000000Next number = 2.0, SMA = 1.500000000Next number = 3.0, SMA = 2.000000000Next number = 4.0, SMA = 3.000000000Next number = 5.0, SMA = 4.000000000Next number = 5.0, SMA = 4.666666667Next number = 4.0, SMA = 4.666666667Next number = 3.0, SMA = 4.000000000Next number = 2.0, SMA = 3.000000000Next number = 1.0, SMA = 2.000000000Next number = 1.0, SMA = 1.000000000Next number = 2.0, SMA = 1.500000000Next number = 3.0, SMA = 2.000000000Next number = 4.0, SMA = 2.500000000Next number = 5.0, SMA = 3.000000000Next number = 5.0, SMA = 3.800000000Next number = 4.0, SMA = 4.200000000Next number = 3.0, SMA = 4.200000000Next number = 2.0, SMA = 3.800000000Next number = 1.0, SMA = 3.000000000
importdequesprocsimplemovingaverage(period:int):auto=assertperiod>0varsumm,n=0.0values:Deque[float]foriin1..period:values.addLast(0)procsma(x:float):float=values.addLast(x)summ+=x-values.popFirst()n=min(n+1,float(period))result=summ/nreturnsmavarsma=simplemovingaverage(3)foriin1..5:echosma(float(i))foriincountdown(5,1):echosma(float(i))echo""varsma2=simplemovingaverage(5)foriin1..5:echosma2(float(i))foriincountdown(5,1):echosma2(float(i))
1.01.52.03.04.04.6666666666666674.6666666666666674.03.02.01.01.52.02.53.03.84.24.23.83.0
use Collection;class MovingAverage { @window : FloatQueue; @period : Int; @sum : Float; New(period : Int) { @window := FloatQueue->New(); @period := period; } method : NewNum(num : Float) ~ Nil { @sum += num; @window->Add(num); if(@window->Size() > @period) { @sum -= @window->Remove(); }; } method : GetAvg() ~ Float { if(@window->IsEmpty()) { return 0; # technically the average is undefined }; return @sum / @window->Size(); } function : Main(args : String[]) ~ Nil { testData := [1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 4.0, 3.0, 2.0, 1.0]; windowSizes := [3.0, 5.0]; each(i : windowSizes) { windSize := windowSizes[i]; ma := MovingAverage->New(windSize); each(j : testData) { x := testData[j]; ma->NewNum(x); avg := ma->GetAvg(); "Next number = {$x}, SMA = {$avg}"->PrintLine(); }; IO.Console->PrintLine(); }; }}Next number = 1.0, SMA = 1.0Next number = 2.0, SMA = 1.500Next number = 3.0, SMA = 2.0Next number = 4.0, SMA = 3.0Next number = 5.0, SMA = 4.0Next number = 5.0, SMA = 4.667Next number = 4.0, SMA = 4.667Next number = 3.0, SMA = 4.0Next number = 2.0, SMA = 3.0Next number = 1.0, SMA = 2.0Next number = 1.0, SMA = 1.0Next number = 2.0, SMA = 1.500Next number = 3.0, SMA = 2.0Next number = 4.0, SMA = 2.500Next number = 5.0, SMA = 3.0Next number = 5.0, SMA = 3.800Next number = 4.0, SMA = 4.200Next number = 3.0, SMA = 4.200Next number = 2.0, SMA = 3.800Next number = 1.0, SMA = 3.0
#import <Foundation/Foundation.h>@interfaceMovingAverage :NSObject{unsignedintperiod;NSMutableArray*window;doublesum;}-(instancetype)initWithPeriod:(unsignedint)thePeriod;@end@implementationMovingAverage// init with default period-(instancetype)init{self=[superinit];if(self){period=10;window=[[NSMutableArrayalloc]init];sum=0.0;}returnself;}// init with specified period-(instancetype)initWithPeriod:(unsignedint)thePeriod{self=[superinit];if(self){period=thePeriod;window=[[NSMutableArrayalloc]init];sum=0.0;}returnself;}// add a new number to the window-(void)add:(double)val{sum+=val;[windowaddObject:@(val)];if([windowcount]>period){NSNumber*n=window[0];sum-=[ndoubleValue];[windowremoveObjectAtIndex:0];}}// get the average value-(double)avg{if([windowcount]==0){return0;// technically the average is undefined}returnsum/[windowcount];}// set the period, resizes current window-(void)setPeriod:(unsignedint)thePeriod{// make smaller?if(thePeriod<[windowcount]){for(inti=0;i<thePeriod;++i){NSNumber*n=window[0];sum-=[ndoubleValue];[windowremoveObjectAtIndex:0];}}period=thePeriod;}// get the period (window size)-(unsignedint)period{returnperiod;}// clear the window and current sum-(void)clear{[windowremoveAllObjects];sum=0;}@endintmain(intargc,constchar*argv[]){@autoreleasepool{doubletestData[10]={1,2,3,4,5,5,4,3,2,1};intperiods[2]={3,5};for(inti=0;i<2;++i){MovingAverage*ma=[[MovingAveragealloc]initWithPeriod:periods[i]];for(intj=0;j<10;++j){[maadd:testData[j]];NSLog(@"Next number = %f, SMA = %f",testData[j],[maavg]);}NSLog(@"\n");}}return0;}
Next number = 1.000000, SMA = 1.000000Next number = 2.000000, SMA = 1.500000Next number = 3.000000, SMA = 2.000000Next number = 4.000000, SMA = 3.000000Next number = 5.000000, SMA = 4.000000Next number = 5.000000, SMA = 4.666667Next number = 4.000000, SMA = 4.666667Next number = 3.000000, SMA = 4.000000Next number = 2.000000, SMA = 3.000000Next number = 1.000000, SMA = 2.000000Next number = 1.000000, SMA = 1.000000Next number = 2.000000, SMA = 1.500000Next number = 3.000000, SMA = 2.000000Next number = 4.000000, SMA = 2.500000Next number = 5.000000, SMA = 3.000000Next number = 5.000000, SMA = 3.800000Next number = 4.000000, SMA = 4.200000Next number = 3.000000, SMA = 4.200000Next number = 2.000000, SMA = 3.800000Next number = 1.000000, SMA = 3.000000
letsma(n,s,q)x=letl=Queue.lengthqands=s+.xinQueue.pushxq;ifl<nthen(n,s,q),s/.float(l+1)else(lets=s-.Queue.popqin(n,s,q),s/.floatl)let_=letperiodLst=[3;5]inletseries=[1.;2.;3.;4.;5.;5.;4.;3.;2.;1.]inList.iter(fund->Printf.printf"SIMPLE MOVING AVERAGE: PERIOD = %d\n"d;ignore(List.fold_left(funox->leto,m=smaoxinPrintf.printf"Next number = %-2g, SMA = %g\n"xm;o)(d,0.,Queue.create())series;);print_newline();)periodLst
SIMPLE MOVING AVERAGE: PERIOD = 3Next number = 1 , SMA = 1Next number = 2 , SMA = 1.5Next number = 3 , SMA = 2Next number = 4 , SMA = 3Next number = 5 , SMA = 4Next number = 5 , SMA = 4.66667Next number = 4 , SMA = 4.66667Next number = 3 , SMA = 4Next number = 2 , SMA = 3Next number = 1 , SMA = 2SIMPLE MOVING AVERAGE: PERIOD = 5Next number = 1 , SMA = 1Next number = 2 , SMA = 1.5Next number = 3 , SMA = 2Next number = 4 , SMA = 2.5Next number = 5 , SMA = 3Next number = 5 , SMA = 3.8Next number = 4 , SMA = 4.2Next number = 3 , SMA = 4.2Next number = 2 , SMA = 3.8Next number = 1 , SMA = 3
More imperatively:
letsma_createperiod=letq=Queue.create()andsum=ref0.0infunx->sum:=!sum+.x;Queue.pushxq;ifQueue.lengthq>periodthensum:=!sum-.Queue.popq;!sum/.float(Queue.lengthq)let()=letperiodLst=[3;5]inletseries=[1.;2.;3.;4.;5.;5.;4.;3.;2.;1.]inList.iter(fund->Printf.printf"SIMPLE MOVING AVERAGE: PERIOD = %d\n"d;letsma=sma_createdinList.iter(funx->Printf.printf"Next number = %-2g, SMA = %g\n"x(smax);)series;print_newline();)periodLst
createSMA returns a closure.The list of values is included into a channel so this code is thread-safe : multiple tasks running in parallel can call the closure returned.
import: parallel: createSMA(period)| ch | Channel new [ ] over send drop ->ch #[ ch receive + left(period) dup avg swap ch send drop ] ;
Usage:
: test| sma3 sma5 l | 3 createSMA -> sma3 5 createSMA -> sma5 [ 1, 2, 3, 4, 5, 5, 4, 3, 2, 1 ] ->l "SMA3" .cr l apply( #[ sma3 perform . ] ) printcr "SMA5" .cr l apply( #[ sma5 perform . ] ) ;
>testSMA31 1.5 2 3 4 4.66666666666667 4.66666666666667 4 3 2SMA51 1.5 2 2.5 3 3.8 4.2 4.2 3.8 3 ok
ooRexx does not have stateful functions, but the same effect can be achieved by using object instances.
testdata=.array~of(1,2,3,4,5,5,4,3,2,1)--runwithdifferentperiodsizesloopperiodover.array~of(3,5)say"Period size ="periodsaymovingaverage=.movingaverage~new(period)loopnumberovertestdataaverage=movingaverage~addnumber(number)say" Next number ="number", moving average ="averageendsayend::classmovingaverage::methodinitexposeperiodqueuesumusestrictargperiodsum=0--thecircularqueuemakesthiseasyqueue=.circularqueue~new(period)--addanumbertotheaverageset::methodaddNumberexposequeuesumusestrictargnumbersum+=number--addthistothequeueold=queue~queue(number)--ifwepushedanelementofftheendofthequeue,--subtractthisfromoursumifold\=.nilthensum-=old--andreturnthecurrentitemreturnsum/queue~items--extramethodtoretrievecurrentaverage::methodaverageexposequeuesum--undefinedreally,butjustreturn0ifqueue~isemptythenreturn0--returncurrentqueuereturnsum/queue~items
Period size = 3 Next number = 1, moving average = 1 Next number = 2, moving average = 1.5 Next number = 3, moving average = 2 Next number = 4, moving average = 3 Next number = 5, moving average = 4 Next number = 5, moving average = 4.66666667 Next number = 4, moving average = 4.66666667 Next number = 3, moving average = 4 Next number = 2, moving average = 3 Next number = 1, moving average = 2Period size = 5 Next number = 1, moving average = 1 Next number = 2, moving average = 1.5 Next number = 3, moving average = 2 Next number = 4, moving average = 2.5 Next number = 5, moving average = 3 Next number = 5, moving average = 3.8 Next number = 4, moving average = 4.2 Next number = 3, moving average = 4.2 Next number = 2, moving average = 3.8 Next number = 1, moving average = 3
def max 1000Class MovingAverage'==================indexbase 1double average,invperiod,mdata[max]sys index,periodmethod Setup(double a,p)sys iPeriod=pinvPeriod=1/pindex=0average=afor i=1 to period mdata[i]=anextend methodmethod Data(double v) as doublesys iindex++if index>period then index=1 'recyclei=index+1 'for oldest dataif i>period then i=1 'recyclemdata[index]=vaverage=average+invperiod*(v-mdata[i])end methodend class'TEST'====MovingAverage AA.Setup 100,10 'initial value and periodA.data 50'...print A.average 'reult 95
declare fun {CreateSMA Period} Xs = {NewCell nil} in fun {$ X} Xs := {List.take X|@Xs Period} {FoldL @Xs Number.'+' 0.0} / {Int.toFloat {Min Period {Length @Xs}}} end endin for Period in [3 5] do SMA = {CreateSMA Period} in {System.showInfo "\nSTART PERIOD "#Period} for I in 1..5 do {System.showInfo " Number = "#I#" , SMA = "#{SMA {Int.toFloat I}}} end for I in 5..1;~1 do {System.showInfo " Number = "#I#" , SMA = "#{SMA {Int.toFloat I}}} end endPartial implementation: does not (yet?) create different stores on each invocation.
sma_per(n)={ sma_v=vector(n); sma_i = 0; n->if(sma_i++>#sma_v,sma_v[sma_i=1]=n;0,sma_v[sma_i]=n;0)+sum(i=1,#sma_v,sma_v[i])/#sma_v};Like in other implementations the sum of the last p values is only updated by subtracting the oldest value and addindg the new. To minimize rounding errors after p values the sum is corrected to the real sum.
programsma;typetsma=recordsmaValue:arrayofdouble;smaAverage,smaSumOld,smaSumNew,smaRezActLength:double;smaActLength,smaLength,smaPos:NativeInt;smaIsntFull:boolean;end;proceduresmaInit(varsma:tsma;p:NativeUint);BeginwithsmadoBeginsetlength(smaValue,0);setlength(smaValue,p);smaLength:=p;smaActLength:=0;smaAverage:=0.0;smaSumOld:=0.0;smaSumNew:=0.0;smaPos:=p-1;smaIsntFull:=trueend;end;functionsmaAddValue(varsma:tsma;v:double):double;BeginwithsmadoBeginIFsmaIsntFullthenBegininc(smaActLength);smaRezActLength:=1/smaActLength;smaIsntFull:=smaActLength<smaLength;end;smaSumOld:=smaSumOld+v-smaValue[smaPos];smaValue[smaPos]:=v;smaSumNew:=smaSumNew+v;smaPos:=smaPos-1;ifsmaPos<0thenbeginsmaSumOld:=smaSumNew;smaSumNew:=0.0;smaPos:=smaLength-1;end;smaAverage:=smaSumOld*smaRezActLength;smaAddValue:=smaAverage;end;end;varsma3,sma5:tsma;i:LongInt;beginsmaInit(sma3,3);smaInit(sma5,5);Fori:=1to5doBeginwrite('Inserting ',i,' into sma3 ',smaAddValue(sma3,i):0:4);writeln(' Inserting ',i,' into sma5 ',smaAddValue(sma5,i):0:4);end;Fori:=5downto1doBeginwrite('Inserting ',i,' into sma3 ',smaAddValue(sma3,i):0:4);writeln(' Inserting ',i,' into sma5 ',smaAddValue(sma5,i):0:4);end;//speed testsmaInit(sma3,3);Fori:=1to100000000dosmaAddValue(sma3,i);writeln('100''000''000 insertions ',sma3.smaAverage:0:4);end.
time ./smaInserting 1 into sma3 1.0000 Inserting 1 into sma5 1.0000Inserting 2 into sma3 1.5000 Inserting 2 into sma5 1.5000Inserting 3 into sma3 2.0000 Inserting 3 into sma5 2.0000Inserting 4 into sma3 3.0000 Inserting 4 into sma5 2.5000Inserting 5 into sma3 4.0000 Inserting 5 into sma5 3.0000Inserting 5 into sma3 4.6667 Inserting 5 into sma5 3.8000Inserting 4 into sma3 4.6667 Inserting 4 into sma5 4.2000Inserting 3 into sma3 4.0000 Inserting 3 into sma5 4.2000Inserting 2 into sma3 3.0000 Inserting 2 into sma5 3.8000Inserting 1 into sma3 2.0000 Inserting 1 into sma5 3.0000100'000'000 insertions 99999999.0000real 0m0.780s { 64-Bit }Using an initializer function which returns an anonymous closure which closes over an instance(separate for each call to the initializer!) of the lexical variables$period,@list, and$sum:
subsma_generator{my$period=shift;my(@list,$sum);returnsub{my$number=shift;push@list,$number;$sum+=$number;$sum-=shift@listif@list>$period;return$sum/@list;}}# Usage:my$sma=sma_generator(3);for(1,2,3,2,7){printf"append $_ --> sma = %.2f (with period 3)\n",$sma->($_);}
append 1 --> sma = 1.00 (with period 3)append 2 --> sma = 1.50 (with period 3)append 3 --> sma = 2.00 (with period 3)append 2 --> sma = 2.33 (with period 3)append 7 --> sma = 4.00 (with period 3)
withjavascript_semantics-- (optional) put first part in sma.e for encapsulation/reusesequencesma={}-- ((period,history,circnxt))integersma_free=0globalfunctionnew_sma(integerperiod)integerresifsma_freethenres=sma_freesma_free=sma[sma_free]sma[res]={period,{},0}elsesma=append(sma,{period,{},0})res=length(sma)endifreturnresendfunctionglobalprocedureadd_sma(integersidx,atomval)integerperiod,circnxtsequencehistory{period,history,circnxt}=sma[sidx]sma[sidx][2]=0-- (kill refcount)iflength(history)<periodthenhistory=append(history,val)elsecircnxt+=1ifcircnxt>periodthencircnxt=1endifsma[sidx][3]=circnxthistory[circnxt]=valendifsma[sidx][2]=historyendprocedureglobalfunctionget_sma_average(integersidx)sequencehistory=sma[sidx][2]integerl=length(history)returniff(l=0?0:sum(history)/l)endfunctionglobalfunctionmoving_average(integersidx,atomval)add_sma(sidx,val)returnget_sma_average(sidx)endfunctionglobalprocedurefree_sma(integersidx)sma[sidx]=sma_freesma_free=sidxendprocedure--</sma.e>--include sma.econstantsma3=new_sma(3)constantsma5=new_sma(5)printf(1," x sma3 sma5\n")forxin{1,2,3,4,5,5,4,3,2,1}doatoma3=moving_average(sma3,x),a5=moving_average(sma5,x)printf(1,"%2g %=6.3g %=4.3g\n",{x,a3,a5})endfor
x sma3 sma5 1 1 1 2 1.5 1.5 3 2 2 4 3 2.5 5 4 3 5 4.67 3.8 4 4.67 4.2 3 4 4.2 2 3 3.8 1 2 3
main => L=[1, 2, 3, 4, 5, 5, 4, 3, 2, 1], Map3 = new_map([p=3]), Map5 = new_map([p=5]), foreach(N in L) printf("n: %-2d sma3: %-17w sma5: %-17w\n",N, sma(N,Map3), sma(N,Map5)) end.sma(N,Map) = Average => Stream = Map.get(stream,[]) ++ [N], if Stream.len > Map.get(p) then Stream := Stream.tail end, Average = cond(Stream.len == 0, 0, sum(Stream) / Stream.len), Map.put(stream,Stream).n: 1 sma3: 1.0 sma5: 1.0 n: 2 sma3: 1.5 sma5: 1.5 n: 3 sma3: 2.0 sma5: 2.0 n: 4 sma3: 3.0 sma5: 2.5 n: 5 sma3: 4.0 sma5: 3.0 n: 5 sma3: 4.666666666666667 sma5: 3.8 n: 4 sma3: 4.666666666666667 sma5: 4.2 n: 3 sma3: 4.0 sma5: 4.2 n: 2 sma3: 3.0 sma5: 3.8 n: 1 sma3: 2.0 sma5: 3.0
(de sma (@Len) (curry (@Len (Data)) (N) (push 'Data N) (and (nth Data @Len) (con @)) # Truncate (*/ (apply + Data) (length Data)) ) )
(def 'sma3 (sma 3))(def 'sma5 (sma 5))(scl 2)(for N (1.0 2.0 3.0 4.0 5.0 5.0 4.0 3.0 2.0 1.0) (prinl (format N *Scl) " (sma3) " (format (sma3 N) *Scl) " (sma5) " (format (sma5 N) *Scl) ) )
1.00 (sma3) 1.00 (sma5) 1.002.00 (sma3) 1.50 (sma5) 1.503.00 (sma3) 2.00 (sma5) 2.004.00 (sma3) 3.00 (sma5) 2.505.00 (sma3) 4.00 (sma5) 3.005.00 (sma3) 4.67 (sma5) 3.804.00 (sma3) 4.67 (sma5) 4.203.00 (sma3) 4.00 (sma5) 4.202.00 (sma3) 3.00 (sma5) 3.801.00 (sma3) 2.00 (sma5) 3.00
SMA: procedure (N) returns (float byaddr); declare N fixed; declare A(*) fixed controlled, (p, q) fixed binary static initial (0); if allocation(A) = 0 then signal error; p = p + 1; if q < 20 then q = q + 1; if p > hbound(A, 1) then p = 1; A(p) = N; return (sum(float(A))/q);I: ENTRY (Period); declare Period fixed binary; if allocation(A) > 0 then FREE A; allocate A(Period); A = 0; p = 0;end SMA;
*process source attributes xref; mat: Proc Options(main); Dcl a(10) Dec Fixed(8,6); Dcl s Dec Fixed(10,8); Dcl n Bin Fixed(31) init(hbound(a)); /* number of items in the list. */ Dcl p Bin Fixed(31) init(3); /* the 1st period */ Dcl q Bin Fixed(31) init(5); /* the 2nd period */ Dcl m Bin Fixed(31); Call i(a); Put Edit(' SMA with SMA with', ' number period 3 period 5', ' -------- ---------- ----------') (Skip,a); Do m=1 To n; Put Edit(m,sma(p,m),sma(q,m))(Skip,f(5),2(f(13,6))); End; i: Proc(a); Dcl a(*) Dec Fixed(8,6); Dcl (j,m) Bin Fixed(31); Do j=1 To hbound(a)/2; a(j)=j; /* ··· increasing values. */ End; Do k=hbound(a)/2 To 1 By -1; a(j)=k; /* ··· decreasing values. */ j+=1; End; End; sma: Proc(p,j) Returns(Dec Fixed(8,6)); Dcl s Dec fixed(8,6) Init(0); Dcl i Bin Fixed(31) Init(0); Dcl j Bin Fixed(31) Init((hbound(a)+1)); Dcl (p,i,k,ka,kb) Bin Fixed(31); ka=max(1,j-p+1); kb=j+p; Do k=ka To kb While(k<=j); i+=1; s+=a(k) End; s=s/i+0.5e-6; Return(s); End; End;SMA with SMA with number period 3 period 5 -------- ---------- ---------- 1 1.000000 1.000000 2 1.500000 1.500000 3 2.000000 2.000000 4 3.000000 2.500000 5 4.000000 3.000000 6 4.666667 3.800000 7 4.666667 4.200000 8 4.000000 4.200000 9 3.000000 3.800000 10 2.000000 3.000000
localfmt=require"fmt"localfunctionsma(period)locali=0localsum=0localstorage={}returnfunction(input)if#storage<periodthensum+=inputstorage:insert(input)endsum+=input-storage[i+1]storage[i+1]=inputi=(i+1)%periodreturnsum/#storageendendlocalsma3=sma(3)localsma5=sma(5)print(" x sma3 sma5")for{1,2,3,4,5,5,4,3,2,1}asxdofmt.print("%5.3f %5.3f %5.3f",x,sma3(x),sma5(x))end
x sma3 sma51.000 1.000 1.0002.000 1.500 1.5003.000 2.000 2.0004.000 3.000 2.5005.000 4.000 3.0005.000 4.667 3.8004.000 4.667 4.2003.000 4.000 4.2002.000 3.000 3.8001.000 2.000 3.000
classMovingAverageletperiod:USizelet_arr:Array[I32]// circular buffervar_curr:USize// index of pointer positionvar_total:I32// cache the total so farnewcreate(period':USize)=>period=period'_arr=Array[I32](period)// preallocate space_curr=0_total=0funrefapply(n:I32):F32=>_total=_total+nif_arr.size()<periodthen_arr.push(n)elsetryletprev=_arr.update(_curr,n)?_total=_total-prev_curr=(_curr+1)%periodendend_total.f32()/_arr.size().f32()// ---- TESTING -----actorMainnewcreate(env:Env)=>letfoo=MovingAverage(3)letbar=MovingAverage(5)letdata:Array[I32]=[1;2;3;4;5;5;4;3;2;1]forvindata.values()doenv.out.print("Foo: "+foo(v).string())endforvindata.values()doenv.out.print("Bar: "+bar(v).string())end
#This version allows a user to enter numbers one at a time to figure this into the SMA calculations$inputs=@()#Create an array to hold all inputs as they are entered.$period1=3#Define the periods you want to utilize$period2=5Write-host"Enter numbers to observe their moving averages."-ForegroundColorGreenfunctiongetSMA($inputs,[int]$period)#Function takes a array of entered values and a period (3 and 5 in this case){if($inputs.Count-lt$period){$period=$inputs.Count}#Makes sure that if there's less numbers than the designated period (3 in this case), the number of availble values is used as the period instead.for($count=0;$count-lt$period;$count++)#Loop sums the latest available values{$result+=$inputs[($inputs.Count)-$count-1]}return($result|ForEach-Object-begin{$sum=0}-process{$sum+=$_}-end{$sum/$period})#Gets the average for a given period}while($true)#Infinite loop so the user can keep entering numbers{try{$inputs+=[decimal](Read-Host)}catch{Write-Host"Enter only numbers"-ForegroundColorRed}#Enter the numbers. Error checking to help mitigate bad inputs (non-number values)"Added "+$inputs[(($inputs.Count)-1)]+", sma($period1) = "+(getSMA$inputs$Period1)+", sma($period2) = "+(getSMA$inputs$period2)}
Procedure.dSMA(Number,Period=0)StaticPStaticNewListL()ProtectedSum=0IfPeriod<>0P=PeriodEndIfLastElement(L())AddElement(L())L()=NumberWhileListSize(L())>PFirstElement(L())DeleteElement(L(),1)WendForEachL()sum+L()NextProcedureReturnsum/ListSize(L())EndProcedure
Both implementations use thedeque datatype.
fromcollectionsimportdequedefsimplemovingaverage(period):assertperiod==int(period)andperiod>0,"Period must be an integer >0"summ=n=0.0values=deque([0.0]*period)# old value queuedefsma(x):nonlocalsumm,nvalues.append(x)summ+=x-values.popleft()n=min(n+1,period)returnsumm/nreturnsma
fromcollectionsimportdequeclassSimplemovingaverage():def__init__(self,period):assertperiod==int(period)andperiod>0,"Period must be an integer >0"self.period=periodself.stream=deque()def__call__(self,n):stream=self.streamstream.append(n)# appends on the rightstreamlength=len(stream)ifstreamlength>self.period:stream.popleft()streamlength-=1ifstreamlength==0:average=0else:average=sum(stream)/streamlengthreturnaverage
Tests
if__name__=='__main__':forperiodin[3,5]:print("\nSIMPLE MOVING AVERAGE (procedural): PERIOD =",period)sma=simplemovingaverage(period)foriinrange(1,6):print(" Next number =%-2g, SMA =%g "%(i,sma(i)))foriinrange(5,0,-1):print(" Next number =%-2g, SMA =%g "%(i,sma(i)))forperiodin[3,5]:print("\nSIMPLE MOVING AVERAGE (class based): PERIOD =",period)sma=Simplemovingaverage(period)foriinrange(1,6):print(" Next number =%-2g, SMA =%g "%(i,sma(i)))foriinrange(5,0,-1):print(" Next number =%-2g, SMA =%g "%(i,sma(i)))
SIMPLE MOVING AVERAGE (procedural): PERIOD = 3 Next number = 1 , SMA = 1 Next number = 2 , SMA = 1.5 Next number = 3 , SMA = 2 Next number = 4 , SMA = 3 Next number = 5 , SMA = 4 Next number = 5 , SMA = 4.66667 Next number = 4 , SMA = 4.66667 Next number = 3 , SMA = 4 Next number = 2 , SMA = 3 Next number = 1 , SMA = 2 SIMPLE MOVING AVERAGE (procedural): PERIOD = 5 Next number = 1 , SMA = 1 Next number = 2 , SMA = 1.5 Next number = 3 , SMA = 2 Next number = 4 , SMA = 2.5 Next number = 5 , SMA = 3 Next number = 5 , SMA = 3.8 Next number = 4 , SMA = 4.2 Next number = 3 , SMA = 4.2 Next number = 2 , SMA = 3.8 Next number = 1 , SMA = 3 SIMPLE MOVING AVERAGE (class based): PERIOD = 3 Next number = 1 , SMA = 1 Next number = 2 , SMA = 1.5 Next number = 3 , SMA = 2 Next number = 4 , SMA = 3 Next number = 5 , SMA = 4 Next number = 5 , SMA = 4.66667 Next number = 4 , SMA = 4.66667 Next number = 3 , SMA = 4 Next number = 2 , SMA = 3 Next number = 1 , SMA = 2 SIMPLE MOVING AVERAGE (class based): PERIOD = 5 Next number = 1 , SMA = 1 Next number = 2 , SMA = 1.5 Next number = 3 , SMA = 2 Next number = 4 , SMA = 2.5 Next number = 5 , SMA = 3 Next number = 5 , SMA = 3.8 Next number = 4 , SMA = 4.2 Next number = 3 , SMA = 4.2 Next number = 2 , SMA = 3.8 Next number = 1 , SMA = 3
[ $ "bigrat.qky" loadfile ] now! [ over size - space swap of join ] is pad ( $ n --> $ ) [ ' [ stack [ ] ] copy nested ' [ tuck take swap join dup size ] join swap join ' [ > if [ 1 split nip ] tuck swap put 0 over witheach + swap size dip n->v n->v v/ ] join copy ] is make-sma ( n --> [ ) ( behaviour of [ is: n --> n/d ) [ stack ] is sma-3 ( --> s ) 3 make-sma sma-3 put [ stack ] is sma-5 ( --> s ) 5 make-sma sma-5 put say "n sma-3 sma-5" cr cr ' [ 1 2 3 4 5 5 4 3 2 1 ] witheach [ dup echo sp dup sma-3 share do 7 point$ 10 pad echo$ sp sma-5 share do 7 point$ 10 pad echo$ cr ]
n sma-3 sma-51 1 1 2 1.5 1.5 3 2 2 4 3 2.5 5 4 3 5 4.6666667 3.8 4 4.6666667 4.2 3 4 4.2 2 3 3.8 1 2 3
This is easiest done with two functions: one to handle the state (i.e. the numbers already entered), and one to calculate the average.
#concat concatenates the new values to the existing vector of values, then discards any values that are too old.lastvalues<-local({values<-c();function(x,len){values<<-c(values,x);lenv<-length(values);if(lenv>len)values<<-values[(len-lenv):-1]values}})#moving.average accepts a numeric scalars input (and optionally a length, i.e. the number of values to retain) and calculates the stateful moving average.moving.average<-function(latestvalue,len=3){#Check that all inputs are numeric scalarsis.numeric.scalar<-function(x)is.numeric(x)&&length(x)==1Lif(!is.numeric.scalar(latestvalue)||!is.numeric.scalar(len)){stop("all arguments must be numeric scalars")}#Calculate mean of variables so farmean(lastvalues(latestvalue,len))}moving.average(5)# 5moving.average(1)# 3moving.average(-3)# 1moving.average(8)# 2moving.average(7)# 4
#langracket(requiredata/queue)(define(simple-moving-averageperiod)(definequeue(make-queue))(definesum0.0)(lambda(x)(enqueue!queuex)(set!sum(+sumx))(when(>(queue-lengthqueue)period)(set!sum(-sum(dequeue!queue))))(/sum(queue-lengthqueue))));; Tests(definesma3(simple-moving-average3))(definesma5(simple-moving-average5))(for/lists(lst1lst2)([i'(1234554321)])(values(sma3i)(sma5i)))
(formerly Perl 6)
subsma-generator (Int$Pwhere * >0) {sub ($x) {state@a =0xx$P;@a.push($x).shift;@a.sum /$P; }}# Usage:my&sma =sma-generator3;for1,2,3,2,7 {printf"append $_ --> sma = %.2f (with period 3)\n",sma$_;}
append 1 --> sma = 0.33 (with period 3)append 2 --> sma = 1.00 (with period 3)append 3 --> sma = 2.00 (with period 3)append 2 --> sma = 2.33 (with period 3)append 7 --> sma = 4.00 (with period 3)
The same list of numbers was used as in the ALGOL68 example.
The 1st and 2nd periods (number of values) were parametrized, as well as the total number of values.
/*REXX program illustrates and displays a simple moving average using a constructed list*/parseargpqn./*obtain optional arguments from the CL*/ifp==''|p==","thenp=3/*Not specified? Then use the default.*/ifq==''|q==","thenq=5/* " " " " " " */ifn==''|n==","thenn=10/* " " " " " " */@.=0/*default value, only needed for odd N.*/doj=1forn%2;@.j=j/*build 1st half of list, increasing #s*/end/*j*/dok=n%2by-1to1;@.j=k;j=j+1/* " 2nd " " " decreasing " */end/*k*/say' number '" SMA with period"p' '" SMA with period"qsay' ──────── '"───────────────────"'───────────────────'pad=' 'dom=1forn;saycenter(@.m,10)padleft(SMA(p,m),19)left(SMA(q,m),19)end/*m*/exit/*stick a fork in it, we're all done. *//*──────────────────────────────────────────────────────────────────────────────────────*/SMA:procedureexpose@.;parseargp,j;i=0;$=0dok=max(1,j-p+1)toj+pforpwhilek<=j;i=i+1;$=$+@.kend/*k*/return$/i/*SMA ≡ simple moving average. */
number SMA with period 3 SMA with period 5 ──────── ─────────────────── ─────────────────── 1 1 1 2 1.5 1.5 3 2 2 4 3 2.5 5 4 3 5 4.66666667 3.8 4 4.66666667 4.2 3 4 4.2 2 3 3.8 1 2 3
load "stdlib.ring"decimals(8)maxperiod = 20nums = newlist(maxperiod,maxperiod)accum = list(maxperiod)index = list(maxperiod)window = list(maxperiod)for i = 1 to maxperiod index[i] = 1 accum[i] = 0 window[i] = 0nextfor i = 1 to maxperiod for j = 1 to maxperiod nums[i][j] = 0 nextnextfor n = 1 to 5 see "number = " + n + " sma3 = " + left((string(sma(n,3)) + " "),9) + " sma5 = " + sma(n,5) + nlnextfor n = 5 to 1 step -1 see "number = " + n + " sma3 = " + left((string(sma(n,3)) + " "),9) + " sma5 = " + sma(n,5) + nlnextsee nl func sma number, period accum[period] += number - nums[period][index[period]] nums[period][index[period]] = number index[period]= (index[period] + 1) % period + 1 if window[period]<period window[period] += 1 ok return (accum[period] / window[period])
Output:
number = 1 sma3 = 1 sma5 = 1number = 2 sma3 = 1.5000000 sma5 = 1.50000000number = 3 sma3 = 2 sma5 = 2number = 4 sma3 = 3 sma5 = 2.50000000number = 5 sma3 = 4 sma5 = 3number = 5 sma3 = 4.6666666 sma5 = 3.80000000number = 4 sma3 = 4.6666666 sma5 = 4.20000000number = 3 sma3 = 4 sma5 = 4.20000000number = 2 sma3 = 3 sma5 = 3.80000000number = 1 sma3 = 2 sma5 = 3
load "stdlib.ring"decimals(8)maxperiod = 20nums = newlist(maxperiod,maxperiod)accum = list(maxperiod)index = list(maxperiod)window = list(maxperiod)for i = 1 to maxperiod index[i] = 1 accum[i] = 0 window[i] = 0nextfor i = 1 to maxperiod for j = 1 to maxperiod nums[i][j] = 0 nextnextfor n = 1 to 5 see "number = " + n + " sma3 = " + left((string(sma(n,3)) + " "),9) + " sma5 = " + sma(n,5) + nlnextfor n = 5 to 1 step -1 see "number = " + n + " sma3 = " + left((string(sma(n,3)) + " "),9) + " sma5 = " + sma(n,5) + nlnextsee nl func sma number, periodaccum[period] += number - nums[period][index[period]]nums[period][index[period]] = numberindex[period]= (index[period] + 1) % period + 1if window[period]<period window[period] += 1 okreturn (accum[period] / window[period])
Output:
number = 1 sma3 = 1 sma5 = 1number = 2 sma3 = 1.5000000 sma5 = 1.50000000number = 3 sma3 = 2 sma5 = 2number = 4 sma3 = 3 sma5 = 2.50000000number = 5 sma3 = 4 sma5 = 3number = 5 sma3 = 4.6666666 sma5 = 3.80000000number = 4 sma3 = 4.6666666 sma5 = 4.20000000number = 3 sma3 = 4 sma5 = 4.20000000number = 2 sma3 = 3 sma5 = 3.80000000number = 1 sma3 = 2 sma5 = 3
### RING: Function Moving Average. Bert Mariani 2016-06-22###------------------------------### Data array of Google pricesaGOOGPrices = ["658","675","670","664","664","663","663","662","675","693","689","675","636","633","632","607","607","617","617","581","593","570","574","571","575","596","596","601","583","635","587","574","552","531","536","502","488","482","490","503","507","521","534","525","534","559","552","554","555","555","552","579","580","577","575","562","560","559","558","569","573","577","574","559","552","553","560","569","582","579","593","598","593","598","593","586","602","591","594","595","603","614","620","625","635","627","632","631","620","626","616","606","602","659","683","671","670","659","673","679"]###-------------------------------------------------------------### CALL the Function: MovingAverage arrayOfPrices timePeriodaGOOGMvgAvg = MovingAverage( aGOOGPrices, 10 )aGOOGMvgAvg = MovingAverage( aGOOGPrices, 30 )###-------------------------------------------------------------### FUNCTION: MovingAverage Func MovingAverage arrayPrices, timePeriod arrayMvgAvg = [] ### Output Results to this array z = len(arrayPrices) ### array data length sumPrices = 0 ###-------------------------------- ### First MAvg Sum 1 to timePeriod ###-------------------------------- for i = 1 to timePeriod sumPrices = sumPrices + arrayPrices[i] mvgAvg = sumPrices / i Add( arrayMvgAvg, mvgAvg) next ###----------------------------------------------- ### Second MAvg Sum timePeriod +1 to End of Data ###----------------------------------------------- for i = timePeriod + 1 to z sumPrices = sumPrices - arrayPrices[i-timePeriod] + arrayPrices[i] mvgAvg = sumPrices / timePeriod Add (arrayMvgAvg, mvgAvg next return arrayMvgAvg###-------------------------------------------------------------OUTPUT Google Prices moving average using timePeriod = 10Index 88 CurPrice 631 Sum 17735 MvgAvg 591.17Index 89 CurPrice 620 Sum 17797 MvgAvg 593.23Index 90 CurPrice 626 Sum 17854 MvgAvg 595.13Index 91 CurPrice 616 Sum 17897 MvgAvg 596.57Index 92 CurPrice 606 Sum 17926 MvgAvg 597.53Index 93 CurPrice 602 Sum 17954 MvgAvg 598.47Index 94 CurPrice 659 Sum 18054 MvgAvg 601.80Index 95 CurPrice 683 Sum 18185 MvgAvg 606.17Index 96 CurPrice 671 Sum 18303 MvgAvg 610.10Index 97 CurPrice 670 Sum 18413 MvgAvg 613.77Index 98 CurPrice 659 Sum 18503 MvgAvg 616.77Index 99 CurPrice 673 Sum 18594 MvgAvg 619.80Index 100 CurPrice 679 Sum 18694 MvgAvg 623.13###-------------------------------------------------------------
« IF DUP 0 >THEN 1 →LIST 'SMAPAR' STOELSE "Period must be >0" MSGBOXEND» 'CLRSMA' STO« 'SMAPAR' SWAP STO+SMAPAR TAIL DUP SIZE DUPSMAPAR HEAD - 1 + 0 MAX SWAP SUB SIZE LASTARG 0 + ∑LIST SWAP /» 'SMA' STO
3CLRSMA25SMA 75SMA 100SMA 125SMA
4: 253: 50 2: 66.66666666671: 100
A closure:
defsimple_moving_average(size)nums=[]sum=0.0lambdado|hello|nums<<hellogoodbye=nums.length>size?nums.shift:0sum+=hello-goodbyesum/nums.lengthendendma3=simple_moving_average(3)ma5=simple_moving_average(5)(1.upto(5).to_a+5.downto(1).to_a).eachdo|num|printf"Next number = %d, SMA_3 = %.3f, SMA_5 = %.1f\n",num,ma3.call(num),ma5.call(num)end
A class
classMovingAveragerdefinitialize(size)@size=size@nums=[]@sum=0.0enddef<<(hello)@nums<<hellogoodbye=@nums.length>@size?@nums.shift:0@sum+=hello-goodbyeselfenddefaverage@sum/@nums.lengthendaliasto_faveragedefto_saverage.to_sendendma3=MovingAverager.new(3)ma5=MovingAverager.new(5)(1.upto(5).to_a+5.downto(1).to_a).eachdo|num|printf"Next number = %d, SMA_3 = %.3f, SMA_5 = %.1f\n",num,ma3<<num,ma5<<numendend
data 1,2,3,4,5,5,4,3,2,1dim sd(10) ' series data global sd ' make it global so we all see itfor i = 1 to 10:read sd(i): next ix = sma(3) ' simple moving average for 3 periodsx = sma(5) ' simple moving average for 5 periodsfunction sma(p) ' the simple moving average functionprint "----- SMA:";p;" -----" for i = 1 to 10 sumSd = 0 for j = max((i - p) + 1,1) to i sumSd = sumSd + sd(j) ' sum series data for the period next j if p > i then p1 = i else p1 = p print sd(i);" sma:";p;" ";sumSd / p1 next iend function
----- SMA:3 -----1 sma:3 12 sma:3 1.53 sma:3 24 sma:3 35 sma:3 45 sma:3 4.66666654 sma:3 4.66666653 sma:3 42 sma:3 31 sma:3 2----- SMA:5 -----1 sma:5 12 sma:5 1.53 sma:5 24 sma:5 2.55 sma:5 35 sma:5 3.799999954 sma:5 4.19999983 sma:5 4.19999982 sma:5 3.799999951 sma:5 3
structSimpleMovingAverage{period:usize,numbers:Vec<usize>}implSimpleMovingAverage{fnnew(p:usize)->SimpleMovingAverage{SimpleMovingAverage{period:p,numbers:Vec::new()}}fnadd_number(&mutself,number:usize)->f64{self.numbers.push(number);ifself.numbers.len()>self.period{self.numbers.remove(0);}ifself.numbers.is_empty(){return0f64;}else{letsum=self.numbers.iter().fold(0,|acc,x|acc+x);returnsumasf64/self.numbers.len()asf64;}}}fnmain(){forperiodin[3,5].iter(){println!("Moving average with period {}",period);letmutsma=SimpleMovingAverage::new(*period);foriin[1,2,3,4,5,5,4,3,2,1].iter(){println!("Number: {} | Average: {}",i,sma.add_number(*i));}}}
usestd::collections::VecDeque;structSimpleMovingAverage{period:usize,numbers:VecDeque<usize>}implSimpleMovingAverage{fnnew(p:usize)->SimpleMovingAverage{SimpleMovingAverage{period:p,numbers:VecDeque::new()}}fnadd_number(&mutself,number:usize)->f64{self.numbers.push_back(number);ifself.numbers.len()>self.period{self.numbers.pop_front();}ifself.numbers.is_empty(){return0f64;}else{letsum=self.numbers.iter().fold(0,|acc,x|acc+x);returnsumasf64/self.numbers.len()asf64;}}}fnmain(){forperiodin[3,5].iter(){println!("Moving average with period {}",period);letmutsma=SimpleMovingAverage::new(*period);foriin[1,2,3,4,5,5,4,3,2,1].iter(){println!("Number: {} | Average: {}",i,sma.add_number(*i));}}}
Moving average with period 3Number: 1 | Average: 1Number: 2 | Average: 1.5Number: 3 | Average: 2Number: 4 | Average: 3Number: 5 | Average: 4Number: 5 | Average: 4.666666666666667Number: 4 | Average: 4.666666666666667Number: 3 | Average: 4Number: 2 | Average: 3Number: 1 | Average: 2Moving average with period 5Number: 1 | Average: 1Number: 2 | Average: 1.5Number: 3 | Average: 2Number: 4 | Average: 2.5Number: 5 | Average: 3Number: 5 | Average: 3.8Number: 4 | Average: 4.2Number: 3 | Average: 4.2Number: 2 | Average: 3.8Number: 1 | Average: 3
classMovingAverage(period:Int){privatevarqueue=newscala.collection.mutable.Queue[Double]()defapply(n:Double)={queue.enqueue(n)if(queue.size>period)queue.dequeuequeue.sum/queue.size}overridedeftoString=queue.mkString("(",", ",")")+", period "+period+", average "+(queue.sum/queue.size)defclear=queue.clear}
scala> List(3,5) foreach { period => | println("SIMPLE MOVING AVERAGE: PERIOD = "+period) | val sma = new MovingAverage(period) | 1.0 to 5.0 by 1.0 foreach {i => println(" Next number = %-2g, SMA = %g " format (i, sma(i)))} | 5.0 to 1.0 by -1.0 foreach {i => println(" Next number = %-2g, SMA = %g " format (i, sma(i)))} | println(sma+"\n") | }SIMPLE MOVING AVERAGE: PERIOD = 3 Next number = 1.00000, SMA = 1.00000 Next number = 2.00000, SMA = 1.50000 Next number = 3.00000, SMA = 2.00000 Next number = 4.00000, SMA = 3.00000 Next number = 5.00000, SMA = 4.00000 Next number = 5.00000, SMA = 4.66667 Next number = 4.00000, SMA = 4.66667 Next number = 3.00000, SMA = 4.00000 Next number = 2.00000, SMA = 3.00000 Next number = 1.00000, SMA = 2.00000(3.0, 2.0, 1.0), period 3, average 2.0SIMPLE MOVING AVERAGE: PERIOD = 5 Next number = 1.00000, SMA = 1.00000 Next number = 2.00000, SMA = 1.50000 Next number = 3.00000, SMA = 2.00000 Next number = 4.00000, SMA = 2.50000 Next number = 5.00000, SMA = 3.00000 Next number = 5.00000, SMA = 3.80000 Next number = 4.00000, SMA = 4.20000 Next number = 3.00000, SMA = 4.20000 Next number = 2.00000, SMA = 3.80000 Next number = 1.00000, SMA = 3.00000(5.0, 4.0, 3.0, 2.0, 1.0), period 5, average 3.0(define((simple-moving-averagersize.nums)num)(set!nums(consnum(if(=(lengthnums)size)(reverse(cdr(reversenums)))nums)))(/(apply+nums)(lengthnums)))(defineav(simple-moving-averager3))(mapav'(1234554321))
(1 3/2 2 3 4 14/3 14/3 4 3 2)
Implemented with closures:
funcsimple_moving_average(period){varlist=[]varsum=0func(number){list.append(number)sum+=numberif(list.len>period){sum-=list.shift}(sum/list.length)}}varma3=simple_moving_average(3)varma5=simple_moving_average(5)fornum(1..5,flip(1..5)){printf("Next number = %d, SMA_3 = %.3f, SMA_5 = %.1f\n",num,ma3.call(num),ma5.call(num))}
Implemented as a class:
classsma_generator(period,list=[],sum=0){methodSMA(number){list.append(number)sum+=numberif(list.len>period){sum-=list.shift}(sum/list.len)}}varma3=sma_generator(3)varma5=sma_generator(5)fornum(1..5,flip(1..5)){printf("Next number = %d, SMA_3 = %.3f, SMA_5 = %.1f\n",num,ma3.SMA(num),ma5.SMA(num))}
Next number = 1, SMA_3 = 1.000, SMA_5 = 1.0Next number = 2, SMA_3 = 1.500, SMA_5 = 1.5Next number = 3, SMA_3 = 2.000, SMA_5 = 2.0Next number = 4, SMA_3 = 3.000, SMA_5 = 2.5Next number = 5, SMA_3 = 4.000, SMA_5 = 3.0Next number = 5, SMA_3 = 4.667, SMA_5 = 3.8Next number = 4, SMA_3 = 4.667, SMA_5 = 4.2Next number = 3, SMA_3 = 4.000, SMA_5 = 4.2Next number = 2, SMA_3 = 3.000, SMA_5 = 3.8Next number = 1, SMA_3 = 2.000, SMA_5 = 3.0
Objectsubclass:MovingAverage [|valueCollection period collectedNumber sum|MovingAverageclass>>newWithPeriod:thePeriod [|r|r:=superbasicNew.^rinitWithPeriod:thePeriod ]initWithPeriod:thePeriod [valueCollection:=OrderedCollectionnew:thePeriod.period:=thePeriod.collectedNumber:=0.sum:=0 ]sma [collectedNumber<periodifTrue: [^sum/collectedNumber ]ifFalse: [^sum/period ] ]add:value [collectedNumber<periodifTrue: [sum:=sum+value.valueCollectionadd:value.collectedNumber:=collectedNumber+1.]ifFalse: [sum:=sum- (valueCollectionremoveFirst).sum:=sum+value.valueCollectionadd:value].^selfsma ]].
|sma3 sma5|sma3:=MovingAveragenewWithPeriod:3.sma5:=MovingAveragenewWithPeriod:5.#(1234554321)do: [:v| ('Next number %1, SMA_3 = %2, SMA_5 = %3'% {v. (sma3add:v)asFloat. (sma5add:v)asFloat })displayNl]
(* helper functions *)valsum=List.foldl(op+)0.0funmeanxs=(sumxs)/(Real.fromInt(lengthxs))typesmastate=int*reallist(* initialize an SMA state with the given period *)funsmaInitperiod:smastate=(period,[])(* update the SMA in the given state with the given number *)(* returns a tuple containing the new SMA and the new state *)funsma(state:smastate)(num:real):(real*smastate)=letval(period,buffer)=statevalamt=Int.min(1+List.lengthbuffer,period)valnewlist=List.take(num::buffer,amt)in(meannewlist,(period,newlist))endfunprintSMA'_[]=()|printSMA'state(x::xs)=letval(n,next)=smastatexinprint("Added "^(Real.toStringx)^": SMA="^(Real.toStringn)^"\n");printSMA'nextxsend(* print the SMA with given period at each step over the list xs *)funprintSMAperiodxs=printSMA'(smaInitperiod)xs
> printSMA 3 [1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 4.0, 3.0, 2.0, 1.0]);Added 1.0: SMA=1.0Added 2.0: SMA=1.5Added 3.0: SMA=2.0Added 4.0: SMA=3.0Added 5.0: SMA=4.0Added 5.0: SMA=4.66666666667Added 4.0: SMA=4.66666666667Added 3.0: SMA=4.0Added 2.0: SMA=3.0Added 1.0: SMA=2.0val it = (): unit> printSMA 5 (map Real.fromInt [1,2,3,4,5,5,4,3,2,1]);Added 1.0: SMA=1.0Added 2.0: SMA=1.5Added 3.0: SMA=2.0Added 4.0: SMA=2.5Added 5.0: SMA=3.0Added 5.0: SMA=3.8Added 4.0: SMA=4.2Added 3.0: SMA=4.2Added 2.0: SMA=3.8Added 1.0: SMA=3.0val it = (): unit
structSimpleMovingAverage{varperiod:Intvarnumbers=[Double]()mutatingfuncaddNumber(_n:Double)->Double{numbers.append(n)ifnumbers.count>period{numbers.removeFirst()}guard!numbers.isEmptyelse{return0}returnnumbers.reduce(0,+)/Double(numbers.count)}}forperiodin[3,5]{print("Moving average with period\(period)")varaverager=SimpleMovingAverage(period:period)fornin[1.0,2,3,4,5,5,4,3,2,1]{print("n:\(n); average\(averager.addNumber(n))")}}
Moving average with period 3n: 1.0; average 1.0n: 2.0; average 1.5n: 3.0; average 2.0n: 4.0; average 3.0n: 5.0; average 4.0n: 5.0; average 4.666666666666667n: 4.0; average 4.666666666666667n: 3.0; average 4.0n: 2.0; average 3.0n: 1.0; average 2.0Moving average with period 5n: 1.0; average 1.0n: 2.0; average 1.5n: 3.0; average 2.0n: 4.0; average 2.5n: 5.0; average 3.0n: 5.0; average 3.8n: 4.0; average 4.2n: 3.0; average 4.2n: 2.0; average 3.8n: 1.0; average 3.0
or
oo::classcreateSimpleMovingAverage{variablevalsidxconstructor{{period3}}{setidxend-[expr{$period-1}]setvals{}}methodvalx{setvals[lrange[list{*}$vals$x]$idxend]expr{[tcl::mathop::+{*}$vals]/double([llength$vals])}}}
Demonstration:
SimpleMovingAveragecreateaverager3SimpleMovingAveragecreateaverager55foreachn{1234554321}{puts"Next number = $n, SMA_3 = [averager3 val $n], SMA_5 = [averager5 val $n]"}
Next number = 1, SMA_3 = 1.0, SMA_5 = 1.0Next number = 2, SMA_3 = 1.5, SMA_5 = 1.5Next number = 3, SMA_3 = 2.0, SMA_5 = 2.0Next number = 4, SMA_3 = 3.0, SMA_5 = 2.5Next number = 5, SMA_3 = 4.0, SMA_5 = 3.0Next number = 5, SMA_3 = 4.666666666666667, SMA_5 = 3.8Next number = 4, SMA_3 = 4.666666666666667, SMA_5 = 4.2Next number = 3, SMA_3 = 4.0, SMA_5 = 4.2Next number = 2, SMA_3 = 3.0, SMA_5 = 3.8Next number = 1, SMA_3 = 2.0, SMA_5 = 3.0
Continuously prompts for an inputI, which is added to the end of a listL1. L1 can be found by pressing "2ND"/"1", andmean can be found in "List"/"OPS"
PressON to terminate the program.
:1->C:While 1:Prompt I:C->dim(L1):I->L1(C):Disp mean(L1):1+C->C:End
Function that returns a list containing the averaged data of the supplied argument
movinavg(list,p)Func Local r, i, z For i,1,dim(list) max(i-p,0)→z sum(mid(list,z+1,i-z))/(i-z)→r[i] EndFor rEndFunc
Program that returns a simple value at each invocation:
movinav2(x_,v_)Prgm If getType(x_)="STR" Then {}→list v_→p Return EndIf right(augment(list,{x_}),p)→list sum(list)/dim(list)→#v_EndPrgmExample1: Using the function
movinavg({1,2,3,4,5,6,7,8,9,10},5)
list is the list being averaged: {1,2,3,4,5,6,7,8,9,10}
p is the period: 5
returns the averaged list: {1, 3/2, 2, 5/2, 3, 4, 5, 6, 7, 8}
Example 2: Using the program
movinav2("i",5) - Initializing moving average calculation, and define period of 5
movinav2(3, "x"):x - new data in the list (value 3), and result will be stored on variable x, and displayed
movinav2(4, "x"):x - new data (value 4), and the new result will be stored on variable x, and displayed (4+3)/2
...
Description of the function movinavg:
variable r - is the result (the averaged list) that will be returned
variable i - is the index variable, and it points to the end of the sub-list the list being averaged.
variable z - an helper variable
The function uses variable i to determine which values of the list will be considered in the next average calculation.
At every iteration, variable i points to the last value in the list that will be used in the average calculation.
So we only need to figure out which will be the first value in the list.
Usually we'll have to consider p elements, so the first element will be the one indexed by (i-p+1).
However on the first iterations that calculation will usually be negative, so the following equation will avoid negative indexes: max(i-p+1,1) or, arranging the equation, max(i-p,0)+1.
But the number of elements on the first iterations will also be smaller, the correct value will be (end index - begin index + 1) or, arranging the equation, (i - (max(i-p,0)+1) +1) ,and then, (i-max(i-p,0)).
Variable z holds the common value (max(i-p),0) so the begin_index will be (z+1) and the number_of_elements will be (i-z)
mid(list,z+1, i-z) will return the list of value that will be averaged
sum(...) will sum them
sum(...)/(i-z) → r[i] will average them and store the result in the appropriate place in the result list
This is a "simple" moving average.
Classsma'to be stored in a class module with name "sma"PrivatenAsInteger'periodPrivatearr()AsDouble'circular listPrivateindexAsInteger'pointer into arrPrivateoldsmaAsDoublePublicSubinit(sizeAsInteger)n=sizeReDimarr(n-1)index=0EndSubPublicFunctionsma(numberAsDouble)AsDoublesma=oldsma+(-arr(index)+number)/noldsma=smaarr(index)=numberindex=(index+1)ModnEndFunctionNormalmodulePublicSubmain()s=[{1,2,3,4,5,5,4,3,2,1}]Dimsma3AsNewsmaDimsma5AsNewsmasma3.init3sma5.init5Fori=1ToUBound(s)Debug.Printi,Format(sma3.sma(CDbl(s(i))),"0.00000"),Debug.PrintFormat(sma5.sma(CDbl(s(i))),"0.00000")NextiEndSub
1 0,33333 0,20000 2 1,00000 0,60000 3 2,00000 1,20000 4 3,00000 2,00000 5 4,00000 3,00000 6 4,66667 3,80000 7 4,66667 4,20000 8 4,00000 4,20000 9 3,00000 3,80000 10 2,00000 3,00000
data="1,2,3,4,5,5,4,3,2,1"token=Split(data,",")stream=""WScript.StdOut.WriteLine"Number"&vbTab&"SMA3"&vbTab&"SMA5"Forj=LBound(token)ToUBound(token)IfLen(stream)=0Thenstream=token(j)Elsestream=stream&","&token(j)EndIfWScript.StdOut.WriteLinetoken(j)&vbTab&Round(SMA(stream,3),2)&vbTab&Round(SMA(stream,5),2)NextFunctionSMA(s,p)IfLen(s)=0ThenSMA=0ExitFunctionEndIfd=Split(s,",")sum=0IfUBound(d)+1>=pThenc=0Fori=UBound(d)ToLBound(d)Step-1sum=sum+Int(d(i))c=c+1Ifc=pThenExitForEndIfNextSMA=sum/pElseFori=UBound(d)ToLBound(d)Step-1sum=sum+Int(d(i))NextSMA=sum/(UBound(d)+1)EndIfEndFunction
Number SMA3 SMA511121.51.5322432.554354.67 3.844.67 4.2344.2233.8123
fn sma(period int) fn(f64) f64 { mut i := int(0) mut sum := f64(0) mut storage := []f64{len: 0, cap:period} return fn[mut storage, mut sum, mut i, period](input f64) f64 { if storage.len < period { sum += input storage << input } sum += input - storage[i] storage[i], i = input, (i+1)%period return sum / f64(storage.len) }} fn main() { sma3 := sma(3) sma5 := sma(5) println("x sma3 sma5") for x in [f64(1), 2, 3, 4, 5, 5, 4, 3, 2, 1] { println("${x:5.3f} ${sma3(x):5.3f} ${sma5(x):5.3f}") }}x sma3 sma51.000 1.000 1.0002.000 1.500 1.5003.000 2.000 2.0004.000 3.000 2.5005.000 4.000 3.0005.000 4.667 3.8004.000 4.667 4.2003.000 4.000 4.2002.000 3.000 3.8001.000 2.000 3.000
import"./fmt"forFmtvarsma=Fn.new{|period|vari=0varsum=0varstorage=[]returnFn.new{|input|if(storage.count<period){sum=sum+inputstorage.add(input)}sum=sum+input-storage[i]storage[i]=inputi=(i+1)%periodreturnsum/storage.count}}varsma3=sma.call(3)varsma5=sma.call(5)System.print(" x sma3 sma5")for(xin[1,2,3,4,5,5,4,3,2,1]){Fmt.precision=3Fmt.print("$5f $5f $5f",x,sma3.call(x),sma5.call(x))}
x sma3 sma51.000 1.000 1.0002.000 1.500 1.5003.000 2.000 2.0004.000 3.000 2.5005.000 4.000 3.0005.000 4.667 3.8004.000 4.667 4.2003.000 4.000 4.2002.000 3.000 3.8001.000 2.000 3.000
fcn SMA(P){ fcn(n,ns,P){ sz:=ns.append(n.toFloat()).len(); if(P>sz) return(0.0); if(P<sz) ns.del(0); ns.sum(0.0)/P; }.fp1(List.createLong(P+1),P) // pre-allocate a list of length P+1}fp1 creates a partial application fixing the (in this case) the second and thirdparameters
T(1,2,3,4,5,5,4,3,2,1).apply(SMA(3)).println();T(1,2,3,4,5,5,4,3,2,1).apply(SMA(5)).println();
L(0,0,2,3,4,4.66667,4.66667,4,3,2)L(0,0,0,0,3,3.8,4.2,4.2,3.8,3)