scottL
scottL

Reputation: 21

C# LINQ TimeSpan Average Days

I am learning LINQ and want a LINQ query that is the equivalent of the following code.

The IEnumerable list contains a sorted list of dates: Oldest to newest.

The code below derives TimeSpans by subtracting the array's zeroth element date from array's first element date, the first element date from the 2nd, 2nd from the 3rd and so on. The TimeSpan.Days are then averaged elsewhere in the code.

I trust a LINQ query would not require the construction of an array. The IEnumerable<DateTime> structure could be used as the data source.

 IEnumerable<DateTime> list; // contains a sorted list of oldest to newest dates

// build DateTime array 

 DateTime[] datesArray = null;
 TimeSpan ts;
 List<int> daysList = new List<int>();

 datesArray = (from dt in list
               select dt).ToArray();

// loop through the array and derive the TimeSpan by subtracting the previous date,
// contained in the previous array element, from the date in the current array element.
// start the loop at element 1.

 for (int i = 1; i < list.Count(); i++)
 {
     ts = datesArray[i].Subtract(datesArray[i - 1]); // ts is a TimeSpan type
     daysList.Add(ts.Days);
     // add the TimeSpan.Days to a List<int> for averaging elsewhere.
 }

Thank you,

Scott

Upvotes: 2

Views: 1881

Answers (3)

Sergey Shandar
Sergey Shandar

Reputation: 2387

If you need an average only then you do not need to check all elements in the list. You need only the first one and the last one.

var period = list.Last() - list.First();
var averageDays = (double)period.TotalDays / (list.Count() - 1);

Upvotes: 2

Ani
Ani

Reputation: 113402

I think you want:

double averageDays = list
                     .Skip(1)
                     .Zip(list, (next, prev) => (double)next.Subtract(prev).Days)
                     .Average();

Do note that this is a lossy average. Are you sure you don't want to use TotalDays instead?

EDIT:

The way this works is to overlay the sequence with a 'one-deferred' version of the sequence, which makes it easy to calculate consecutive deltas. Then it's just a matter of averaging the deltas up to produce the result.

For comparison, a 'faithful' translation of your existing code would look like:

double averageDays = Enumerable
                     .Range(1, list.Count - 1)
                     .Average(i => (double)list[i].Subtract(list[i - 1]).Days);

Upvotes: 4

Jeffrey Hantin
Jeffrey Hantin

Reputation: 36494

This is arranged into a more Lispy indentation style to emphasize the functional style and avoid running off into scrollbar-land on the right side of the page.

list.Skip(1)
    .Aggregate(new { Previous = list.FirstOrDefault(), 
                     Sum = 0.0,
                     Count = 0 },
               (a, n) => new { Previous = n,
                               Sum = a.Sum + (n - a.Previous).Days,
                               Count = a.Count + 1 },
               a => a.Sum / a.Count)

Upvotes: 1

Related Questions