C++: Simple Moving Average

Bjarne-stroustrup
 

Computing the simple moving average of a series of numbers.

The task is to:

Create a stateful 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.

Description
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:

  • The period, P
  • An ordered container of at least the last P numbers from each of its individual calls.

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.

Pseudocode 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

#include <iostream>
#include <stddef.h>
#include <assert.h>

using std::cout;
using std::endl;

class SMA {
public:
	SMA(unsigned int period) :
	period(period), window(new double[period]), head(NULL), tail(NULL),
	total(0) {
		assert(period >= 1);
	}
	~SMA() {
		delete[] window;
	}

	// Adds a value to the average, pushing one out if nescessary
	void add(double val) {
		// Special case: Initialization
		if (head == NULL) {
			head = window;
			*head = val;
			tail = head;
			inc(tail);
			total = val;
			return;
		}

		// Were we already full?
		if (head == tail) {
			// Fix total-cache
			total -= *head;
			// Make room
			inc(head);
		}

		// Write the value in the next spot.
		*tail = val;
		inc(tail);

		// Update our total-cache
		total += val;
	}

	// Returns the average of the last P elements added to this SMA.
	// If no elements have been added yet, returns 0.0
	double avg() const {
		ptrdiff_t size = this->size();
		if (size == 0) {
			return 0; // No entries => 0 average
		}
		return total / (double) size; // Cast to double for floating point arithmetic
	}

private:
	unsigned int period;
	double * window; // Holds the values to calculate the average of.

	// Logically, head is before tail
	double * head; // Points at the oldest element we've stored.
	double * tail; // Points at the newest element we've stored.

	double total; // 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.
	void inc(double * & p) {
		if (++p >= window + period) {
			p = window;
		}
	}

	// Returns how many numbers we have stored.
	ptrdiff_t size() const {
		if (head == NULL)
		return 0;
		if (head == tail)
		return period;
		return (period + tail - head) % period;
	}
};

int main(int argc, char * * argv) {
	SMA foo(3);
	SMA bar(5);

	int data[] = { 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;
	}

	return 0;
}

SOURCE

Content is available under GNU Free Documentation License 1.2.