首页 > 解决方案 > 带有 linq 的列表中的线性回归

问题描述

我有一个形成坡道系列的“步骤”列表。每个步骤都有一个start value、一个end value和一个duration。这是一个示例图:

步骤列表的示例图

保证后续步骤的起始值等于结束值。它是一个单调的功能。

现在我需要在给定时间获取值。我已经有一个使用好的旧 foreach 的工作实现,但我想知道是否有一些聪明的方法可以使用linq. 也许有人有替代该GetValueAt功能的想法?

class Program
{
    class Step
    {
        public double From { get; set; }
        public double To { get; set; }
        public int Duration { get; set; }
    }

    static void Main(string[] args)
    {
        var steps = new List<Step>
        {
            new Step { From =  0, To = 10, Duration = 20},
            new Step { From =  10, To = 12, Duration = 10},
        };

        const double doubleTolerance = 0.001;
        // test turning points
        Debug.Assert(Math.Abs(GetValueAt(steps, 0) - 0) < doubleTolerance);
        Debug.Assert(Math.Abs(GetValueAt(steps, 20) - 10) < doubleTolerance);
        Debug.Assert(Math.Abs(GetValueAt(steps, 30) - 12) < doubleTolerance);
        // test linear interpolation
        Debug.Assert(Math.Abs(GetValueAt(steps, 10) - 5) < doubleTolerance);
        Debug.Assert(Math.Abs(GetValueAt(steps, 25) - 11) < doubleTolerance);
    }

    static double GetValueAt(IList<Step> steps, int seconds)
    {
        // guard statements if seconds is within steps omitted here
        var runningTime = steps.First().Duration;
        var runningSeconds = seconds;

        foreach (var step in steps)
        {
            if (seconds <= runningTime)
            {
                var x1 = 0; // stepStartTime
                var x2 = step.Duration; // stepEndTime

                var y1 = step.From; //  stepStartValue
                var y2 = step.To; // stepEndValue

                var x = runningSeconds;
                // linear interpolation
                return y1 + (y2 - y1) / (x2 - x1) * (x - x1);  
            }
            runningTime += step.Duration;
            runningSeconds -= step.Duration;
        }

        return double.NaN;
    }
}

标签: c#linq

解决方案


你可以试试Aggregate

static double GetValueAt(IList<Step> steps, int seconds)
{
    var (value, remaining) = steps.Aggregate(
        (Value: 0d, RemainingSeconds: seconds),
        (secs, step) =>
        {
            if (secs.RemainingSeconds > step.Duration)
            {
                return (step.To, secs.RemainingSeconds - step.Duration);
            }
            else
            {
                return (secs.Value + ((step.To - step.From) / step.Duration) * secs.RemainingSeconds, 0);
            }
        });

    return remaining > 0 ? double.NaN : value;
}

推荐阅读