【LINQ】System.Linq.Enumerable.Aggregate

Aggregate是LINQ中可以自定义功能的聚合函数,该函数输入一个可枚举的序列,输出单个值。在微软的官方文档中给出了三个重载,分别如下:

1. 对序列应用累加器函数

1.1 函数原型

public static TSource Aggregate<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, 
                                          Func<TSource,TSource,TSource> func);

其中:

  • System.Collections.Generic.IEnumerable source 指出参数source必须是可枚举类型,TSource指出参数source中元素的类型。
  • Func<TSource,TSource,TSource> funcunc 指示要对每个元素调用的累加器函数。第一个与第二个TSource指出累加器函数的参数类型,第三个TSource指出累加器函数的返回值类型

1.2 样例解析

使用Aggregate对元素进行求和,这里给出了三种方式

private static int Add(int x, int y){return x + y;}

void Main()
{
	var numbers = new List<int> { 6, 2, 8, 3 };
	// 方式一
	int sum1 = numbers.Aggregate(func: (result, item) => result + item);
	System.Console.WriteLine("Method 1: " + sum1);
	
	// 方式二
	int sum2 = numbers.Aggregate(func: Add);
	System.Console.WriteLine("Method 2: " + sum2);
	
	// 方式三
	int sum3 = Enumerable.Aggregate<int>(numbers, func:(result, item) => result + item);
	System.Console.WriteLine("Method 3: " + sum3);
}

结果:
Method 1: 19
Method 2: 19
Method 3: 19

1.3 累加器的执行过程

为了进一步分析累加器函数是如何工作的,对上面的方式三进行修改,如下:

int sum3 = Enumerable.Aggregate<int>(numbers, func: (result, item) => 
{
	System.Console.WriteLine("Result: " + result + ", Item: " + item);
	return result + item;
});

结果:
Result: 6, Item: 2
Result: 8, Item: 8
Result: 16, Item: 3

由输出可以得出以下信息:

  • 第一次进行计算时,累加器的第一个参数与第二个参数分别接收列表中的第一个与第二个元素,值分别为6与2
  • 累加器第一次执行的返回结果被存储到一个临时变量,该临时变量在累加器第二次调用时被当作参数输入给累加器的第一个参数,累加器的第二个参数使用列表的第三个元素
  • 依次类推,累加器第三次调用使用第二次调用的返回值作为第一个参数,列表中的第四个参数作为第二个参数

1.4 累加器的内部实现方式

public static TSource Aggregate<TSource>( this IEnumerable<TSource> source,
                                          Func<TSource, TSource, TSource> func)
{
    if (source == null) throw Error.ArgumentNull("source");
    if (func == null) throw Error.ArgumentNull("func");

    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        if (!e.MoveNext()) throw Error.NoElements();
        TSource result = e.Current;
        while (e.MoveNext()) {
            result = func(result, e.Current);
        }
        return result;
    }
}

2. 对序列应用累加器函数,并将指定的种子值用作累加器初始值

2.1 函数原型

该重载相对于上一个仅仅是给累加器增加了一个初始值。但该初始值的类型可以与列表元素的类型不同。上一个函数可以看作该函数的特殊版本,累加器的输入参数类型与返回值类型相同

public static TAccumulate Aggregate<TSource,TAccumulate> (this System.Collections.Generic.IEnumerable<TSource> source, 
                                                          TAccumulate seed, 
                                                          Func<TAccumulate,TSource,TAccumulate> func);

其中:

  • System.Collections.Generic.IEnumerable source 指出参数source必须是可枚举类型,TSource指出参数source中元素的类型。
  • TAccumulate seed 指定累加器的类型与初始值
  • Func<TAccumulate,TSource,TAccumulate> func 指示要对每个元素调用的累加器函数。第一个与第三个参数类型TAccumulate指出累加器函数的第一个参数与返回值的类型,第二个参数TSource对应于要执行累加功能的序列的元素类型

2.2 样例解析

这里同样使用计算列表中元素合的方式。

private static int Add(int x, int y){return x + y;}

void Main()
{
	var numbers = new List<int> { 6, 2, 8, 3 };
	// 方式一
	int sum1 = numbers.Aggregate(seed: 0, func: (result, item) => result + item);
	System.Console.WriteLine("Method 1: " + sum1);
	
	// 方式二
	int sum2 = numbers.Aggregate(seed: 0, func: Add);
	System.Console.WriteLine("Method 2: " + sum2);
	
	// 方式三
	int sum3 = Enumerable.Aggregate<int, int>(source: numbers, 
	                                          seed: 0, 
	                                          func:(result, item) => result + item);
	System.Console.WriteLine("Method 3: " + sum3);
}

结果:
Method 1: 19
Method 2: 19
Method 3: 19

2.3 累加器的执行过程

为了进一步分析累加器函数是如何工作的,对上面的方式三进行修改,如下:

int sum3 = Enumerable.Aggregate<int, int>(source: numbers, seed: 0, func: (result, item) => 
{
	System.Console.WriteLine("Result: " + result + ", Item: " + item);
	return result + item;
});

