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