如何使用Linq计算可为null的数字的变化

本文关键字:null 数字 变化 何使用 Linq 计算 | 更新日期: 2023-09-27 17:58:59

我需要计算可为null的数字的时间序列的变化。以下代码完成了任务:

public static double?[] GetChanges(double?[] x)
{
    if(x.Length == 1)
        throw new Exception("Time Series Too Short");
    var ret = new double?[x.Length - 1];
    for (int i = 1; i < x.Length; i++)
    {
        ret[i-1] = (x[i - 1].HasValue && x[i].HasValue) ? x[i] - x[i - 1] : null;
    }
    return ret;
}

有没有更好的方法可以和林克一起做到这一点?库正在使用。净额3.5。现在我不能使用Zip,因为它附带了。净4。

编辑:根据mquander和Eric Lippert的建议,我提出了以下在3.5上运行的代码:

public class Tuple<T>
{
    public Tuple(T first)
    {
        First = first;
    }
    public T First { get; set; }
}
public class Tuple<T, T2> : Tuple<T>
{
    public Tuple(T first, T2 second)
        : base(first)
    {
        Second = second;
    }
    public T2 Second { get; set; }
    public static Tuple<T1, T2> New<T1, T2>(T1 t1, T2 t2)
    {
        return new Tuple<T1, T2>(t1, t2);
    }
}
public static class EnumerableExtensions
{
    public static IEnumerable<Tuple<T, T>> Pairs<T>(this IEnumerable<T> seq)
    {
        using (var enumerator = seq.GetEnumerator())
        {
            enumerator.MoveNext();
            var prior = enumerator.Current;
            while (enumerator.MoveNext())
            {
                yield return Tuple<T, T>.New(prior, enumerator.Current);
                prior = enumerator.Current;
            }
        }
    }
}

我使用这个代码如下:

    public static IEnumerable<double?> GetChanges2(double?[] x)
    {
        if (x.Length == 1)
            throw new Exception("Time Series Too Short");
        return x.Pairs().Select(p => p.Second - p.First);
    }

欢迎提出进一步改进的建议。当我有VS2010和时,我会回来的。Net 4,这样我就可以尝试两个答案中建议的方法。

谢谢!

如何使用Linq计算可为null的数字的变化

也许只是

Enumerable.Zip(
    x.Skip(1),
    x,
    (a, b) => (a.HasValue && b.HasValue) ? (a - b) : null)
)

顺便说一下,我只会用替身和替身。NaN而不是null。这样代码就可以简化为

Enumerable.Zip(x.Skip(1), x, (a, b) => a - b)

在这个地方,也许还有其他一些地方。

编辑:
根据@Eric Lippert的建议,即使在Nullable的情况下,删除空检查也是可能的。所以答案只是

Enumerable.Zip(x.Skip(1), x, (a, b) => a - b)

即使在这种情况下。

另一个想法(受到这个答案的启发)是将前一项保留在捕获的变量中:

static IEnumerable<double?> GetChanges(IEnumerable<double?> x)
{
    double? previous = x.First();
    return x.Skip(1).Select(d =>
              { double? result = d - previous; previous = d; return result; });
}

这必须起作用,因为捕获的变量在函数中是"隐藏的"。

不是。我会按照你的方式做。如果你觉得功能特别强大,那么最好在IEnumerable<T>上定义一个Pairs方法,将序列分解为一系列连续的重叠对,然后将每个对映射到其第一个值和第二个值之间的delta。

EDIT,因为请求了一个示例:

public static class EnumerableExtensions
{
    public static IEnumerable<Tuple<T, T>> Pairs<T>(this IEnumerable<T> seq)
    {
        using (var enumerator = seq.GetEnumerator())
        {
            enumerator.MoveNext();
            var prior = enumerator.Current;
            while (enumerator.MoveNext())
            {
                yield return Tuple.Create(prior, enumerator.Current);
                prior = enumerator.Current;
            }
        }
    }
}

GetChanges被简化为:

var changes = values.Pairs().Select(x => x.Item2 - x.Item1);

(请注意,如果values包含的值少于两个,则我的实现返回一个空序列,而不是抛出异常。)

(再次编辑--最后清理了可为null的类型处理,感谢Eric指出!)