C# Enumerable类 之 数据分组

总目录


前言

在 C# 中,System.Linq.Enumerable 类是 LINQ(Language Integrated Query)的核心组成部分,它提供了一系列静态方法,用于操作实现了 IEnumerable 接口的集合。通过这些方法,我们可以轻松地对集合进行查询、转换、排序和聚合等操作。

本文属于 C# Enumerable类 使用详解 中的一个章节,着重介绍 C# Enumerable 类中数据分组这部分的内容。


一、概览

方法描述示例
GroupBy数据分组people.GroupBy(p => p.Age);
ToLookup 数据分组people.ToLookup(p => p.Age);

二、GroupBy :数据分组

1. 什么是 GroupBy

GroupBy 是 LINQ 提供的一个扩展方法,用于将源集合中的元素按某个键值进行分组,并返回一个包含 IGrouping<TKey, TElement> 对象的集合。每个 IGrouping<TKey, TElement> 对象都表示一个组,其中包含该组的键和属于该组的所有元素。

2. GroupBy 方法 基本信息

GroupBy 方法用于根据指定的键对集合中的元素进行分组。

1) GroupBy

public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source,Func<TSource, TKey> keySelector)public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(this IEnumerable<TSource> source,Func<TSource, TKey> keySelector,Func<TSource, TElement> elementSelector)public static IEnumerable<TResult> GroupBy<TSource, TKey, TResult>(this IEnumerable<TSource> source,Func<TSource, TKey> keySelector,Func<TKey, IEnumerable<TSource>, TResult> resultSelector)
  • 参数

    • source:要分组的源集合。

    • keySelector:一个函数,用于从源集合的每个元素中提取分组键。

    • elementSelector(可选):一个函数,用于从源集合的每个元素中选择要包含在组中的元素。

      • 指定组内元素的映射方式(默认映射原元素)
    • resultSelector(可选):一个函数,用于定义如何将每个组转换为最终结果。

      • 自定义分组结果的输出格式
  • 返回值GroupBy 方法返回一个 IEnumerable<IGrouping<TKey, TElement>> 集合,其中:

    • TKey:表示分组依据的键类型。
    • TElement:表示原始集合中的元素类型。

2)工作原理

  1. 迭代源集合
    GroupBy 方法首先会迭代源集合中的每一个元素。对于每个元素,它会调用 keySelector 函数来提取分组键。

  2. 创建组
    根据提取的键值,GroupBy 方法会创建或找到相应的组。如果某个键值对应的组已经存在,则将当前元素添加到该组中;如果不存在,则创建一个新的组并将当前元素添加进去。

  3. 返回结果
    在遍历完所有元素后,GroupBy 方法返回一个包含所有组的集合。每个组都是一个 IGrouping<TKey, TElement> 对象,包含了键值和该组的所有元素。

  4. 惰性求值
    GroupBy 方法采用惰性求值(Lazy Evaluation),这意味着它不会立即执行分组操作,而是等到实际遍历时才进行计算。这使得 GroupBy 可以处理无限序列或延迟执行复杂的查询。

3)使用场景

  • 数据分类:当需要根据某个属性或多个属性对数据进行分类时。
  • 数据统计:当需要计算每个类别的统计信息(如计数、总和、平均值等)时。
  • 复杂的查询和分析:当需要对数据进行复杂的查询和分析时。

3. 使用示例

示例 1:基本分组

假设我们有一个包含若干 Person 对象的列表,每个对象都有 NameAge 属性。我们希望根据 Age 属性对这些对象进行分组。

class Person
{public string Name { get; set; }public int Age { get; set; }public override string ToString(){return $"{Name} ({Age})";}
}class Program
{static void Main(){List<Person> people = new List<Person>{new Person { Name = "Alice", Age = 30 },new Person { Name = "Bob", Age = 25 },new Person { Name = "Charlie", Age = 30 },new Person { Name = "David", Age = 25 }};var groupedPeople = people.GroupBy(p => p.Age);foreach (var group in groupedPeople){Console.WriteLine($"Age: {group.Key}");foreach (var person in group){Console.WriteLine($"  {person}");}}}
}

输出结果

Age: 30Alice (30)Charlie (30)
Age: 25Bob (25)David (25)

在这个例子中,我们使用 GroupBy 方法根据 Age 属性对 people 列表进行了分组,并打印了每个组及其成员。

