使用LINQ根据两个特性对列表中的项目进行分组

本文关键字:列表 项目 LINQ 两个 使用 | 更新日期: 2023-09-27 18:00:15

我有一个Column类,如下所示:

public class Column
{
    public int LocId { get; set; }
    public int SecId { get; set; }
    public double StartElevation { get; set; }
    public double EndElevation { get; set; }
}

以及Column对象列表:

List<Column> Columns = new List<Column>();

例如:

Columns:
{
    Column1: { LocId = 1 , SecId = 1, StartElevation = 0, EndElevation = 160 }
    Column2: { LocId = 1 , SecId = 1, StartElevation = 160, EndElevation = 320 }
    Column3: { LocId = 1 , SecId = 2, StartElevation = 320, EndElevation = 640 }
    Column4: { LocId = 2 , SecId = 1, StartElevation = 0, EndElevation = 160 }
    Column5: { LocId = 2 , SecId = 2, StartElevation = 160, EndElevation = 320 }
}

我想使用Linq将下面的算法应用于上面的列表。

浏览Columns列表并:

  • (A)选择具有相同LocId的项目列表

  • (B)然后从该列表中选择另一个具有相同SecId的项目列表

这将给我一个列表,希望我能在上面做其他事情。

因此,将上述算法应用于上述数据将是这样的。

                                       Columns List
                ---------------------------------------------------------
               | Column1     Column2     Column3     Column4     Column5 |
                ---------------------------------------------------------
                                            |
                                            |
                                           (A)
                                            |
                                            |
                        --------------------------------------------
                        |                                          |
                GroupBasedOnLocId                         GroupBasedOnLocId
                        |                                          |
                  -----------                                 -----------
                  | Column1 |                                 | Column4 |
                  | Column2 |                                 | Column5 |
                  | Column3 |                                 -----------
                  -----------                                      |
                       |                                           |
                      (B)                                         (B)
                       |                                           |
          -------------------------                   -------------------------
          |                       |                   |                       |
          |                       |                   |                       |
 GroupBasedOnSecId        GroupBasedOnSecId  GroupBasedOnSecId        GroupBasedOnSecId
          |                       |                   |                       |
          |                       |                   |                       |
       Column1                 Column3             Column4                 Column5
       Column2

如何使用LINQ实现这一点?

使用LINQ根据两个特性对列表中的项目进行分组

使用带有复合键的.GroupBy

样本代码:

List<Column> Columns = new List<Column>
{
   new Column { LocId = 1 , SecId = 1, StartElevation = 0, EndElevation = 160 },
   new Column { LocId = 1 , SecId = 1, StartElevation = 160, EndElevation = 320 },
   new Column { LocId = 1 , SecId = 2, StartElevation = 320, EndElevation = 640 },
   new Column { LocId = 2 , SecId = 1, StartElevation = 0, EndElevation = 160 },
   new Column { LocId = 2 , SecId = 2, StartElevation = 160, EndElevation = 320 }
};
foreach (var group in Columns.GroupBy(c => new { c.LocId, c.SecId }))
{
    Console.WriteLine("group: LocId = {0}, SecId = {1}", group.Key.LocId, group.Key.SecId);
    foreach(Column column in group)
        Console.WriteLine("  item: StartElevation = {0}, EndElevation = {1}", column.StartElevation, column.EndElevation);
}

你可以用任何你想要的方式转换群组:

foreach (var res in Columns.GroupBy(c => new { c.LocId, c.SecId })
                           .Select(g => new
                           {
                               g.Key.LocId,
                               g.Key.SecId,
                               MinStartElevation = g.Min(c => c.StartElevation),
                               MaxEndElevation = g.Max(c => c.EndElevation)
                           }))
{
    Console.WriteLine("LocId = {0}, SecId = {1}, MinStartElevation = {2}, MaxEndElevation = {3}",
        res.LocId, res.SecId, res.MinStartElevation, res.MinStartElevation);
}

如果您想要如图所示的两级分组,则需要使用GroupBy两次:

var grouping = columns
  .GroupBy(col => new { col.LocId, col.SecId }) // create groups by LocId+SecId
  .GroupBy(group => group.Key.LocId); // re-group by LocId only

然后,您将有一个组序列,每个组都有一个具有int密钥(即LocId)的,并由一系列其他组组成,每个组具有LocIdSecId的复合密钥,并由列序列组成(与LocIdSecId匹配)。

