【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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章