c# - LINQ 中的循环移动平均滤波器

标签 c# .net linq filtering signal-processing

我正在寻找一种在 C# 中实现移动平均过滤器的优雅方法。现在,这很容易,但在边界处,平均窗口应环绕开始/结束。这让我的代码变得丑陋和不直观,我想知道是否有更聪明的方法来使用 LINQ 等来解决这个问题。

所以我目前拥有的是:

// input is a List<double> y, output is List<double> yfiltered
int yLength = y.Count;
for (int i = 0; i < yLength; i++)
{
    double sum = 0.0;
    for (int k = i - halfWindowWidth; k <= i + halfWindowWidth; k++)
    {
        if (k < 0)
        {
            // k is negative, wrap around
            sum += y[yLength - 1 + k];
        }
        else if (k >= yLength)
        {
            // k exceeds y length, wrap around
            sum += y[k - yLength];
        }
        else
        {
            // k within y.Count
            sum += y[k];
        }
    }
    yfiltered[i] = sum / (2 * halfWindowWidth + 1);
}

最佳答案

这是一个完全不同的建议-

我试图让它变得更好,而不是更具可读性。

您当前代码的问题在于,它会在不需要时一次又一次地对许多数字求和。

Comparing both approaches after the implementation code...

我只是第一次对一堆求和,然后一次又一次地减去尾部并添加头部:

double sum = 0;

// sum = Enumerable.Range(i - halfWindowWidth, halfWindowWidth * 2 + 1)
//    .Select(k => y[(k + yLength) % yLength]).Sum();

for (var i = -halfWindowWidth; i <= halfWindowWidth; i++)
{
    sum += y[(i + yLength) % yLength];
}

yFiltered[0] = sum / (2 * halfWindowWidth + 1);

for (var i = 1; i < yLength; i++)
{
    sum = sum -
          y[(i - halfWindowWidth - 1 + yLength) % yLength] +
          y[(i + halfWindowWidth) % yLength];

    yFiltered[i] = sum / (2 * halfWindowWidth + 1);
}

下面是速度测试,比较了完全重新计算方法和这个方法:

private static double[] Foo1(IList<double> y, int halfWindowWidth)
{
    var yfiltered = new double[y.Count];

    var yLength = y.Count;

    for (var i = 0; i < yLength; i++)
    {
        var sum = 0.0;

        for (var k = i - halfWindowWidth; k <= i + halfWindowWidth; k++)
        {
            sum += y[(k + yLength) % yLength];
        }

        yfiltered[i] = sum / (2 * halfWindowWidth + 1);
    }

    return yfiltered;
}

private static double[] Foo2(IList<double> y, int halfWindowWidth)
{
    var yFiltered = new double[y.Count];
    var windowSize = 2 * halfWindowWidth + 1;

    double sum = 0;

    for (var i = -halfWindowWidth; i <= halfWindowWidth; i++)
    {
        sum += y[(i + y.Count) % y.Count];
    }

    yFiltered[0] = sum / windowSize;

    for (var i = 1; i < y.Count; i++)
    {
        sum = sum -
              y[(i - halfWindowWidth - 1 + y.Count) % y.Count] +
              y[(i + halfWindowWidth) % y.Count];

        yFiltered[i] = sum / windowSize;
    }

    return yFiltered;
}

private static TimeSpan TestFunc(Func<IList<double>, int, double[]> func, IList<double> y, int halfWindowWidth, int iteration
{
    var sw = Stopwatch.StartNew();

    for (var i = 0; i < iterations; i++)
    {
        var yFiltered = func(y, halfWindowWidth);
    }

    sw.Stop();
    return sw.Elapsed;
}

private static void RunTests()
{
    var y = new List<double>();
    var rand = new Random();

    for (var i = 0; i < 1000; i++)
    {
        y.Add(rand.Next());
    }

    var foo1Res = Foo1(y, 100);
    var foo2Res = Foo2(y, 100);

    Debug.WriteLine("Results are equal: " + foo1Res.SequenceEqual(foo2Res));

    Debug.WriteLine("Foo1: " + TestFunc(Foo1, y, 100, 1000));
    Debug.WriteLine("Foo2: " + TestFunc(Foo2, y, 100, 1000));
}

Time complexities:

MyWay: O(n + m)

OtherWay: O(n * m)

因为 Foo1 是 O(n * m),而 Foo2 是 O(n + m),所以差异如此之大也就不足为奇了。

这个不是真正疯狂的大规模的结果是:

Results are equal: True

Foo1: 5.52 seconds

Foo2: 61.1 milliseconds

并且在更大的范围内(在迭代和计数中将 1000 替换为 10000):

Foo1: Stopped after 10 minutes...

Foo2: 6.9 seconds

关于c# - LINQ 中的循环移动平均滤波器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10495433/

相关文章:

linq - EF4 LINQ Include(string) 替代硬编码字符串?

c# - C#中using语句的用法

.net - 逻辑线程的数量令人难以置信; Windbg 看不到它们?

c# - @ Html.PasswordFor不要让密码粘贴

c# - Azure SDK 2.2 in Production : Could not load file or assembly 'msshrtmi' or one of its dependencies. 系统找不到指定的文件

c# - 仅知道部分文件名的情况下在 SFTP 上获取远程文件

c# - 2 列范围之间的 Linq 值

c# - LINQ:使用 "AND"时生成 "OR"表达式而不是 "CONTAINS"

c# - JSON反序列化——String自动转为Int

c# - 我想等待抛出 AggregateException,而不仅仅是第一个异常