Search code examples
c#.netlinqnunit

Create a list of values based on a seed and the previous value


I have an object (a trigger) that contains a method called GetNextFireTime(DateTimeOffset after), which returns the next time the trigger will fire after the given input. My goal is, given an input of a list of times of unknown size n, as well as a start time, to ensure the list of times matches the first n times the trigger will fire after the start time. This is fairly easily accomplished like this

void Test(ITrigger trigger, DateTimeOffset start, DateTimeOffset[] expectedFireTimes){
  var nextFire = startTime;
  foreach (var expectedFire in expectedFireTimes){
    nextFire = trigger.GetFireTimeAfter(nextFire);
    Assert.That(nextFire, Is.EqualTo(expectedFire));
  }
}

However, I then decided I would rather be able to view test results as a single assertion, rather than asserting elements one by one, so that if the first 2 fire times are correct but the 3rd is wrong, the test results show clearly what was expected and what was found. This can be done by creating a list rather than asserting in the loop:

void Test(ITrigger trigger, DateTimeOffset start, DateTimeOffset[] expectedFireTimes){
  var nextFire = startTime;
  var upcomingFireTimes = new List<DateTimeOffset>();
  foreach (var expectedFire in expectedFireTimes){
    nextFire = trigger.GetFireTimeAfter(nextFire);
    upcomingFireTimes.Add(nextFire);
  }
  Assert.That(upcomingFireTimes, Is.EqualTo(expectedFireTimes));
}

This also works, but it made me start thinking about whether this can be accomplished via Linq. Does Linq have a method to create a list where each element is calculated based on an initial seed and the previous element? The closest I can come up with is Aggregate(), which takes a seed and a function, but returns only a single value and I would have to store the individual elements in the list inside the function, which makes it very similar to the foreach loop above:

var upcomingFireTimes = new List<DateTimeOffset>();
expectedFireTimes.Aggregate(
  startTime,
  (seed, _) => {
    var nextfire = trigger.GetFireTimeAfter(seed);
    upcomingFireTimes.Add(nextFire);
    return nextFire;
  });

My goal is a single line Linq method that, rather than ultimately returning only the last value calculated, as Aggregate() does, returns all values calculated along the way, like this:

var upcomingFireTimes = expectedFireTimes.SomeLinqMethod(
  startTime,
  (seed, _) => trigger.GetFireTimeAfter(seed));

Solution

  • Your Linq method is Select!

    DateTime firetime = startTime;
    var upcomingFireTimes = expectedFireTimes.Select(s => { return fireTime = trigger.GetFireTimeAfter(fireTime) } );
    

    You can generate whatever you need using/not using the source collection.