-2

We have sampling data. on each time we have a different sampling value:

We have sampling data. on each time we have a different sampling value.

Each sample is (time, value) -> list of samples [(t1,v1),...(tn,vn)]

Our job is to implement moving_avg(data, window_sec). Data -> list of samples [(t1,v1),...(tn,vn)] Windows_secs -> size of window in secs

Example input: data = [(1,2),(2,4),(3,3),(4,2),(6,8),(8,2),(12,1)]

moving_avg(data, 2) returns: [(1,2),(2,3),(3,3),(4,3),(6,5),(8,5),(12,1)]

moving_avg(data, 3) returns: [(1,2),(2,3),(3,3),(4,2.75),(6,4.33),(8,5),(12,1)]

How do I implement it in C# with the best time complexity? the interviewer told me I cannot use an additional space (I used in the interview in Dictionary)

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
hilas57
  • 1
  • 1

1 Answers1

1

Assuming that successive samples with always have greater t values, a simple list of prior items should be sufficient to handle the task. Add each incoming value to the buffer, remove any with times outside of the window, then average the values.

Something like this:

public class TimedValue
{
    public readonly int Time;
    public readonly double Value;
    
    public TimedValue(int time, double value)
    {
        Time = time;
        Value = value;
    }
}

public static partial class Extensions
{
    public static IEnumerable<TimedValue> MovingAverage(this IEnumerable<TimedValue> source, int numSeconds)
    {
        var buffer = new List<TimedValue>();
        
        foreach (var item in source)
        {
            // Add next item to the buffer
            buffer.Add(item);

            // Remove expired items from buffer
            int limit = item.Time - numSeconds;
            while (buffer.Count > 0 && buffer[0].Time < limit)
                buffer.RemoveAt(0);

            // Calculate average and yield back
            double average = buffer.Average(i => i.Value);
            yield return new TimedValue(item.Time, average);
        }
    }
}

Not sure what you meant by "non serially" in your title, but if you don't want to do this with IEnumerable<> and Linq extensions (which aren't always the most effective way) then there are other ways.

One method is to allocate the result buffer and fill it as you iterate through the source data. Use a head pointer for the current item, a tail pointer for the oldest included item, add and subtract from a sum... and so on. It looks something like:

static TimedValue[] MakeMovingAverage(TimedValue[] source, int numSeconds)
{
    // Allocate the result buffer
    var result = new TimedValue[source.Length];
    
    // index of the first item to average
    int tail = 0;
    // the running total for the window
    double sum = 0;
    for (int head = 0; head < source.Length; head++)
    {
        // Add the current item to the running total
        sum += source[head].Value;
        
        // Remove values from the running total for expired items
        var limit = source[head].Time - numSeconds;         
        while (source[tail].Time < limit)
            sum -= source[tail++].Value;
        
        // Set windowed average for this item
        var average = sum / (head + 1 - tail);
        result[head] = new TimedValue(source[head].Time, average);
    }
    return result;
}

For an array input this is more efficient, since each input item is processed only once.

Corey
  • 15,524
  • 2
  • 35
  • 68