问题
I have this linq query as stated below. The problem is when the data is grouped by price, it groups dates by price without considering a case where same price can occur for nonconsecutive dates
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
//Console.WriteLine("Hello World");
List<Prices> list = new List<Prices>();
list.Add(new Prices() { Date = DateTime.Parse("2017-06-17"), Price = Double.Parse("50")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-18"), Price = Double.Parse("50")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-19"), Price = Double.Parse("50")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-20"), Price = Double.Parse("100")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-21"), Price = Double.Parse("100")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-22"), Price = Double.Parse("100")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-23"), Price = Double.Parse("50")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-24"), Price = Double.Parse("50")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-25"), Price = Double.Parse("50")});
var baseservices = list
.GroupBy(l => l.Price)
.Select(g => new
{
Price = g.Key,
PeriodStart = g.Select(l=>l.Date).Min(),
PeriodEnd = g.Select(l => l.Date).Max(),
});
foreach(var item in baseservices)
{
Console.WriteLine(item.Price + " " + item.PeriodStart + " " + item.PeriodEnd);
}
}
}
public class Prices
{
public DateTime Date {get;set;}
public double Price {get;set;}
}
public class Quote
{
public DateTime PeriodStart {get;set;}
public DateTime PeriodEnd {get;set;}
public double Price {get;set;}
}
The result is
50 6/17/2017 12:00:00 AM 6/25/2017 12:00:00 AM
100 6/20/2017 12:00:00 AM 6/22/2017 12:00:00 AM
How can I get the following result
50 6/17/2017 12:00:00 AM 6/29/2017 12:00:00 AM
100 6/20/2017 12:00:00 AM 6/22/2017 12:00:00 AM
50 6/23/2017 12:00:00 AM 6/25/2017 12:00:00 AM
回答1:
LINQ is not well suited for such operations. The only standard LINQ operator that can be used to do such processing is Aggregate, but it's no more than LINQ-ish foreach
loop:
var baseservices = list
.OrderBy(e => e.Date)
.Aggregate(new List<Quote>(), (r, e) =>
{
if (r.Count > 0 && r[r.Count - 1].Price == e.Price && r[r.Count - 1].PeriodEnd.AddDays(1) == e.Date)
r[r.Count - 1].PeriodEnd = e.Date;
else
r.Add(new Quote { Price = e.Price, PeriodStart = e.Date, PeriodEnd = e.Date });
return r;
});
Note that in contrast with many LINQ methods, this executes immediate and does not return until the whole result is ready.
回答2:
If you create help class for your DateRange:
public class DateRange
{
public DateTime PeriodStart { get; set; }
public DateTime PeriodEnd { get; set; }
}
and help method to convert your list of dates:
public static IEnumerable<DateRange> Convert(IEnumerable<DateTime> dates)
{
var ret = new DateRange();
foreach (var date in dates)
{
if (ret.PeriodEnd == default(DateTime))
{
ret.PeriodStart = date;
ret.PeriodEnd = date;
}
else if (ret.PeriodEnd.AddDays(1) == date)
{
ret.PeriodEnd = date;
}
else
{
yield return ret;
ret = new DateRange();
}
}
yield return ret;
}
You will be able to sort your dates to periods:
var baseservices = list
.GroupBy(l => l.Price)
.Select(g => new
{
Price = g.Key,
Dates = Convert(g.Select(d=>d.Date)).ToList()
})
.SelectMany(r=>r.Dates, (a,b)=>new Quote {
Price = a.Price,
PeriodStart = b.PeriodStart,
PeriodEnd = b.PeriodEnd})
.ToList();
来源:https://stackoverflow.com/questions/41653586/how-to-optmize-linq-query-for-grouping-dates-by-price-without-merging-results