Select from IEnumerable with Distinct/GroupBy and sorting — possible?
Say you have this:
class LogEntry
{
int ID;
int UserName;
datetime TimeStamp;
string Details;
}
and you have pulled a set of 开发者_C百科data like this:
ID Username Timestamp Details
1 foo 1/01/2010 Account created
2 zip 2/02/2010 Account created
3 bar 2/02/2010 Account created
4 sandwich 3/03/2010 Account created
5 bar 5/05/2010 Stole food
6 foo 5/05/2010 Can't find food
7 sandwich 8/08/2010 Donated food
8 sandwich 9/09/2010 Ate more food
9 foo 9/09/2010 Ate food
10 bar 11/11/2010 Can't find food
What I want to do is select only the last single record (ie Sort on TimeStamp Descending) for each user (ie GroupBy Username). I can get my head around Distinct and GroupBy, but combining them in a single statement which also returns the non-distinct/grouped fields/properties AND sorts by timestamp is giving me a headache.
What should come out with the above example is:
ID Username Timestamp Details
2 zip 2/02/2010 Account created
8 sandwich 9/09/2010 Ate more food
9 foo 9/09/2010 Ate food
10 bar 11/11/2010 Can't find food
I don't want to 'cheat' and resort to a long-winded way of doing it when I'm confident it can be done in a single LINQ statement.
Hopefully my Linq-fu is right on this one: =)
var results = sourceList
.OrderByDescending(item => item.Timestamp)
.GroupBy(item => item.Username)
.Select(grp => grp.First())
.ToArray();
This sample code using your data, and final ordering by ID, gives exactly the same output as your example: (if you don't mind the crude formatting!)
class Program
{
static void Main(string[] args)
{
var sourceItems = new[] {
new LogEntry {ID=1 ,UserName="foo ", TimeStamp= new DateTime(2010 ,1,01),Details="Account created ",} ,
new LogEntry {ID=2 ,UserName="zip ", TimeStamp= new DateTime(2010 ,2,02),Details="Account created ",} ,
new LogEntry {ID=3 ,UserName="bar ", TimeStamp= new DateTime(2010 ,2,02),Details="Account created ",} ,
new LogEntry {ID=4 ,UserName="sandwich ", TimeStamp= new DateTime(2010 ,3,03),Details="Account created ",} ,
new LogEntry {ID=5 ,UserName="bar ", TimeStamp= new DateTime(2010 ,5,05),Details="Stole food ",} ,
new LogEntry {ID=6 ,UserName="foo ", TimeStamp= new DateTime(2010 ,5,05),Details="Can't find food ",} ,
new LogEntry {ID=7 ,UserName="sandwich ", TimeStamp= new DateTime(2010 ,8,08),Details="Donated food ",} ,
new LogEntry {ID=8 ,UserName="sandwich ", TimeStamp= new DateTime(2010 ,9,09),Details="Ate more food ",} ,
new LogEntry {ID=9 ,UserName="foo ", TimeStamp= new DateTime(2010 ,9,09),Details="Ate food ",} ,
new LogEntry {ID=10 ,UserName="bar ", TimeStamp= new DateTime(2010,11,11),Details="Can't find food ",} ,
};
var results = sourceItems
.OrderByDescending(item => item.TimeStamp)
.GroupBy(item => item.UserName)
.Select(grp => grp.First())
.OrderBy(item=> item.ID)
.ToArray();
foreach (var item in results)
{
Console.WriteLine("{0} {1} {2} {3}",
item.ID, item.UserName, item.TimeStamp, item.Details);
}
Console.ReadKey();
}
}
public class LogEntry
{
public int ID;
public string UserName;
public DateTime TimeStamp;
public string Details;
}
The accepted answer is correct and works, but there's now an easier way to do this since DistinctBy
has been added to LINQ.
var results = source
.OrderByDescending(item => item.Timestamp) // Order however you want
.DistinctBy(item => item.Username); // Distincts by the key provided
DistinctBy
does exactly the same as if you would GroupBy
first and then Select
the first item in each group. It might even be more performant.
精彩评论