在Linq中保留订购的同时进行分组

时间:2012-07-03 11:46:28

标签: .net linq group-by

我有一个IQueryable(Of Job),其中有Job,其中包括:

Property CreatedOn as DateTime
Property JobType as JobTypes

Enum JobTypes
    JobType1
    JobType2
    JobType3
End Enum

我想要的是一个列表,按CreatedOn排序,然后按JobType分组计数

例如说我有(缩写日期)

11:00  JobType1
11:01  JobType2
11:02  JobType2
11:03  JobType2
11:04  JobType2
11:05  JobType3
11:06  JobType1
11:07  JobType1

我想要

JobType1 1
JobType2 4
JobType3 1
JobType1 2

我不知道如何在分组时考虑订购。有人能以正确的方式指出我吗?根据喜好,我更喜欢流利的语法。 VB.Net或C#很好。

5 个答案:

答案 0 :(得分:3)

这个技巧很容易训练LinqToObjects:

public static IEnumerable<IGrouping<TKey, TSource>> GroupContiguous<TKey, TSource>(
  this IEnumerable<TSource> source,
  Func<TSource, TKey> keySelector)
{
  bool firstIteration = true;
  MyCustomGroupImplementation<TKey, TSource> currentGroup = null;

  foreach (TSource item in source)
  {
    TKey key = keySelector(item);
    if (firstIteration)
    {
      currentGroup = new MyCustomGroupImplementation<TKey, TSource>();
      currentGroup.Key = key;
      firstIteration = false;
    }
    else if (!key.Equals(currentGroup.Key))
    {
      yield return currentGroup;
      currentGroup = new MyCustomGroupImplementation<TKey, TSource>();
      currentGroup.Key = key;
    }
    currentGroup.Add(item);
  }
  if (currentGroup != null)
  {
    yield return currentGroup;
  }
}

public class MyCustomGroupImplementation<TKey, TSource> : IGrouping<TKey, TSource>
{
  //TODO implement IGrouping and Add
}

使用
IEnumerable<IGrouping<JobType, Job> query = Jobs
  .OrderBy(j => j.CreatedOn)
  .GroupContiguous(j => j.JobType);

与任何旧的linq提供商一起“查看上一行”并不容易。我希望你不必教LinqToSql或LinqToEntities如何做到这一点。

答案 1 :(得分:2)

这是一种使用Aggregrate方法的合理方法。

如果你从这样的JobTypes列表开始:

var jobTypes = new []
{
    JobTypes.JobType1,
    JobTypes.JobType2,
    JobTypes.JobType2,
    JobTypes.JobType2,
    JobTypes.JobType2,
    JobTypes.JobType3,
    JobTypes.JobType1,
    JobTypes.JobType1,
};

您可以通过首先定义累加器来使用Aggregate,如下所示:

var accumulator = new List<KeyValuePair<JobTypes, int>>()
{
    new KeyValuePair<JobTypes, int>(jobTypes.First(), 0),
};

然后Aggregate方法调用如下所示:

var results = jobTypes.Aggregate(accumulator, (a, x) =>
{
    if (a.Last().Key == x)
    {
        a[a.Count - 1] =
            new KeyValuePair<JobTypes, int>(x, a.Last().Value + 1);
    }
    else
    {
        a.Add(new KeyValuePair<JobTypes, int>(x, 1));
    }
    return a;
});

最后调用这个给你这个结果:

Job Types Results

简单,有点......

答案 2 :(得分:2)

此更新版本使用子例程执行与以前相同的操作,但不需要额外的内部字段。 (我保留了我的早期版本,为避免使用Zip例程,需要额外的OrDer字段。)

Option Explicit On
Option Strict On
Option Infer On
Imports so11310237.JobTypes
Module so11310237
 Enum JobTypes
  JobType1
  JobType2
  JobType3
 End Enum
