module aggregator;

import std.algorithm, std.math, std.range, std.traits;

struct Aggregator
{
	private:
		size_t _count = 0;
		real _mean = 0;
		real _var = 0;
		real _min = real.max;
		real _max = real.min;

	public:
		size_t count() @property pure nothrow const
		{
			return _count;
		}

		real mean() @property pure nothrow const
		{
			return _mean;
		}

		real sampleVar() @property pure nothrow const
		{
			return _var / (_count - 1);
		}

		real sampleSD() @property pure nothrow const
		{
			return sqrt(this.sampleVar);
		}

		real standardVar() @property pure nothrow const
		{
			return _var / _count;
		}

		real standardSD() @property pure nothrow const
		{
			return sqrt(this.standardVar);
		}

		real min() @property pure nothrow const
		{
			return _min;
		}

		real max() @property pure nothrow const
		{
			return _max;
		}

		void add(T)(T x) @property pure nothrow
			if(isNumeric!T)
		{
			_count++;
			_min = std.algorithm.min(_min, x);
			_max = std.algorithm.max(_max, x);
			_var = _var + ((_count - 1) * (x - _mean) ^^ 2) / _count;
			_mean = _mean + (x - _mean) / _count;
		}

		void add(T)(T r)
			if(isInputRange!T)
		{
			foreach(x; r)
				this.add(x);
		}
}

unittest
{
	Aggregator a;

	foreach(i; iota(1, 11))
		a.add(i);

	assert(a.count == 10);
	assert(a.mean == reduce!"a+b"(iota(0, 11)) / 10.0L);
	assert(a.min == 1);
	assert(a.max == 10);
	real testVar = reduce!((x, y) => x + (y - a.mean) ^^ 2)(iota(0.0L, 11.0L));
	assert(a.sampleVar == testVar / (a.count - 1));
	assert(a.sampleSD == sqrt(testVar / (a.count - 1)));
	assert(a.standardVar == testVar / a.count);
	assert(a.standardSD == sqrt(testVar / a.count));

	Aggregator b;
	b.add([10_000, 10_001, 10_002]);
	assert(b.mean == 10_001);
	assert(b.sampleVar == 1);

	Aggregator c;
	c.add([[1, 2, 3], [4, 5, 6], [7, 8, 9, 10]]);
	assert(c.mean == a.mean);
	assert(c.sampleVar == a.sampleVar);
	assert(c.sampleSD == a.sampleSD);
	assert(c.standardVar == a.standardVar);
	assert(c.standardSD == a.standardSD);
	assert(c.min == a.min);
	assert(c.max == a.max);
	assert(c.count == a.count);
}