然后,您可以通过在每个级别上使用foreach来访问两级分组。例如:

foreach (var locGroup in grouping) {
  Console.WriteLine("LocId: " + locGroup.Key)
  foreach (var secGroup in locGroup) {
    Console.WriteLine("  SecId:" + secGroup.Key.SecId)
      Console.WriteLine("  Min StartElevation: {0}", 
        secGroup.Min(col => col.StartElevation);
      Console.WriteLine("  Max EndElevation: {0}", 
        secGroup.Max(col => col.EndElevation);
      foreach (var column in secGroup) {
        Console.WriteLine("    {0} -> {1}", column.StartElevation, column.EndElevation);
      }
    }
  }
}

或者,如果您想在树中找到特定的节点,可以使用ToDictionaryToLookup:

var lookup = columns
  // group columns by LocId
  .GroupBy(col => col.LocId) 
  // create a dictionary from the groups to find them by LocId,
  // where the value of each entry is a lookup of its own columns by SecId
  .ToDictionary(             
     locGroup => locGroup.Key, 
     locGroup => locGroup.ToLookup(col => col.SecId));

然后你可以做一些事情,比如:

var locId = "123";
var locGroup = lookup[locId];
Console.WriteLine("LocId {0} has {1} sub-groups", locId, locGroup.Count);
Console.WriteLine("LocId {0} has {1} total columns", locId, 
  locGroup.Sum(secGroup => secGroup.Count()));
var secId = "456";
var secGroup = locGroup[secId];
Console.WriteLine("LocId {0}, SecId {1} has {2} columns", 
  locId, secId, secGroup.Count());

这里有一个使用LINQ查询语法的解决方案。(查询语法和方法语法在语义上是相同的,但许多人发现查询语法更简单、更容易阅读。)

// Declare and then populate the LINQ source.
List<Column> columns = new List<Column>();
var query =
    from column in columns
    group column by new {column.LocId, column.SecId} into g
    orderby g.Key.LocId, g.Key.SecId
    select new
    {
        LocId = g.Key.LocId,
        SecId = g.Key.SecId,
        Columns = g
    };

下面,您将使用您提供的数据找到此LINQ查询的完整演示程序。在演示程序之前,我已经介绍了它的预期输出。另请参阅现场演示。

预期输出

LocId:1, SecId:1
  StartElevation:0, EndElevation:160
  StartElevation:160, EndElevation:320
LocId:1, SecId:2
  StartElevation:320, EndElevation:640
LocId:2, SecId:1
  StartElevation:0, EndElevation:160
LocId:2, SecId:2
  StartElevation:160, EndElevation:320

程序

using System;
using System.Collections.Generic;
using System.Linq;
class LinqGroupDemo
{
    static public void Main(string[] args)
    {
        var query =
            from column in GetSource()
            group column by new {column.LocId, column.SecId} into g
            orderby g.Key.LocId, g.Key.SecId
            select new
            {
                LocId = g.Key.LocId,
                SecId = g.Key.SecId,
                Columns = g
            };
        foreach (var key in query)
        {
            Console.WriteLine("LocId:{0}, SecId:{1}",
                              key.LocId,
                              key.SecId);
            foreach (var column in key.Columns)
            {
                Console.WriteLine("  StartElevation:{0}, EndElevation:{1}",
                                  column.StartElevation,
                                  column.EndElevation);
            }
        }
    }
    static private List<Column> GetSource()
    {
        return new List<Column>
        {
            new Column { LocId = 1 , SecId = 1, StartElevation = 0, EndElevation = 160 },
            new Column { LocId = 1 , SecId = 1, StartElevation = 160, EndElevation = 320 },
            new Column { LocId = 1 , SecId = 2, StartElevation = 320, EndElevation = 640 },
            new Column { LocId = 2 , SecId = 1, StartElevation = 0, EndElevation = 160 },
            new Column { LocId = 2 , SecId = 2, StartElevation = 160, EndElevation = 320 }
        };
    }
}
public class Column
{
    public int LocId { get; set; }
    public int SecId { get; set; }
    public double StartElevation { get; set; }
    public double EndElevation { get; set; }
}

使用GroupBy:

var results = columns
    .GroupBy(column => column.LocId)
    .Select(group => group.GroupBy(c => c.Sec.Id));