Sub Main()
 Dim data = {New With{.CO=#11:00#, .JT=JobType1, .OD=0},
  New With{.CO=#11:03#, .JT=JobType2, .OD=0},
  New With{.CO=#11:05#, .JT=JobType3, .OD=0},
  New With{.CO=#11:02#, .JT=JobType2, .OD=0},
  New With{.CO=#11:06#, .JT=JobType1, .OD=0},
  New With{.CO=#11:01#, .JT=JobType2, .OD=0},
  New With{.CO=#11:04#, .JT=JobType2, .OD=0},
  New With{.CO=#11:07#, .JT=JobType1, .OD=0}}

 ' Check that there's any data to process
 If Not data.Any Then Exit Sub

 ' Both versions include a normal ordering first.
 Dim odata = From q In data Order By q.CO

 ' First version here (and variables reused below):

 Dim ljt = odata.First.JT

 Dim c = 0
 For Each o In odata
  If ljt <> o.JT Then
   ljt = o.JT
   c += 1
  End If
  o.OD = c
 Next

 For Each p In From q In data Group By r=q.JT, d=q.OD Into Count()
  Console.WriteLine(p)
 Next

 Console.WriteLine()

 ' New version from here:

 ' Reset variables (still needed :-()
 ljt = odata.First.JT
 c = 0 
 For Each p In From q In odata Group By r=q.JT, d=IncIfNotEqual(c,q.JT,ljt) Into Count()
  Console.WriteLine(p)
 Next

End Sub

Function IncIfNotEqual(Of T)(ByRef c As Integer, ByVal Value As T, ByRef Cmp As T) As Integer
 If Not Object.Equals(Value, Cmp) Then
  Cmp = Value
  c += 1
 End If
 Return c 
End Function

End Module

答案 3 :(得分:1)

Non-Linq答案

鉴于

public enum JobTypes
{
    JobType1,
    JobType2,
    JobType3
}

public class Job
{
    public JobTypes JobType { get; set; }
    public DateTime CreatedOn { get; set; }
}

public class JobSummary
{
    public JobSummary(JobTypes jobType, long count)
    {
        this.JobType = jobType;
        this.Count = count;
    }

    public JobTypes JobType { get; set; }
    public long Count { get; set; }
}

然后你可以

private List<JobSummary> GetOrderedSummary(List<Job> collection)
{
    var result = new List<JobSummary>();
    if (!collection.Any())
    {
        return result;
    }
    var orderedCollection = collection.OrderBy(j => j.CreatedOn);
    var temp = orderedCollection.First();
    var count = 1;

    foreach (var job in orderedCollection.Skip(1))
    {
        if (temp.JobType == job.JobType)
        {
            count++;
            continue;
        }

        result.Add(new JobSummary(temp.JobType, count));
        temp = job;
        count = 1;
    }

    result.Add(new JobSummary(temp.JobType, count));

    return result;
}
使用

private void DoSomething()
{
    var collection = new List<Job>
    {
        new Job{JobType = JobTypes.JobType1, CreatedOn = DateTime.Now},
        new Job{JobType = JobTypes.JobType2, CreatedOn = DateTime.Now.AddSeconds(1)},
        new Job{JobType = JobTypes.JobType2, CreatedOn = DateTime.Now.AddSeconds(2)},
        new Job{JobType = JobTypes.JobType2, CreatedOn = DateTime.Now.AddSeconds(3)},
        new Job{JobType = JobTypes.JobType2, CreatedOn = DateTime.Now.AddSeconds(4)},
        new Job{JobType = JobTypes.JobType3, CreatedOn = DateTime.Now.AddSeconds(5)},
        new Job{JobType = JobTypes.JobType3, CreatedOn = DateTime.Now.AddSeconds(6)},
        new Job{JobType = JobTypes.JobType1, CreatedOn = DateTime.Now.AddSeconds(7)},
        new Job{JobType = JobTypes.JobType1, CreatedOn = DateTime.Now.AddSeconds(8)},
    };

    var summary = GetOrderedSummary(collection);

}

答案 4 :(得分:1)

Linq答案

public enum JobTypes
{
    JobType1,
    JobType2,
    JobType3
}

static void Main(string[] args)
{
    var collection = new[]
    {
        new {JobType = JobTypes.JobType1, CreatedOn = DateTime.Now},
        new {JobType = JobTypes.JobType2, CreatedOn = DateTime.Now.AddSeconds(1)},
        new {JobType = JobTypes.JobType2, CreatedOn = DateTime.Now.AddSeconds(2)},
        new {JobType = JobTypes.JobType2, CreatedOn = DateTime.Now.AddSeconds(3)},
        new {JobType = JobTypes.JobType2, CreatedOn = DateTime.Now.AddSeconds(4)},
        new {JobType = JobTypes.JobType3, CreatedOn = DateTime.Now.AddSeconds(5)},
        new {JobType = JobTypes.JobType1, CreatedOn = DateTime.Now.AddSeconds(7)},
        new {JobType = JobTypes.JobType1, CreatedOn = DateTime.Now.AddSeconds(8)}
    };

    var orderedCollection = collection.OrderBy(job => job.CreatedOn);
    var temp = orderedCollection.First().JobType;
    var identifier = 0;
    var summary = orderedCollection.Select(job =>
    {
        if (job.JobType == temp)
        {
            return new { JobType = job.JobType, Id = identifier };
        }

        temp = job.JobType;
        return new { JobType = job.JobType, Id = ++identifier };
    }).GroupBy(job => new { job.JobType, job.Id }).Select(job => new { JobType = job.Key.JobType, Count = job.Count() });

    foreach (var sum in summary)
    {
        Console.WriteLine("JobType: {0}, Count: {1}", sum.JobType, sum.Count);
    }

    Console.ReadLine();
}