当前位置: 首页 > article >正文

Linq(C#)之对数据分组

分组是指将数据分到不同的组,使每组中的元素拥有公共的属性。 下图演示了对字符序列进行分组的结果。 每个组的键是字符。

1、group by

List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];

IEnumerable<IGrouping<int, int>> query = from number in numbers
                                         group number by number % 2;

foreach (var group in query)
{
    Console.WriteLine(group.Key == 0 ? "\nEven numbers:" : "\nOdd numbers:");
    foreach (int i in group)
    {
        Console.WriteLine(i);
    }
}

以下代码显示了使用方法语法的等效查询:

List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];

IEnumerable<IGrouping<int, int>> query = numbers
    .GroupBy(number => number % 2);

foreach (var group in query)
{
    Console.WriteLine(group.Key == 0 ? "\nEven numbers:" : "\nOdd numbers:");
    foreach (int i in group)
    {
        Console.WriteLine(i);
    }
}

2、对查询结果进行分组

分组是 LINQ 最强大的功能之一。 以下示例演示如何以各种方式对数据进行分组:

  • 依据单个属性。
  • 依据字符串属性的首字母。
  • 依据计算出的数值范围。
  • 依据布尔谓词或其他表达式。
  • 依据组合键。

此外,最后两个查询将其结果投影到一个新的匿名类型中,该类型仅包含学生的名字和姓氏。

按单个属性分组示例

以下示例演示如何通过使用元素的单个属性作为分组键对源元素进行分组。 键是一个 enum,学生的在校时间。 分组操作对该类型使用默认的相等比较器。

var groupByYearQuery =
    from student in students
    group student by student.Year into newGroup
    orderby newGroup.Key
    select newGroup;

foreach (var yearGroup in groupByYearQuery)
{
    Console.WriteLine($"Key: {yearGroup.Key}");
    foreach (var student in yearGroup)
    {
        Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
    }
}

以下示例显示了使用方法语法的等效代码:

// Variable groupByLastNamesQuery is an IEnumerable<IGrouping<string,
// DataClass.Student>>.
var groupByYearQuery = students
    .GroupBy(student => student.Year)
    .OrderBy(newGroup => newGroup.Key);

foreach (var yearGroup in groupByYearQuery)
{
    Console.WriteLine($"Key: {yearGroup.Key}");
    foreach (var student in yearGroup)
    {
        Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
    }
}

按值分组示例

下例演示如何通过使用除对象属性以外的某个项作为分组键对源元素进行分组。 在此示例中,键是学生姓氏的第一个字母。

var groupByFirstLetterQuery =
    from student in students
    let firstLetter = student.LastName[0]
    group student by firstLetter;

foreach (var studentGroup in groupByFirstLetterQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key}");
    foreach (var student in studentGroup)
    {
        Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
    }
}

需要嵌套 foreach 才能访问组项。

以下示例显示了使用方法语法的等效代码:

var groupByFirstLetterQuery = students
    .GroupBy(student => student.LastName[0]);

foreach (var studentGroup in groupByFirstLetterQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key}");
    foreach (var student in studentGroup)
    {
        Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
    }
}

按范围分组示例

以下示例演示如何通过使用某个数值范围作为分组键对源元素进行分组。 然后,查询将结果投影到一个匿名类型中,该类型仅包含学生的名字和姓氏以及该学生所属的百分等级范围。 使用匿名类型的原因是没有必要使用完整的 Student 对象来显示结果。 GetPercentile 是一个帮助程序函数,它根据学生的平均分数计算百分比。 该方法返回 0 到 10 之间的整数。

static int GetPercentile(Student s)
{
    double avg = s.Scores.Average();
    return avg > 0 ? (int)avg / 10 : 0;
}

var groupByPercentileQuery =
    from student in students
    let percentile = GetPercentile(student)
    group new
    {
        student.FirstName,
        student.LastName
    } by percentile into percentGroup
    orderby percentGroup.Key
    select percentGroup;

foreach (var studentGroup in groupByPercentileQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key * 10}");
    foreach (var item in studentGroup)
    {
        Console.WriteLine($"\t{item.LastName}, {item.FirstName}");
    }
}

需要嵌套 foreach 才能循环访问组和组项。 以下示例显示了使用方法语法的等效代码:

