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

发布于 2024-11-15 05:38:32 字数 1865 浏览 2 评论 0原文

我需要计算可为空数字的时间序列的变化。以下代码可以完成工作:

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

是否有更好的方法使用 Linq 来完成此任务? 该库正在使用 .Net 3.5。 现在我无法使用 Zip,因为它是 .Net 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 时,我会回来,这样我就可以尝试这两个答案中建议的方法。

谢谢!

I need to calculate changes off of time series of nullable numbers. The following code gets the job done:

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

Is there a better way to accomplish that with Linq?
The library is using .Net 3.5.
Right now I cannot use Zip, because that comes with .Net 4.

Edit: following the advice by mquander and Eric Lippert, I have come up with the following code which runs on 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;
            }
        }
    }
}

I am using this code as follows:

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

Any suggestions on further improvement are welcome.
I will be back when I have VS2010 and .Net 4, so that I can try out the approaches suggested in both answers.

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

放手` 2024-11-22 05:38:32

也许只是

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

顺便说一句,我只会使用双精度和 double.NaN 而不是空值。这样,代码就可以简化为仅

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

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

编辑:
遵循 @Eric Lippert 的建议,即使对于 Nullable 情况,也可以删除 null 检查。所以即使在这种情况下,答案也很简单

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

Maybe just

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

?

By the way, I would use just doubles and double.NaN instead of nulls. This way the code can be simplified to just

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

in this and perhaps some other places.

EDIT:
Following @Eric Lippert's advice, removing null checks is possible even for Nullable case. So the answer would be simply

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

even in this case.

§对你不离不弃 2024-11-22 05:38:32

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

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

这必须有效,因为捕获的变量“隐藏”在函数中。

Another idea (inspired by this answer) would be to keep the previous item in a captured variable:

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

This must work because the captured variable is "hidden" in the function.

南城旧梦 2024-11-22 05:38:32

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

编辑,因为请求了一个示例:

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 包含少于两个值,我的实现将返回一个空序列,而不是抛出异常。)

(再次编辑——最后清理了可空类型处理,感谢埃里克指出!)

Not really. I would do it your way. If you're feeling particularly functional, the way to go would be to define a Pairs method on IEnumerable<T> that breaks a sequence into a series of consecutive overlapping pairs, and then map each pair to the delta between its first and second value.

EDIT since an example was requested:

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

Then GetChanges is reduced to:

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

(Note that my implementation returns an empty sequence instead of throwing an exception if values contains less than two values.)

(edit again -- cleaned up the nullable type handling at the end, thanks Eric for pointing it out!)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文