如何使用“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月份的每个日期。
好的,所以我想做的是将列表项分组,以便:
- 组中的每个项目必须包含相同的十进制A值和相同的十进制B值
- 如果日期值按顺序排列,则组中的日期值必须形成一组连续日期
- 如果总结每组中的项目总和,则总数将等于原始列表中的项目数(或者您可以说具有特定日期的结构不能属于多个组)
任何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
希望这可以帮助。 干杯