【问题标题】:Moving Average non serially非连续移动平均线
【发布时间】:2021-08-22 07:31:16
【问题描述】:

我们有抽样数据。每次我们都有不同的采样值:

每个样本是(时间,值) -> 样本列表 [(t1,v1),...(tn,vn)]

我们的工作是实现moving_avg(data, window_sec)。 数据 -> 样本列表 [(t1,v1),...(tn,vn)] Windows_secs -> 以秒为单位的窗口大小

示例输入: 数据 = [(1,2),(2,4),(3,3),(4,2),(6,8),(8,2),(12,1)]

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

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

如何在 C# 中以最佳时间复杂度实现它?面试官告诉我我不能使用额外的空间(我在字典中的面试中使用过)

【问题讨论】:

    标签: c# moving-average


    【解决方案1】:

    假设连续的样本总是有更大的t 值,一个简单的先前项目列表应该足以处理任务。将每个传入的值添加到缓冲区,删除窗口外的任何时间,然后对这些值进行平均。

    类似这样的:

    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);
            }
        }
    }
    

    不确定标题中“非连续”是什么意思,但如果您不想使用 IEnumerable&lt;&gt; 和 Linq 扩展(这并不总是最有效的方式)来做到这一点,那么还有其他方法.

    一种方法是分配结果缓冲区并在您遍历源数据时填充它。对当前项目使用头指针,对最早包含的项目使用尾指针,对总和进行加减运算……等等。它看起来像:

    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;
    }
    

    对于数组输入,这更有效,因为每个输入项只处理一次。

    【讨论】:

      猜你喜欢
      • 2017-09-01
      • 2013-12-22
      • 2022-01-26
      • 2017-07-04
      • 2011-06-29
      • 2019-04-24
      • 2012-05-24
      • 2021-01-18
      • 1970-01-01
      相关资源
      最近更新 更多