使用 GroupBy 将平面 List<T> 转换为 List<U> 以生成汇总数据

Convert flat List<T> to a List<U> using GroupBy to produce rolled up data

UPDATE 我还需要保留 List<T>List<U> 中员工首次进入的索引以备后用。目前我正在使用从

下面的 修改的代码
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 函数)但无法解决问题。

您可以使用 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
});

主要有两种方法。第一个是用 list.Select(emp => new { foo = emp.Where(e => e.empid == emp.empid) }) 保持 selecting 在你的初始列表上......但这在计算上很糟糕,并且不如...... 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进行操作,以供将来的汇总和selects使用。然后,您 select 一个新的输出类型,并且您在 属性 本身上有第二个内部 select。

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

并不比

复杂多少
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> 的分组,其中每个 KeyValuePairKey 属性 是原始列表中的整数位置及其 Value 属性 是原始 Employee 实例。

只需进行由此产生的更改即可。