今天,我們將著眼於五個用於序列的彙總運算。很多時候當我們在對序列進行操作時,我們想要做基於這些序列執行某種匯總然後,計算結果。
Enumerable 靜態類的LINQ擴充方法可以做到這一點 。就像之前大多數的LINQ擴充方法一樣,這些是基於IEnumerable <TSource>序列的操作。
SUM() - 計算整個序列的總和
它有兩種形式:
- SUM( )
- 計算整個序列的總值。
- 源類型必須是以下類型之一: int,long,double,decimal,single 或這些類型的可空變種(int?, long?, double?…) 。
- Sum(Func<TSource, X> projection)
- 計算序列投影值的和。
- 從MSDN上,我們得知X必須是以下類型之一:int,long,double,decimal,single 或這些類型的可空變種(int?, long?, double?…) 。
在這裡請注意幾件事情。
首先,儘管在C#中支援許多類型,SUM()方法-非投影式-只支援int,long,double,decimal,single 。
1: // 正確
2: double[] data = { 3.14, 2.72, 1.99, 2.32 };
3: var result = data.Sum();
4:
5: //不支援
6: short[] shortData = { 1, 2, 5, 7 };
7:
8: // 出現編輯錯誤
9: var shortResult = shortData.Sum();
還要注意的是,你可以操作上面這些類型允許Null 的可空值變種。在之前我們討論過,可為空白的類型可以是一個棘手的事情,但用SUM()時我們不用擔心,因為所有的空值在求和時都排除了 :
var data = new List<int?> { 1, 3, 9, 13, null, 7, 12, null };
var result = data.Sum();
第二,投影形式是一個比較有趣和有用的方法:
為了說明這一點,讓我們假設一個簡單的POCO Employee:
public sealed class Employee
{
public string Name { get; set; }
public double Salary { get; set; }
public short Dependents { get; set; }
}
var employees = new List<Employee>
{
new Employee { Name = "Bob", Salary = 35000.00, Dependents = 0 },
new Employee { Name = "Sherry", Salary = 75250.00, Dependents = 1 },
new Employee { Name = "Kathy", Salary = 32000.50, Dependents = 0 },
new Employee { Name = "Joe", Salary = 17500.00, Dependents = 2 },
};
然後我們就可以使用投影方式獲得Salary 的總值:
var totalSalary = employees.Sum(e => e.Salary);
雖然投影形式表面上似乎被限制在了上述的類型裡(int,long,single,double,decimal),但是如果我們使用lambda運算式或匿名表達,投影的形式將允許較短的類型:
employees.Sum(e => e.Dependents);
employees.Sum(delegate(Employee e) { return e.Dependents; });
這是因為lambda運算式和匿名委託的結果可以自動擴大小數實值型別(如 short)到 int。
Average() - 返回序列的平均值
Average()方法,就像SUM()一樣,只不過它是用總和除以實際涉及到的項目數。涉及到的是什麼意思?請記住,SUM( )不包括空值 。Average()是將所有非null值求平均。例如:
var intList = new int?[] { 10, 20, 30, null };
// 返回 20
Console.WriteLine(intList.Average());
MIN() - 返回最小的序列值
MIN()擴充方法用於研究序列,並返回從它的最小值 :
- Min()
- 尋找序列中最小的值。
- 拋出異常,如果沒有實現IComparable或IComparable<T>。
- 拋出異常,如果序列是空的,源類型是實值型別。
- 如果序列是空的,X是參考型別或者Nullable的實值型別,則返回 null 。
- Min(Func<TSource, X> projection)
- 返回泛型序列中的最小值
- 如果 TSource 類型實現 IComparable<T>,則此方法使用該實現比較值。 否則,如果 TSource 類型實現 IComparable,則使用該實現比較值。
- 拋出異常,如果序列是空的,X是實值型別。
- 如果 TSource 為參考型別,且源序列為空白或僅包含為 null 的值,則此函數返回 null。
MIN()支援幾乎任何類型,只要該類型實現IComparable或IComparable <T>。因此,它是不限制的數實值型別,可以用於任何比較的對象(包括像實值型別的DateTime,TimeSpan):
var shortList = new short[] { 1, 3, 7, 9, -9, 33 };
// 返回 -9
var smallest = shortList.Min();
// 根據家庭成員數量找到最小值
var minDependents = employees.Min(e => e.Dependents);
此外,MIN()不使用泛型約束限制那些支援IComparable 介面的型別參數。相反,它拋出一個運行異常來回應如果序列非空,沒有在它的對象實現IComparable的介面。
因此如果使用我們的之前定義的Employee類,下面的第一次調用將返回Null(序列為空白),第二次調用會拋出(非空,但不包含IComparable的對象序列) 。
var result1 = Enumerable.Empty<Employee>().Min();
var result2 = employees.Min();
最後,請注意,對於實值型別,如果序列是空,將拋出異常, 所以下面兩個都會拋出異常:
var result3 = Enumerable.Empty<int>().Min();
var result4 = Enumerable.Empty<Employee>().Min(e => e.Dependents);
MAX() - 返回最大的序列值
MAX()MIN()的行為完全一樣,只不過它返回最大值,而不是最小值。因此,我們可以使用這些序列中的最大值,或從一個序列的預測最大值:
///返回33
VAR biggestShort = shortList.Max();
//返回75250.0
VAR highestSalary = employees.Max(E => e.Salary);
其他方面,請參考Min()。
Aggregate() - 序列的自訂累加器
有三種形式的Aggregate():
- Aggregate(Func<TSource, TSource, TSource> function)
- 適用於一個函數,它接受一個累加器值和下一個值,並返回結果。
- 值和序列類型是相同的。
- 種子值是序列中的第一個值。
- Aggregate(TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> function)
- 應用序列中的一個函數,累加器值和下一個項目,並返回一個結果。
- 值和序列類型可以不同或相同。
- 必須提供一個種子值來初始化,將指定的種子值用作累加器初始值。
- Aggregate(TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> function, Func<TAccumulate, TResult> resultProjection)
- 同上述,將指定的種子值用作累加器的初始值,並使用指定的函數選擇結果值。
這可能看起來相當複雜。只要記住 “此方法的工作原理是對 source 中的每個元素調用一次 func。 每次調用 func 時,都將傳遞序列中的元素和彙總值(作為 func 的第一個參數)。 並用 func 的結果替換以前的彙總值。”
例如,如果我們想要做一個序列中的所有數位乘法:
var numbers = new int[] { 1, 3, 9, 2 };
// 使用當前總值乘以下一個數值得到新的總值
var product = numbers.Aggregate((total, next) => total * next);
最後的值是: 1 X 3 X 9 X 2 = 54。
下面看看怎麼用更複雜的彙總計算, 可能我們想得到這樣一個結果 -- 用每個僱員的工資除以家庭總人口數(包括他自己),再將這些數相加:
var weirdCalculation = employees.Aggregate(0.0,
(result, next) => result + next.Salary / (next.Dependents + 1));
參照上面的Empolyee 定義,得到的結果是 110458.8333, 為方便理解請看下面的Excel 表格:
所以你看,我們可以做相當複雜的彙總計算,關鍵是要記住,你所提供的函數留給下一個“元素”,並把它應用到正在啟動並執行“總值”。
摘要
四個簡單的和一個可能有點複雜的,這一組功能相當強大!這些方法可以很容易地對序列進行彙總,使你不需要進行迴圈和自己計算。他們很運行快也很容易使用,他們很容易閱讀,他們也被全面測試過了。敬請享受!
更多精彩的相關文章,請閱讀喜樂的ASP.NET(Alex Song)
本文參考翻譯自C#/.NET Little Wonders: Five Easy Sequence Aggregators