如何使用“Linq to Objects”将一组连续日期放在一个组中?

我写一个麻烦的查询。 我现在正在写一些令人讨厌的循环来解决它,但我很想知道Linq是否可以为我做这件事。

我有:

struct TheStruct { public DateTime date {get; set;} //(time portion will always be 12 am) public decimal A {get; set;} public decimal B {get; set;} } 

以及包含这些结构的列表。 让我们说它是这样订购的:

 List orderedList = unorderedList.OrderBy(x => x.date).ToList(); 

如果您将orderedList结构日期放在一个集合中,它们将始终与日期相连。即如果列表中的最新日期是2011/01/31,并且列表中的最早日期是2011/01 / 01,然后你会发现该列表将包含31个项目,一个用于1月份的每个日期。

好的,所以我想做的是将列表项分组,以便:

  1. 组中的每个项目必须包含相同的十进制A值和相同的十进制B值
  2. 如果日期值按顺序排列,则组中的日期值必须形成一组连续日期
  3. 如果总结每组中的项目总和,则总数将等于原始列表中的项目数(或者您可以说具有特定日期的结构不能属于多个组)

任何Linq大师都知道如何做到这一点?

谢谢!

您可以使用GroupAdjacent扩展方法对序列中的相邻项进行分组 (请参见下文):

 var result = unorderedList .OrderBy(x => x.date) .GroupAdjacent((g, x) => xA == g.Last().A && xB == g.Last().B && x.date == g.Last().date.AddDays(1)) .ToList(); 

例:

 (1,1) 2011-01-01 \ (1,1) 2011-01-02 > Group 1 (1,1) 2011-01-03 __/ (2,1) 2011-01-04 \ (2,1) 2011-01-05 > Group 2 (2,1) 2011-01-06 __/ (1,1) 2011-01-07 \ (1,1) 2011-01-08 > Group 3 (1,1) 2011-01-09 __/ (1,1) 2011-02-01  \ (1,1) 2011-02-02   >  Group 4 (1,1) 2011-02-03 __/ 

扩展方法:

 static IEnumerable> GroupAdjacent( this IEnumerable source, Func, T, bool> adjacent) { var g = new List(); foreach (var x in source) { if (g.Count != 0 && !adjacent(g, x)) { yield return g; g = new List(); } g.Add(x); } yield return g; } 

这是“最令人难以置信的方式”的条目:

 public static class StructOrganizer { public static IEnumerable>> OrganizeWithoutGaps(this IEnumerable someStructs) { var someStructsAsList = someStructs.ToList(); var lastValuesSeen = new Tuple(someStructsAsList[0].A, someStructsAsList[0].B); var currentList = new List(); return Enumerable .Range(0, someStructsAsList.Count) .ToList() .Select(i => { var current = someStructsAsList[i]; if (lastValuesSeen.Equals(new Tuple(current.A, current.B))) currentList.Add(current); else { lastValuesSeen = new Tuple(current.A, current.B); var oldList = currentList; currentList = new List(new [] { current }); return new Tuple>(lastValuesSeen.Item1, lastValuesSeen.Item2, oldList); } return null; }) .Where(i => i != null); } // To Test: public static void Test() { var r = new Random(); var sampleData = Enumerable.Range(1, 31).Select(i => new MyStruct {A = r.Next(0, 2), B = r.Next(0, 2), date = new DateTime(2011, 12, i)}).OrderBy(s => s.date).ToList(); var sortedData = sampleData.OrganizeWithoutGaps(); Console.Out.WriteLine("Sample Data:"); sampleData.ForEach(s => Console.Out.WriteLine("{0} = ({1}, {2})", s.date, sA, sB)); Console.Out.WriteLine("Output:"); sortedData.ToList().ForEach(s => Console.Out.WriteLine("({0}, {1}) = {2}", s.Item1, s.Item2, String.Join(", ", s.Item3.Select(st => st.date)))); } } 

如果我理解你,一个简单的Group By会做到这一点:

 var orderedList = unorderedList.OrderBy(o => o.date).GroupBy(s => new {sA, sB}); 

只是。 要打印结果:

  foreach (var o in orderedList) { Console.WriteLine("Dates of group {0},{1}:", o.Key.A, o.Key.B); foreach(var s in o){ Console.WriteLine("\t{0}", s.date); } } 

输出如下:

 Dates of group 2,3: 02/12/2011 03/12/2011 Dates of group 4,3: 03/12/2011 Dates of group 1,2: 04/12/2011 05/12/2011 06/12/2011 

希望这可以帮助。 干杯