ChristianMurschall
ChristianMurschall

Reputation: 1701

Linear regression in a list with linq

I have a list of 'steps' that form a ramps series. Eeach step has a start value, an end value and a duration. Here is an example plot:

Example plot of a list of steps

It is guaranteed, that the start value of a subsequent step is equal to the end value. Its a monotonous function.

Now I need to get the value at a given time. I have already a working implementation using good old foreach but I wonder if there is some clever way to do it with linq. Perhaps someome has an idea to substitute the GetValueAt function?

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

Upvotes: 0

Views: 373

Answers (2)

DarkSquirrel42
DarkSquirrel42

Reputation: 10267

let's ignore linq for a moment...

for small amounts of steps, your foreach approach is quite effective ... also if you can manage the accessing side to favor ordered sequential access instead of random access, you could optimize the way of accessing the required step to calculate the value... think of an iterator that only goes forward if the requested point is not on the current step

if your amount of steps becomes larger and you need to access the values in a random order, you might want to introduce a balanced tree structure for searching the right step element

Upvotes: 1

weichch
weichch

Reputation: 10055

You could try 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;
}

Upvotes: 1

Related Questions