示例 2:选择特定元素

有时我们只关心某些特定的属性,而不是整个对象。在这种情况下,可以使用 elementSelector 参数来选择要包含在组中的元素。

var groupedNames = people.GroupBy(p => p.Age, p => p.Name);foreach (var group in groupedNames)
{Console.WriteLine($"Age: {group.Key}");foreach (var name in group){Console.WriteLine($"  {name}");}
}

输出结果

Age: 30AliceCharlie
Age: 25BobDavid

在这个例子中,我们只选择了 Name 属性进行分组,并打印了每个组的名称。

示例 3:投影分组结果

我们可以使用 resultSelector 参数来自定义分组后的结果。例如,我们可以计算每个年龄段的人数。

var ageCounts = people.GroupBy(p => p.Age,(age, persons) => new { Age = age, Count = persons.Count() }
);foreach (var ageCount in ageCounts)
{Console.WriteLine($"Age: {ageCount.Age}, Count: {ageCount.Count}");
}

输出结果

Age: 30, Count: 2
Age: 25, Count: 2

在这个例子中,我们使用 resultSelector 参数创建了一个匿名对象,其中包含每个年龄段的人数。

示例 4:与其他LINQ方法结合

GroupBy 方法还可以与其他 LINQ 方法结合使用,以实现更复杂的数据处理。例如,我们可以使用 Select 方法来投影每个组的结果:

public class Student
{public string Name { get; set; }public int Grade { get; set; }
}public class Program
{public static void Main(){List<Student> students = new List<Student>{new Student { Name = "Alice", Grade = 10 },new Student { Name = "Bob", Grade = 11 },new Student { Name = "Charlie", Grade = 10 },new Student { Name = "David", Grade = 12 },new Student { Name = "Eve", Grade = 11 }};var summary = students.GroupBy(student => student.Grade).Select(group => new{Grade = group.Key,Count = group.Count(),Names = string.Join(", ", group.Select(s => s.Name))});foreach (var item in summary){Console.WriteLine($"Grade {item.Grade} has {item.Count} students: {item.Names}");}}
}

输出结果:

Grade 10 has 2 students: Alice, Charlie
Grade 11 has 2 students: Bob, Eve
Grade 12 has 1 students: David

示例4 和 示例5 效果是一样的,只不过示例4 是利用GroupBy 的一个重载方法实现,而示例5 是结合Select 方法实现的。

示例 5:多列分组

有时我们需要根据多个属性进行分组。可以通过组合多个属性来创建复合键。

var groupedByAgeAndName = people.GroupBy(p => new { p.Age, p.Name });foreach (var group in groupedByAgeAndName)
{Console.WriteLine($"Age: {group.Key.Age}, Name: {group.Key.Name}");foreach (var person in group){Console.WriteLine($"  {person}");}
}

输出结果

Age: 30, Name: AliceAlice (30)
Age: 25, Name: BobBob (25)
Age: 30, Name: CharlieCharlie (30)
Age: 25, Name: DavidDavid (25)

在这个例子中,我们根据 AgeName 属性创建了一个复合键,并对 people 列表进行了分组。

示例 6:使用自定义比较器

默认情况下,GroupBy 方法使用默认的相等比较器来比较键。如果需要自定义比较逻辑,可以传递一个 IEqualityComparer<TKey> 实现。