结果:
Result: 0, Item: 6
Result: 6, Item: 2
Result: 8, Item: 8
Result: 16, Item: 3

由输出可以得出以下信息:

  • 第一次进行计算时,累加器的第一个参数使用初始值,即参数seed的值;
  • 第二个参数使用列表的第一个元素
  • 累加器第一次执行的返回结果被存储到一个临时变量,该临时变量在累加器第二次调用时被当作参数输入给累加器的第一个参数,累加器的第二个参数使用列表的第二个元素
  • 依次类推,累加器第三次调用使用第二次调用的返回值作为第一个参数,列表中的第三个参数作为第二个参数

2.4 列表的元素类型可以隐式转换到累加器的返回类型

void Main()
{
	var numbers = new List<int> { 6, 2, 8, 3 };
	double sum = Enumerable.Aggregate<int, double>(source: numbers, seed: 0D, func: (result, item) =>
	{
		System.Console.WriteLine("Result Type: " + result.GetType() + ", Result: " + result + ", Item: " + item);
		return result + item;
	});
	System.Console.WriteLine("Method 3: " + sum);
}

结果:
Result Type: System.Double, Result: 0, Item: 6
Result Type: System.Double, Result: 6, Item: 2
Result Type: System.Double, Result: 8, Item: 8
Result Type: System.Double, Result: 16, Item: 3
Method 3: 19

2.5 列表的元素类型无法隐式转换到累加器的返回类型

计算班级中所有学生的年龄总和。

var students = new[] 
{ 
	new Student() {Name="张三", Age = 20},
	new Student() {Name="李四", Age = 21},
	new Student() {Name="王五", Age = 22},
	new Student() {Name="赵六", Age = 23}
};
int sum = Enumerable.Aggregate<Student, int>(source: students, seed: 0, func: (result, item) =>
{
	System.Console.WriteLine("Result Type: " + result.GetType() + ", Result: " + result + ", Item.Age: " + item.Age);
	return result + item.Age;
});
System.Console.WriteLine("Method 3: " + sum);

结果:
Result Type: System.Int32, Result: 0, Item.Age: 20
Result Type: System.Int32, Result: 20, Item.Age: 21
Result Type: System.Int32, Result: 41, Item.Age: 22
Result Type: System.Int32, Result: 63, Item.Age: 23
Method 3: 86

3. 对序列应用累加器函数。 将指定的种子值用作累加器的初始值,并使用指定的函数选择结果值

3.1 函数原型

该累加函数的返回结果的类型,可以既不与列表中元素的类型相同,也不与累加器初始值的类型相同。

public static TResult Aggregate<TSource,TAccumulate,TResult> (this System.Collections.Generic.IEnumerable<TSource> source, 
                                                              TAccumulate seed, 
                                                              Func<TAccumulate,TSource,TAccumulate> func, 
                                                              Func<TAccumulate,TResult> resultSelector);
  • System.Collections.Generic.IEnumerable source 要聚合的IEnumerable
  • TAccumulate seed 累加器的初始值
  • Func<TAccumulate,TSource,TAccumulate> func 要对每个元素调用的累加器函数
  • Func<TAccumulate,TResult> resultSelector 将累加器的最终值转换为结果值的函数

3.2 样例解析

计算班级内学生的平均年龄

class Student
{
	public string Name { get; set; }
	public int Age { get; set; }
}

void Main()
{
	var students = new[] 
	{ 
		new Student() {Name="张三", Age = 20},
		new Student() {Name="李四", Age = 21},
		new Student() {Name="王五", Age = 22},
		new Student() {Name="赵六", Age = 23}
	};
	
	double sum = Enumerable.Aggregate<Student, int, double>(
	source: students, 
	seed: 0, 
	
	func: (result, item) =>
	{
		System.Console.WriteLine("Result Type: " + result.GetType() + ", Result: " + result + ", Item.Age: " + item.Age);
		return result + item.Age;
	},
	
	resultSelector: result =>
	{
		System.Console.WriteLine("=======================================");
		System.Console.WriteLine("ResultSelector Parameter: " + result);
		return (double)result / students.Length;
	});
	
	System.Console.WriteLine("Average Age: " + sum);
}

结果:
Result Type: System.Int32, Result: 0, Item.Age: 20
Result Type: System.Int32, Result: 20, Item.Age: 21
Result Type: System.Int32, Result: 41, Item.Age: 22
Result Type: System.Int32, Result: 63, Item.Age: 23
=======================================
ResultSelector Parameter: 86
Average Age: 21.5

3.3 累加器的内部实现方式

public static TResult Aggregate<TSource, TAccumulate, TResult>(
    this IEnumerable<TSource>               source,
    TAccumulate                             seed,
    Func<TAccumulate, TSource, TAccumulate> func,
    Func<TAccumulate, TResult>              resultSelector)
{
    if (source == null) throw Error.ArgumentNull("source");
    if (func == null) throw Error.ArgumentNull("func");
    if (resultSelector == null) throw Error.ArgumentNull("resultSelector");
    
    TAccumulate result = seed;
    foreach (TSource element in source) {
        result = func(result, element);
    }
    return resultSelector(result);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhy29563

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值