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 TimeSpan
s 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 DateTim开发者_高级运维e 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
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);
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);
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)
精彩评论