Reputation: 1701
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:
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
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
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