使用GroupBy将平面List <t>转换为List <u>以生成汇总数据</u> </t>

时间:2015-01-27 18:02:36

标签: c# .net linq list generics

更新我还需要保留List<T>中来自List<U>的员工的第一个条目的索引以供日后使用。目前我正在使用从Nicolas's answer下面修改的代码

List<EmployeeRollup> summary =
      details
      .GroupBy( e => e.EmployeeId , StringComparer.OrdinalIgnoreCase )
      .Select( g => new EmployeeRollup {
        EmployeeId      = g.Key ,
        ProjectDateFrom = g.Min( e => e.ProjectDate ) ,
        ProjectDateThru = g.Max( e => e.ProjectDate ) ,
        FullRecordsRef = employee
                         .FindIndex(f => f.employeeId == g.Key),
        ProjectCodes    = g.Select( e => e.ProjectCode )
                                          .Distinct( StringComparer
                                          .OrdinalIgnoreCase )
                                          .ToArray() ,
      }).ToList();

这种做法是否正确?有没有更有效的方法呢?

<- End Update ->

我有一个应用程序,我想将对象列表List<T>转换为另一个对象列表List<U>

原始列表(List<T>)为List<Employee>,其中Employee定义为

class Employee{
  public string empid;
  public Date proj_date;
  public string proj_code;
  // other fields and methods
}

,列表中的数据看起来像

empid    proj_date     proj_code 
01     21/Nov/2014       02
01     21/Nov/2014       03
02     21/Nov/2014       09
02     22/Nov/2014       99
02     23/Nov/2014       09
03     21/Nov/2014       15
03     01/Dec/2014       16

我想将此List<Employee>转换为另一个列表,List<Emp2>其中Emp2定义为

class Emp2{
  public string empid;
  public Date min_proj_date;
  public Date max_proj_date;
  public string [] proj_code;
  // other fields and methods
}

List<Employee>转换后,List<Emp2>中的数据应如下所示

empid    min_proj_date   max_proj_date  proj_code[] 
01     21/Nov/2014       21/Nov/2014       [02, 03]
02     21/Nov/2014       23/Nov/2014       [09,99]
03     21/Nov/2014       01/Dec/2014       [15,16]

所以我正在做的是

  • 按employee_id分组,以获取员工的所有不同日期和proj_codes。
  • 获取日期的最小值和最大值
  • 获取proj_code的不同值作为数组

我尝试使用MoreLINQ库中的DistinctBy函数,但无法解决问题。

3 个答案:

答案 0 :(得分:2)

您可以使用GroupBySelect从一个转换为另一个,如下所示:

var myEmps = new List<Employee> { /* data here */ };
var myEmp2s = myEmps
    .GroupBy(x => x.empid)
    .Select(x => new Emp2
{
    empid= x.Key,
    min_project_date = x.Min(y => y.proj_date),
    max_project_date = x.Max(y => y.proj_date),
    proj_code = x.Select(y => y.proj_code).ToArray()

    // Other fields are rolled up in a similar fashion as needed
});

答案 1 :(得分:2)

主要有两种方法。第一个是继续使用list.Select(emp => new { foo = emp.Where(e => e.empid == emp.empid) })来选择初始列表...但这在计算上很糟糕,并且不如... GroupBy惯用。

您似乎还想要一些OrderByDistinct条款,而Date可能意味着DateTime?否则,根据需要进行调整。

var empGroups = emp.GroupBy(e => e.empid);
empGroups.Select(g => new {
    empid = g.Key,
    min_proj_date = g.Min(e => e.proj_date.Date),
    max_proj_date = g.Min(e => e.proj_date.Date),
    proj_code = g.Select(e => e.proj_code).OrderBy(pc => pc).Distinct().ToArray()
})

通过首次分组,您可以对该组的Key进行操作以供将来汇总和选择。然后,为输出选择一种新类型,并在属性本身上进行第二次内部选择。

请注意GroupBy结果的枚举类型为IGrouping<(string) TKey, (Employee) TElement> inherits from IEnumerable<TElement>。抓取g后,其类型现为IEnumerable<Employee>,并且可以访问g.Key。从那时起,像对待任何其他IEnumerable那样对待它,并使用熟悉的LINQ。这基本上是实例化变量,循环和添加到列表的替代方法;变量在lambda中定义并提升到你的lambda中。

答案 2 :(得分:1)

没有比

复杂得多
public class Employee
{
  public string EmployeeId    ;
  public DateTime ProjectDate ;
  public string   ProjectCode ;
}

public class EmployeeRollup
{
  public string EmployeeId        ;
  public DateTime ProjectDateFrom ;
  public DateTime ProjectDateThru ;
  public string[] ProjectCodes    ;
}

class Program
{
  static void Main(string[] args)
  {
    List<Employee>       details = new List<Employee>() ;
    List<EmployeeRollup> summary =
      details
      .GroupBy( e => e.EmployeeId , StringComparer.OrdinalIgnoreCase )
      .Select( g => new EmployeeRollup {
        EmployeeId      = g.Key ,
        ProjectDateFrom = g.Min( e => e.ProjectDate ) ,
        ProjectDateThru = g.Max( e => e.ProjectDate ) ,
        ProjectCodes    = g.Select( e => e.ProjectCode )
                                          .Distinct( StringComparer
                                          .OrdinalIgnoreCase )
                                          .ToArray() ,
      })
      .ToList()
      ;
  }
}

如果您想跟踪原始列表中每个Employee实例的偏移量(位置),您可以执行以下操作:

    List<Employee>       details = new List<Employee>() ;
    int i = 0 ;
    List<EmployeeRollup> summary =
      details
      .Select( e => new KeyValuePair<int,Employee>(i,e) )
      .GroupBy( kvp => kvp.Value.EmployeeId , StringComparer.OrdinalIgnoreCase )
      ...

现在您的分组为KeyValuePair<int,Employee>,其中每个KeyValuePair Key属性是原始列表中的整数位置,其Value属性为原始Employee实例。

只需进行更改即可。