static int GetPercentile(Student s)
{
    double avg = s.Scores.Average();
    return avg > 0 ? (int)avg / 10 : 0;
}

var groupByPercentileQuery = students
    .Select(student => new { student, percentile = GetPercentile(student) })
    .GroupBy(student => student.percentile)
    .Select(percentGroup => new
    {
        percentGroup.Key,
        Students = percentGroup.Select(s => new { s.student.FirstName, s.student.LastName })
    })
    .OrderBy(percentGroup => percentGroup.Key);

foreach (var studentGroup in groupByPercentileQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key * 10}");
    foreach (var item in studentGroup.Students)
    {
        Console.WriteLine($"\t{item.LastName}, {item.FirstName}");
    }
}

按比较分组示例

以下示例演示如何通过使用布尔比较表达式对源元素进行分组。 在此示例中,布尔表达式会测试学生的平均考试分数是否超过 75。 与上述示例一样,将结果投影到一个匿名类型中,因为不需要完整的源元素。 匿名类型中的属性将成为 Key 成员上的属性。

var groupByHighAverageQuery =
    from student in students
    group new
    {
        student.FirstName,
        student.LastName
    } by student.Scores.Average() > 75 into studentGroup
    select studentGroup;

foreach (var studentGroup in groupByHighAverageQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key}");
    foreach (var student in studentGroup)
    {
        Console.WriteLine($"\t{student.FirstName} {student.LastName}");
    }
}

以下代码显示了使用方法语法的等效查询:

var groupByHighAverageQuery = students
    .GroupBy(student => student.Scores.Average() > 75)
    .Select(group => new
    {
        group.Key,
        Students = group.AsEnumerable().Select(s => new { s.FirstName, s.LastName })
    });

foreach (var studentGroup in groupByHighAverageQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key}");
    foreach (var student in studentGroup.Students)
    {
        Console.WriteLine($"\t{student.FirstName} {student.LastName}");
    }
}

按匿名类型分组

以下示例演示如何使用匿名类型来封装包含多个值的键。 在此示例中,第一个键值是学生姓氏的第一个字母。 第二个键值是一个布尔值,指定该学生在第一次考试中的得分是否超过了 85。 可以按照该键中的任何属性对组进行排序。

var groupByCompoundKey =
    from student in students
    group student by new
    {
        FirstLetterOfLastName = student.LastName[0],
        IsScoreOver85 = student.Scores[0] > 85
    } into studentGroup
    orderby studentGroup.Key.FirstLetterOfLastName
    select studentGroup;

foreach (var scoreGroup in groupByCompoundKey)
{
    var s = scoreGroup.Key.IsScoreOver85 ? "more than 85" : "less than 85";
    Console.WriteLine($"Name starts with {scoreGroup.Key.FirstLetterOfLastName} who scored {s}");
    foreach (var item in scoreGroup)
    {
        Console.WriteLine($"\t{item.FirstName} {item.LastName}");
    }
}

以下代码显示了使用方法语法的等效查询:

var groupByCompoundKey = students
    .GroupBy(student => new
    {
        FirstLetterOfLastName = student.LastName[0],
        IsScoreOver85 = student.Scores[0] > 85
    })
    .OrderBy(studentGroup => studentGroup.Key.FirstLetterOfLastName);

foreach (var scoreGroup in groupByCompoundKey)
{
    var s = scoreGroup.Key.IsScoreOver85 ? "more than 85" : "less than 85";
    Console.WriteLine($"Name starts with {scoreGroup.Key.FirstLetterOfLastName} who scored {s}");
    foreach (var item in scoreGroup)
    {
        Console.WriteLine($"\t{item.FirstName} {item.LastName}");
    }
}

创建嵌套组

以下示例演示如何在 LINQ 查询表达式中创建嵌套组。 首先根据学生年级创建每个组,然后根据每个人的姓名进一步细分为小组。

var nestedGroupsQuery =
    from student in students
    group student by student.Year into newGroup1
    from newGroup2 in
    from student in newGroup1
    group student by student.LastName
    group newGroup2 by newGroup1.Key;