class Person
{public string Name { get; set; }public int Age { get; set; }public override string ToString(){return $"{Name} ({Age})";}
}class CustomComparer : IEqualityComparer<int>
{public bool Equals(int x, int y){// 自定义比较逻辑:年龄相差不超过1岁视为相同return Math.Abs(x - y) <= 1;}public int GetHashCode(int obj){return obj.GetHashCode();}
}class Program
{static void Main(){List<Person> people = new List<Person>{new Person { Name = "Alice", Age = 30 },new Person { Name = "Bob", Age = 25 },new Person { Name = "Charlie", Age = 30 },new Person { Name = "David", Age = 26 }};// 使用自定义比较器进行分组var customGroupedPeople = people.GroupBy(p => p.Age, new CustomComparer());foreach (var group in customGroupedPeople){Console.WriteLine($"Age: {group.Key}");foreach (var person in group){Console.WriteLine($"  {person}");}}}
}

输出结果

Age: 30Alice (30)Charlie (30)
Age: 25Bob (25)
Age: 26David (26)

在这个例子中,我们使用了一个自定义的比较器来分组年龄相差不超过1岁的人员。实际上并没有生效

示例 6:合并多个集合

有时我们需要合并多个集合并对合并后的结果进行分组。可以使用 ConcatUnion 方法先合并集合,然后再使用 GroupBy 进行分组。

class Person
{public string Name { get; set; }public int Age { get; set; }public override string ToString(){return $"{Name} ({Age})";}
}class Program
{static void Main(){List<Person> people = new List<Person>{new Person { Name = "Alice", Age = 30 },new Person { Name = "Bob", Age = 25 }};List<Person> morePeople = new List<Person>{new Person { Name = "Charlie", Age = 30 },new Person { Name = "David", Age = 40 }};// 合并两个集合var combinedPeople = people.Concat(morePeople);// 使用 GroupBy 对合并后的集合进行分组var groupedCombinedPeople = combinedPeople.GroupBy(p => p.Age);foreach (var group in groupedCombinedPeople){Console.WriteLine($"Age: {group.Key}");foreach (var person in group){Console.WriteLine($"  {person}");}}}
}

输出结果

Age: 30Alice (30)Charlie (30)
Age: 25Bob (25)
Age: 40David (40)

4. 注意事项

  • GroupBy 方法是一个延迟执行的操作,这意味着它不会立即执行分组操作,而是直到结果被枚举时才会执行。
  • 分组键必须是可比较的,否则会引发异常。
  • 如果源集合为空,GroupBy 方法将返回一个空的集合。

三、ToLookup :数据分组

1. 什么是 ToLookup

ToLookup 是 LINQ 提供的一个扩展方法,用于根据指定的键对集合中的元素进行分组,并返回一个 ILookup<TKey, TElement> 对象。每个 ILookup<TKey, TElement> 对象都表示一个键到多个元素的映射。

2. ToLookup 方法 基本信息

ToLookup 方法用于根据指定的键对集合中的元素进行分组,并返回一个 ILookup<TKey, TElement> 对象。

1) ToLookup

ToLookup 方法有多种重载形式,以下是几种常见的签名:

public static ILookup<TKey, TSource> ToLookup<TSource, TKey>(this IEnumerable<TSource> source,Func<TSource, TKey> keySelector
)public static ILookup<TKey, TElement> ToLookup<TSource, TKey, TElement>(this IEnumerable<TSource> source,Func<TSource, TKey> keySelector,Func<TSource, TElement> elementSelector
)public static ILookup<TKey, TSource> ToLookup<TSource, TKey>(this IEnumerable<TSource> source,Func<TSource, TKey> keySelector,IEqualityComparer<TKey> comparer
)public static ILookup<TKey, TElement> ToLookup<TSource, TKey, TElement>(this IEnumerable<TSource> source,Func<TSource, TKey> keySelector,Func<TSource, TElement> elementSelector,IEqualityComparer<TKey> comparer
)
  • 参数

    • source:要转换为 ILookup 的源集合。
    • keySelector:一个函数,用于从源集合的每个元素中提取分组键。
    • elementSelector(可选):一个函数,用于从源集合的每个元素中选择要包含在组中的元素。
    • comparer(可选):一个 IEqualityComparer<TKey> 实现,用于比较键值。
  • 返回类型ToLookup 方法返回一个 ILookup<TKey, TElement> 集合,其中:

    • TKey:表示分组依据的键类型。
    • TElement:表示原始集合中的元素类型。
    • 每个 ILookup<TKey, TElement> 对象都包含一个键和属于该键的所有元素。

2)工作原理

  1. 即时求值
    与 GroupBy 的惰性求值不同,ToLookup 是立即执行的。这意味着它会立即遍历整个源集合并构建结果。这使得 ToLookup 更适合需要频繁查询且希望避免重复执行分组操作的场景。

  2. 创建组
    ToLookup 方法首先会迭代源集合中的每一个元素。对于每个元素,它会调用 keySelector 函数来提取分组键。然后根据提取的键值创建或找到相应的组。

  3. 添加元素
    根据提取的键值,ToLookup 方法会将当前元素添加到对应的组中。如果某个键值对应的组已经存在,则将当前元素添加到该组中;如果不存在,则创建一个新的组并将当前元素添加进去。

  4. 返回结果
    在遍历完所有元素后,ToLookup 方法返回一个 ILookup<TKey, TElement> 对象,该对象包含了所有分组后的结果。由于 ILookup<TKey, TElement> 是只读的,一旦创建就不能修改。

  5. 多次查询
    由于 ILookup<TKey, TElement> 是预先计算好的,因此可以多次查询而不会重复执行分组操作。这对于需要频繁访问分组结果的应用场景非常有用。

3)使用场景

  • 数据分类:当需要根据某个属性或多个属性对数据进行分类时。
  • 频繁查询分组结果:当需要频繁查询分组结果且希望避免重复执行分组操作时。
  • 不可变的分组结果:当需要一个不可变的分组结果时。

