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

时间:2011-12-03 01:59:49

标签: c# linq

我有一个麻烦的查询要写。我现在正在写一些令人讨厌的循环来解决它,但我很想知道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<TheStruct> orderedList = unorderedList.OrderBy(x => x.date).ToList();

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

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

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

    谢谢!

3 个答案:

答案 0 :(得分:8)

您可以使用GroupAdjacent Extension Method(见下文)对序列中的相邻项目进行分组:

var result = unorderedList
    .OrderBy(x => x.date)
    .GroupAdjacent((g, x) => x.A == g.Last().A && 
                             x.B == 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<IEnumerable<T>> GroupAdjacent<T>(
    this IEnumerable<T> source, Func<IEnumerable<T>, T, bool> adjacent)
{
    var g = new List<T>();
    foreach (var x in source)
    {
        if (g.Count != 0 && !adjacent(g, x))
        {
            yield return g;
            g = new List<T>();
        }
        g.Add(x);
    }
    yield return g;
}

答案 1 :(得分:2)

以下是“最难以理解的方式”的条目:

public static class StructOrganizer
{
    public static IEnumerable<Tuple<Decimal, Decimal, IEnumerable<MyStruct>>> OrganizeWithoutGaps(this IEnumerable<MyStruct> someStructs)
    {
        var someStructsAsList = someStructs.ToList();
        var lastValuesSeen = new Tuple<Decimal, Decimal>(someStructsAsList[0].A, someStructsAsList[0].B);
        var currentList = new List<MyStruct>();
        return Enumerable
            .Range(0, someStructsAsList.Count)
            .ToList()
            .Select(i =>
                        {
                            var current = someStructsAsList[i];
                            if (lastValuesSeen.Equals(new Tuple<Decimal, Decimal>(current.A, current.B)))
                                currentList.Add(current);
                            else
                            {
                                lastValuesSeen = new Tuple<decimal, decimal>(current.A, current.B);
                                var oldList = currentList;
                                currentList = new List<MyStruct>(new [] { current });
                                return new Tuple<decimal, decimal, IEnumerable<MyStruct>>(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, s.A, s.B));
        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))));
    }
}

答案 2 :(得分:1)

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

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

就是这样。要打印结果:

      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

希望这会有所帮助。 干杯