foreach (var outerGroup in nestedGroupsQuery)
{
    Console.WriteLine($"DataClass.Student Level = {outerGroup.Key}");
    foreach (var innerGroup in outerGroup)
    {
        Console.WriteLine($"\tNames that begin with: {innerGroup.Key}");
        foreach (var innerGroupElement in innerGroup)
        {
            Console.WriteLine($"\t\t{innerGroupElement.LastName} {innerGroupElement.FirstName}");
        }
    }
}

需要使用三个嵌套的 foreach 循环来循环访问嵌套组的内部元素。
(将鼠标光标悬停在迭代变量 outerGroupinnerGroup 和 innerGroupElement 上以查看其实际类型。)

以下代码显示了使用方法语法的等效查询:

var nestedGroupsQuery =
    students
    .GroupBy(student => student.Year)
    .Select(newGroup1 => new
    {
        newGroup1.Key,
        NestedGroup = newGroup1
            .GroupBy(student => student.LastName)
    });

foreach (var outerGroup in nestedGroupsQuery)
{
    Console.WriteLine($"DataClass.Student Level = {outerGroup.Key}");
    foreach (var innerGroup in outerGroup.NestedGroup)
    {
        Console.WriteLine($"\tNames that begin with: {innerGroup.Key}");
        foreach (var innerGroupElement in innerGroup)
        {
            Console.WriteLine($"\t\t{innerGroupElement.LastName} {innerGroupElement.FirstName}");
        }
    }
}

对分组操作执行子查询

本文演示创建查询的两种不同方式,此查询将源数据排序成组,然后分别对每个组执行子查询。 每个示例中的基本方法是使用名为 newGroup 的“接续块”对源元素进行分组,然后针对 newGroup 生成新的子查询。 针对由外部查询创建的每个新组运行此子查询。 在此特定示例中,最终输出不是一个组,而是一个匿名类型的线性序列。

var queryGroupMax =
    from student in students
    group student by student.Year into studentGroup
    select new
    {
        Level = studentGroup.Key,
        HighestScore = (
            from student2 in studentGroup
            select student2.Scores.Average()
        ).Max()
    };

var count = queryGroupMax.Count();
Console.WriteLine($"Number of groups = {count}");

foreach (var item in queryGroupMax)
{
    Console.WriteLine($"  {item.Level} Highest Score={item.HighestScore}");
}

还可以使用方法语法编写上述代码片段中的查询。 下面的代码片段具有使用方法语法编写的语义上等效的查询。

var queryGroupMax =
    students
        .GroupBy(student => student.Year)
        .Select(studentGroup => new
        {
            Level = studentGroup.Key,
            HighestScore = studentGroup.Max(student2 => student2.Scores.Average())
        });

var count = queryGroupMax.Count();
Console.WriteLine($"Number of groups = {count}");

foreach (var item in queryGroupMax)
{
    Console.WriteLine($"  {item.Level} Highest Score={item.HighestScore}");
}


http://www.kler.cn/a/418184.html

相关文章:

  • 支持向量机算法:原理、实现与应用
  • Spark Optimization —— Reducing Shuffle
  • Android 系统之Init进程分析
  • GitLab的使用
  • 华为小米苹果三星移动设备访问windows共享文件夹windows11
  • yolo辅助我们健身锻炼
  • Springboot小知识(1):启动类与配置
  • Oracle--表空间Tablespace
  • 验证 kubelet 服务已经停止并且不再生成错误日志
  • 达梦数据库常用指令都是工作中常用的
  • 2024金盾信安杯线上赛 MISC ezpng[wp]
  • 【如何提升代码工程质量】code review篇
  • 【机器学习】机器学习学习笔记 - 数据预处理 - 01
  • C++(四)
  • 【系统架构设计师】高分论文:论分布式架构设计及其实现
  • 基于Java Springboot宠物咖微信小程序
  • 指针与字符串简单练习
  • 深入研究:Vue.js 响应式系统的原理与优化
  • ospf协议(动态路由协议)
  • 架构师的英文:Architect
  • Data Guard 三种保护模式介绍
  • java八股-Redis Stream和RocketMQ实现的解决方案
  • 蓝牙定位的MATLAB程序,四个锚点、三维空间
  • python excel接口自动化测试框架!
  • 数据结构-查找(四)总结与对比
  • IDEA的简易安装思路