3. 使用示例

示例 1:基本分组

假设我们有一个包含若干 Person 对象的列表,每个对象都有 NameAge 属性。我们希望根据 Age 属性对这些对象进行分组。

class Person
{public string Name { get; set; }public int Age { get; set; }public override string ToString(){return $"{Name} ({Age})";}
}class Program
{static void Main(){List<Person> people = new List<Person>{new Person { Name = "Alice", Age = 30 },new Person { Name = "Bob", Age = 25 },new Person { Name = "Charlie", Age = 30 },new Person { Name = "David", Age = 25 }};// 使用 ToLookup 根据 Age 属性进行分组var lookupPeople = people.ToLookup(p => p.Age);foreach (var group in lookupPeople){Console.WriteLine($"Age: {group.Key}");foreach (var person in group){Console.WriteLine($"  {person}");}}}
}

输出结果

Age: 30Alice (30)Charlie (30)
Age: 25Bob (25)David (25)

在这个例子中,我们使用 ToLookup 方法根据 Age 属性对 people 列表进行了分组,并打印了每个组及其成员。

示例 2:选择特定元素

有时我们只关心某些特定的属性,而不是整个对象。在这种情况下,可以使用 elementSelector 参数来选择要包含在组中的元素。

class Person
{public string Name { get; set; }public int Age { get; set; }public override string ToString(){return $"{Name} ({Age})";}
}class Program
{static void Main(){List<Person> people = new List<Person>{new Person { Name = "Alice", Age = 30 },new Person { Name = "Bob", Age = 25 },new Person { Name = "Charlie", Age = 30 },new Person { Name = "David", Age = 25 }};// 使用 ToLookup 并选择特定元素(仅选择 Name)var lookupNames = people.ToLookup(p => p.Age, p => p.Name);foreach (var group in lookupNames){Console.WriteLine($"Age: {group.Key}");foreach (var name in group){Console.WriteLine($"  {name}");}}}
}

输出结果

Age: 30AliceCharlie
Age: 25BobDavid

在这个例子中,我们只选择了 Name 属性进行分组,并打印了每个组的名称。

示例 3:自定义比较器

默认情况下,ToLookup 方法使用默认的相等比较器来比较键。如果需要自定义比较逻辑,可以传递一个 IEqualityComparer<TKey> 实现。

class Person
{public string Name { get; set; }public int Age { get; set; }public override string ToString(){return $"{Name} ({Age})";}
}class CustomComparer : IEqualityComparer<int>
{public bool Equals(int x, int y){// 自定义比较逻辑:年龄相差不超过1岁视为相同return Math.Abs(x - y) <= 1;}public int GetHashCode(int obj){return obj.GetHashCode();}
}class Program
{static void Main(){List<Person> people = new List<Person>{new Person { Name = "Alice", Age = 30 },new Person { Name = "Bob", Age = 25 },new Person { Name = "Charlie", Age = 30 },new Person { Name = "David", Age = 26 }};// 使用自定义比较器进行分组var customLookupPeople = people.ToLookup(p => p.Age, new CustomComparer());foreach (var group in customLookupPeople){Console.WriteLine($"Age: {group.Key}");foreach (var person in group){Console.WriteLine($"  {person}");}}}
}

输出结果:

Age: 30Alice (30)Charlie (30)
Age: 25Bob (25)
Age: 26David (26)

在这个例子中,我们使用了一个自定义的比较器来分组年龄相差不超过1岁的人员。实际上并没有生效

示例 4:多列分组

class Person
{public string Name { get; set; }public int Age { get; set; }public override string ToString(){return $"{Name} ({Age})";}
}class Program
{static void Main(){List<Person> people = new List<Person>{new Person { Name = "Alice", Age = 30 },new Person { Name = "Bob", Age = 25 },new Person { Name = "Charlie", Age = 30 },new Person { Name = "David", Age = 25 }};// 使用 ToLookup 进行多列分组(根据 Age 和 Name)var lookupByAgeAndName = people.ToLookup(p => new { p.Age, p.Name });foreach (var group in lookupByAgeAndName){Console.WriteLine($"Age: {group.Key.Age}, Name: {group.Key.Name}");foreach (var person in group){Console.WriteLine($"  {person}");}}}
}

输出结果:

Age: 30, Name: AliceAlice (30)
Age: 25, Name: BobBob (25)
Age: 30, Name: CharlieCharlie (30)
Age: 25, Name: DavidDavid (25)

在这个例子中,我们根据 AgeName 属性创建了一个复合键,并对 people 列表进行了分组。

示例 5:多次查询

由于 ILookup<TKey, TElement> 是预先计算好的,因此可以多次查询而不会重复执行分组操作。例如:

var lookupPeople = people.ToLookup(p => p.Age);// 第一次查询
foreach (var group in lookupPeople)
{Console.WriteLine($"Age: {group.Key}");foreach (var person in group){Console.WriteLine($"  {person}");}
}// 第二次查询
if (lookupPeople.Contains(30))
{Console.WriteLine("Age 30 exists:");foreach (var person in lookupPeople[30]){Console.WriteLine($"  {person}");}
}

在这个例子中,我们展示了如何多次查询同一个 ILookup<TKey, TElement> 对象而不重复执行分组操作。

4. ILookup<TKey, TElement> 接口

ILookup<TKey, TElement> 接口提供了以下主要成员:

  • Item[TKey]:通过键访问对应的元素集合。
  • Count:获取组的数量。
  • Contains(TKey):检查是否存在具有指定键的组。

示例 1:使用 ILookup 的特性

class Person
{public string Name { get; set; }public int Age { get; set; }public override string ToString(){return $"{Name} ({Age})";}
}class Program
{static void Main(){List<Person> people = new List<Person>{new Person { Name = "Alice", Age = 30 },new Person { Name = "Bob", Age = 25 },new Person { Name = "Charlie", Age = 30 },new Person { Name = "David", Age = 25 }};// 使用 ToLookup 根据 Age 属性进行分组var lookupPeople = people.ToLookup(p => p.Age);// 通过键访问对应的元素集合if (lookupPeople.Contains(30)){Console.WriteLine("Age 30 exists:");foreach (var person in lookupPeople[30]){Console.WriteLine($"  {person}");}}// 获取组的数量Console.WriteLine($"Total groups: {lookupPeople.Count}");}
}

输出结果:

Age 30 exists:Alice (30)Charlie (30)
Total groups: 2

在这个例子中,我们展示了如何使用 ILookup 的特性,如通过键访问对应的元素集合、检查是否存在具有指定键的组以及获取组的数量。

4. 注意事项

  • ToLookup 方法是一个立即执行的操作,这意味着它会立即对源集合进行分组操作,并将结果存储在 Lookup 对象中。
  • 分组键必须是可比较的,否则会引发异常。
  • 如果源集合为空,ToLookup 方法将返回一个空的 Lookup 对象。

四、 ToLookup 和 GroupBy 的区别

Enumerable.ToLookup 是 LINQ(Language Integrated Query)中的一个方法,用于将数据源转换为 ILookup<TKey, TElement> 类型的对象。与 GroupBy 方法类似,ToLookup 也用于对集合进行分组,但有一些关键的区别:

1. 概览

1)Enumerable.ToLookup

  • 即时求值ToLookup 方法会立即遍历整个源集合并构建结果。
  • 不可变性:返回的 ILookup<TKey, TElement> 对象是只读的,一旦创建就不能修改。
  • 多次查询:由于 ILookup<TKey, TElement> 是预先计算好的,因此可以多次查询而不会重复执行分组操作。
  • 返回类型:返回一个 ILookup<TKey, TElement> 集合。

2)Enumerable.GroupBy

  • 惰性求值GroupBy 方法采用惰性求值,只有在实际遍历时才会执行分组操作。
  • 可变性:返回的 IEnumerable<IGrouping<TKey, TElement>> 对象是可变的,可以根据需要动态添加或移除元素(尽管通常不建议这样做)。
  • 单次查询:每次遍历时都会重新执行分组操作,适合处理流式数据或无限序列。
  • 返回类型:返回一个 IEnumerable<IGrouping<TKey, TElement>> 集合。

2. 主要区别

1. 求值方式

  • ToLookup:立即求值。调用 ToLookup 方法时,它会立即遍历整个源集合并构建结果。这意味着所有分组操作在调用时完成,并且结果存储在内存中。这可能会消耗更多的内存,特别是在处理大型数据集时。

    var lookup = source.ToLookup(x => x.Key);
    // 立即执行分组操作
    
  • GroupBy:惰性求值。调用 GroupBy 方法时,它并不会立即执行分组操作,而是等到实际遍历时才进行计算。这使得 GroupBy 可以处理无限序列或延迟执行复杂的查询。这可以节省内存,特别是在处理大型数据集时。

    var groupBy = source.GroupBy(x => x.Key);
    // 分组操作直到实际遍历时才会执行
    

2. 结果类型与特性

  • ToLookup:返回一个 ILookup<TKey, TElement> 对象,该对象是只读的,并且支持通过键快速访问对应的元素集合。

    var lookup = source.ToLookup(x => x.Key);
    foreach (var group in lookup)
    {Console.WriteLine($"Key: {group.Key}");foreach (var item in group){Console.WriteLine($"  {item}");}
    }// 通过键访问特定组
    if (lookup.Contains(someKey))
    {foreach (var item in lookup[someKey]){Console.WriteLine(item);}
    }
    
  • GroupBy:返回一个 IEnumerable<IGrouping<TKey, TElement>> 集合,每个 IGrouping<TKey, TElement> 对象包含一个键和属于该键的所有元素。由于是惰性求值,每次遍历时都会重新执行分组操作。

    var groupBy = source.GroupBy(x => x.Key);
    foreach (var group in groupBy)
    {Console.WriteLine($"Key: {group.Key}");foreach (var item in group){Console.WriteLine($"  {item}");}
    }
    

3. 多次查询

  • ToLookup:由于 ILookup<TKey, TElement> 是预先计算好的,因此可以多次查询而不会重复执行分组操作。这对于需要频繁访问分组结果的应用场景非常有用。

    var lookup = source.ToLookup(x => x.Key);
    // 第一次查询
    foreach (var group in lookup) { ... }// 第二次查询
    if (lookup.Contains(someKey)) { ... }
    
  • GroupBy:每次遍历时都会重新执行分组操作。如果需要多次查询同一个分组结果,可能会导致性能问题。

    var groupBy = source.GroupBy(x => x.Key);
    // 每次遍历时都会重新执行分组操作
    foreach (var group in groupBy) { ... }
    

4. 自定义比较器

  • ToLookup:可以通过传递 IEqualityComparer<TKey> 实现来使用自定义比较器。

    var customLookup = source.ToLookup(x => x.Key, new CustomComparer());
    
  • GroupBy:也可以通过传递 IEqualityComparer<TKey> 实现来使用自定义比较器。

    var customGroupBy = source.GroupBy(x => x.Key, new CustomComparer());
    

5. 投影分组结果

  • ToLookup:可以使用 elementSelector 参数来自定义分组后的结果。

    var lookup = source.ToLookup(x => x.Key, x => x.Value);
    
  • GroupBy:同样可以使用 elementSelector / resultSelector参数来自定义分组后的结果。

    var groupBy = source.GroupBy(x => x.Key, x => x.Value);
    

3. 适用场景

1. ToLookup 的适用场景

  • 频繁查询:当你需要频繁查询分组结果,并希望避免重复执行分组操作时,ToLookup 是更好的选择。

    var lookup = people.ToLookup(p => p.Age);
    // 可以多次查询不同的年龄组
    
  • 固定数据集:当你的数据集是固定的,不需要动态添加或移除元素时,ToLookup 更加合适。

    var lookup = fixedData.ToLookup(x => x.Key);
    
  • 高性能需求:由于 ToLookup 是立即求值的,对于需要高性能的应用场景,它可以提供更快的查询速度。

2. GroupBy 的适用场景

  • 惰性求值:当你需要处理流式数据或无限序列时,GroupBy 的惰性求值特性非常适合这种场景。

    var infiniteNumbers = Enumerable.Range(0, int.MaxValue).Select(i => i * 2);
    var groupedNumbers = infiniteNumbers.GroupBy(n => n % 3);
    
  • 动态数据集:如果你的数据集是动态变化的,并且你可能需要根据新的数据动态调整分组结果,GroupBy 更加灵活。

    var dynamicData = GetDataStream();
    var groupBy = dynamicData.GroupBy(x => x.Key);
    
  • 一次性查询:如果你只需要对数据进行一次分组查询,那么 GroupBy 可能更简单且高效。

Enumerable.ToLookupGrouping.GroupBy 都是 LINQ 中用于对集合进行分组的方法,但它们在返回类型和使用场景上有一些重要的区别。


结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
微软官方文档 Enumerable

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/71991.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

推理模型对SQL理解能力的评测:DeepSeek r1、GPT-4o、Kimi k1.5和Claude 3.7 Sonnet

引言 随着大型语言模型&#xff08;LLMs&#xff09;在技术领域的应用日益广泛&#xff0c;评估这些模型在特定技术任务上的能力变得越来越重要。本研究聚焦于四款领先的推理模型——DeepSeek r1、GPT-4o、Kimi k1.5和Claude 3.7 Sonnet在SQL理解与分析方面的能力&#xff0c;…

IDEA接入阿里云百炼中免费的通义千问[2025版]

安装deepseek 上一篇文章IDEA安装deepseek最新教程2025中说明了怎么用idea安装codeGPT插件&#xff0c;并接入DeepSeek&#xff0c;无奈接入的官方api已经不能使用了&#xff0c;所以我们尝试从其他地方接入 阿里云百炼https://bailian.console.aliyun.com/ 阿里云百炼‌是阿…

实施一套先进的智能摄像头服务系统。

一、项目背景 随着物联网、人工智能和大数据技术的飞速发展&#xff0c;智能摄像头已成为家庭、企业以及公共安全领域的重要设备。其便捷、高效、智能的特点&#xff0c;使得市场需求日益增长。为了满足用户对智能监控的多样化需求&#xff0c;提供更加全面、可靠的监控服务&a…

linux自启动服务

在Linux环境中&#xff0c;systemd是一个系统和服务管理器&#xff0c;它为每个服务使用.service文件进行配置。systemctl是用于控制系统服务的主要工具。本文将详细介绍如何使用systemctl来管理vsftpd服务&#xff0c;以及如何设置服务自启动。 使用Systemd设置自启动服务 创…

010-Catch2

Catch2 一、框架简介 Catch2 是一个基于 C 的现代化单元测试框架&#xff0c;支持 TDD&#xff08;测试驱动开发&#xff09;和 BDD&#xff08;行为驱动开发&#xff09;模式。其核心优势在于&#xff1a; 单头文件设计&#xff1a;v2.x 版本仅需包含 catch.hpp 即可使用自然…

数字人分身开发指南:从概念到实战

一、什么是数字人分身&#xff1f; 想象一下&#xff0c;在电脑或手机屏幕里&#xff0c;一个能跟你聊天、回答问题&#xff0c;甚至还能做表情的虚拟角色。这就是数字人分身&#xff0c;它用上了人工智能技术&#xff0c;让机器也能像人一样交流。无论是在线客服、网络主播还…

Pixelmator Pro for Mac 专业图像处理软件【媲美PS的修图】

介绍 Pixelmator Pro&#xff0c;是一款非常强大、美观且易于使用的图像编辑器&#xff0c;专为 Mac 设计。采用单窗口界面、基于机器学习的智能图像编辑、自动水平检测&#xff0c;智能快速选择及更好的修复工具等功能优点。许多非破坏性的专业编辑工具可让您进行最佳的照片处…

LiveGBS流媒体平台GB/T28181常见问题-视频流安全控制HTTP接口鉴权勾选流地址鉴权后401Unauthorized如何播放调用接口流地址校验

LiveGBS流媒体平台GB/T28181常见问题频流安全控制HTTP接口鉴权勾选流地址鉴权后401Unauthorized如何播放调用接口流地址校验&#xff1f; 1、安全控制1.1、HTTP接口鉴权1.2、流地址鉴权 2、401 Unauthorized2.1、携带token调用接口2.1.1、获取鉴权token2.1.2、调用其它接口2.1.…

C++设计模式-抽象工厂模式:从原理、适用场景、使用方法,常见问题和解决方案深度解析

一、模式基本概念 1.1 定义与核心思想 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;是创建型设计模式的集大成者&#xff0c;它通过提供统一的接口来创建多个相互关联或依赖的对象族&#xff0c;而无需指定具体类。其核心思想体现在两个维度&#xff1a; …

【prompt实战】知乎问题解答专家

本文原创作者&#xff1a;姚瑞南 AI-agent 大模型运营专家&#xff0c;先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗&#xff1b;多年人工智能行业智能产品运营及大模型落地经验&#xff0c;拥有AI外呼方向国家专利与PMP项目管理证书。&#xff08;转载需经授权&am…

数据结构第八节:红黑树(初阶)

【本节要点】 红黑树概念红黑树性质红黑树结点定义红黑树结构红黑树插入操作的分析 一、红黑树的概念与性质 1.1 红黑树的概念 红黑树 &#xff0c;是一种 二叉搜索树 &#xff0c;但 在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是 Red和 Black 。 通过对 任何…

Spring Boot3.3.X整合Mybatis-Plus

前提说明&#xff1a; 项目的springboot版本为&#xff1a;3.3.2 需要整合的mybatis-plus版本&#xff1a;3.5.7 废话不多说&#xff0c;开始造吧 1.准备好数据库和表 2.配置全局文件application.properties或者是application.yml&#xff08;配置mapper的映射文件路径&am…

可视化图解算法:链表指定区间反转

1. 题目 描述 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例1 输入&#xff1a; 输入&#xff1a;head [1,2,3,4,5], left 2, right 4 输…

✨SQL-递归CTE

&#x1f4d6; SQL魔法课堂&#xff1a;CTE「时间折叠术」全解 &#x1f3a9; 第一章&#xff1a;什么是CTE&#xff1f; CTE&#xff08;Common Table Expression&#xff09; 就像 SQL 里的「临时笔记本」&#x1f4d2;&#xff1a; WITH 临时笔记本 AS ( SELECT ... FRO…

Cursor 新手入门使用教程

一、Cursor 是什么&#xff1f; Cursor 是一个集成了 GPT-4、Claude 3.5 等先进 LLM&#xff08;大语言模型&#xff09;的类 VSCode 编译器&#xff0c;可以理解为在 VSCode 中集成了 AI 辅助编程助手。从界面布局来看&#xff0c;Cursor 与 VSCode 基本一致&#xff0c;且使…

如何在Spring Boot中配置和使用MyBatis-Plus

在当今的Java开发中&#xff0c;Spring Boot已经成为了一个非常流行的框架&#xff0c;而MyBatis-Plus则是一个强大的ORM框架&#xff0c;为开发人员提供了更简便的数据库操作方式。很多开发者都在使用Spring Boot和MyBatis-Plus的组合来快速构建高效的应用。今天就来聊聊如何在…

【贪心算法3】

力扣1005.k次取反后最大化的数组和 链接: link 思路 既然要求最大和&#xff0c;那么不妨先给数组排个序&#xff0c;如果有负数&#xff0c;先处理负数从前往后给数组取反&#xff0c;如果负数处理完后k还有次数&#xff0c;此时数组全是正数了&#xff0c;只需要对第一个元…

自然语言处理中的语音识别技术:从声波到语义的智能解码

引言 语音识别&#xff08;Automatic Speech Recognition, ASR&#xff09;是自然语言处理&#xff08;NLP&#xff09;的关键分支&#xff0c;旨在将人类语音信号转化为可处理的文本信息。随着深度学习技术的突破&#xff0c;语音识别已从实验室走向日常生活&#xff0c;赋能…

1688店铺所有商品数据接口详解

​​一、接口概述淘宝开放平台提供 1688.items.onsale.get/taobao.item_search_shop 接口&#xff0c;可批量获取店铺在售商品列表&#xff0c;包含商品 ID、标题、价格、销量、图片等核心信息。该接口适用于商品库管理、竞品监控、数据分析等场景 ​二、接口调用流程 前期准…

ArduPilot开源代码之AP_OSD

ArduPilot开源代码之AP_OSD 1. 源由2. 简介3. 补丁4. 框架设计4.1 启动代码 (AP_OSD::init)4.2 任务代码 (AP_OSD::osd_thread)4.3 实例初始化 (AP_OSD::init_backend) 5. 重要例程5.1 AP_OSD::update_stats5.2 AP_OSD::update_current_screen5.3 AP_OSD::update_osd 6. 总结7.…