Sabre
Sabre

Reputation: 2400

Most efficient method for averaging list<int>

I am keeping a rolling accumulator for a graphing application, that one of the features is providing a running average of the sample.

The size of the accumulator is variable, but essentially the end goal is achieved like this.

The accumulator class (ugly but functional)

    public class accumulator<t>
    {

        private int trim;

        List<t> _points = new List<t>();
        List<string> _labels = new List<string>();
        List<t> _runAvg = new List<t>();
        List<t> _high = new List<t>();
        List<t> _low = new List<t>();

        public List<t> points { get { return _points; } }
        public List<string> labels { get { return _labels; } }
        public List<t> runAvg { get { return _runAvg; } }
        public List<t> high { get { return _high; } }
        public List<t> low { get { return _low; } }

        public delegate void onChangeHandler(accumulator<t> sender, EventArgs e);
        public event onChangeHandler onChange;

        public accumulator(int trim)
        {
            this.trim = trim;
        }

        public void add(t point, string label)
        {
            if (_points.Count == trim)
            {
                _points.RemoveAt(0);
                _labels.RemoveAt(0);
                _runAvg.RemoveAt(0);
                _high.RemoveAt(0);
                _low.RemoveAt(0);
            }

            _points.Add(point);
            _labels.Add(label);

            if (typeof(t) == typeof(System.Int32))
            {
                int avg = 0;
                if (_high.Count == 0)
                {
                    _high.Add(point);
                }
                else
                {
                    t v = (Convert.ToInt32(point) > Convert.ToInt32(_high[0])) ? point : _high[0];
                    _high.Clear();
                    for (int i = 0; i < _points.Count; i++) _high.Add(v);
                }

                if (_low.Count == 0)
                {
                    _low.Add(point);
                }
                else
                {
                    t v = (Convert.ToInt32(point) < Convert.ToInt32(_low[0])) ? point : _low[0];
                    _low.Clear();
                    for (int i = 0; i < _points.Count; i++) _low.Add(v);
                }

                foreach (t item in _points) avg += Convert.ToInt32(item);
                avg = (avg / _points.Count);
                _runAvg.Add((t)(object)avg);
                //_runAvg.Add((t)(object)_points.Average(a => Convert.ToInt32(a)));
            }

            if (typeof(t) == typeof(System.Double))
            {
                double avg = 0;
                if (_high.Count == 0)
                {
                    _high.Add(point);
                }
                else
                {
                    t v = (Convert.ToDouble(point) > Convert.ToDouble(_high[0])) ? point : _high[0];
                    _high.Clear();
                    for (int i = 0; i < _points.Count; i++) _high.Add(v);
                }

                if (_low.Count == 0)
                {
                    _low.Add(point);
                }
                else
                {
                    t v = (Convert.ToDouble(point) < Convert.ToDouble(_low[0])) ? point : _low[0];
                    _low.Clear();
                    for (int i = 0; i < _points.Count; i++) _low.Add(v);
                }

                foreach (t item in _points) avg += Convert.ToDouble(item);
                avg = (avg / _points.Count);
                _runAvg.Add((t)(object)avg);
                //_runAvg.Add((t)(object)_points.Average(a => Convert.ToDouble(a)));
            }
            onChangeHappen();
        }

        private void onChangeHappen()
        {
            if (onChange != null) onChange(this, EventArgs.Empty);
        }
    }

As you can see essentially I want to keep a running average, a High/Low mark (with the same count of data points so it binds directly to the chart control in an extended series class)

The average is I think what is killing me, to have to add every element of the list to a sum / divide by the count is of course how an average is achieved, but is the loop the most efficient way to do this?

I took a stab at a lambda expression (commented out) but figured on some level it had to be doing the same. (Sort of like using the generic list VS array, I figure it has to be re declaring an array somewhere and moving elements around, but it is likely being done as or more efficient than I would have, so let it do it for convenience's sake)

The ultimate goal and final question being really, given a list of generic values...

The most efficient way to average the whole list.

*Note: I DO realize the pedantic nature of typing the int/floats, this is because of type checking in the consumer of this class over which I have no control, it actually looks to see if it is double/int otherwise I would have treated it ALL as floats ;)

Thanks in advance...

Upvotes: 1

Views: 139

Answers (1)

BradleyDotNET
BradleyDotNET

Reputation: 61369

Keeping a running average is actually really easy. You don't need to sum the whole list every time, because you already have it!

Once you have a current average (from your loop), you just do the following:

((oldAverage * oldCount) + newValue) / newCount

This will give you the average of the old set with the new value(s) included.

To get the initial average value, consider using the Average function from LINQ:

double average = listOfInts.Average();

Upvotes: 4

Related Questions