owned this note
owned this note
Published
Linked with GitHub
---
tags: LinQ , C# , Aggregate Operators
---
# Count、Sum、Average、Min、Max
Count、Sum、Average、Min、Max 是 LinQ 內用來進行統計運算(?)的函數. 其與 First 相同 , 都是立即執行(Immediately execution)查詢. 因此不用擔心延遲執行的問題. 另外需要特別注意的是上述函數的回傳值只可能是 **Value Type 以及 Nullable Type .** 也就是說 , 像是回傳學生集合中成績最小的學生物件 , 這個動作是無法達成的.
### [Min](https://docs.microsoft.com/zh-tw/dotnet/api/system.linq.enumerable.min?view=netframework-4.8)
回傳序列中指定項目的最小值.
常用的多載形式如下(不只)
1. TSource Min<TSource>(this IEnumerable<TSource>)
- Returns the minimum value in a generic sequence.
2. TResult Min<TSource,TResult>(this IEnumerable<TSource>, Func<TSource,TResult>)
- Invokes a transform function on each element of a generic sequence and returns the minimum resulting value.
#### 使用時機
1. 當集合元素類型為 Value Type , 使用建構式 1 取出集合中的最小值
2. 當集合元素類型為 Reference Type , 使用建構式 2 取出集合中**指定屬性的最小值**.
#### Min 的用處
找出 list 中最小的數字 , 以及 people 中最小的年紀.
```C#
public static List<int> GetList() => new List<int> { 5, 6, 9, 1, 3 };
public static IEnumerable<(string name, int age)> GetPeople()
{
yield return (name: "小王", age: 15);
yield return (name: "老黃", age: 31);
yield return (name: "阿高", age: 74);
}
```
```C#
static void Main(string[] args)
{
var list = GetList();
var minNum = list.Min();
Console.WriteLine(minNum);
var people = GetPeople();
var minAge = people.Min(person => person.age);
Console.WriteLine(minAge);
Console.ReadKey();
}
```
##### 輸出結果
1. 1
2. 15
#### 簡單實作自己的 Min
不考慮集合中存在 null 的情況.
```C#
public static TSource MyMin<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw new Exception("null source");
var enumerator = source.GetEnumerator();
TSource value = default;
if (enumerator.MoveNext())
{
Comparer<TSource> comparer = Comparer<TSource>.Default;
bool hasValue = false;
do
{
if (hasValue == false || comparer.Compare(value, enumerator.Current) > 0)
{
value = enumerator.Current;
hasValue = true;
}
} while (enumerator.MoveNext());
}
return value;
}
public static TResult MyMin<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> func)
{
if (source == null) throw new Exception(" null source");
var enumerator = source.GetEnumerator();
var value = default(TResult);
if (enumerator.MoveNext())
{
var comparer = Comparer<TResult>.Default;
bool hasValue = false;
do
{
var temp = func(enumerator.Current);
if (hasValue == false || comparer.Compare(value, temp) > 0)
{
value = temp;
hasValue = true;
}
} while (enumerator.MoveNext());
}
return value;
}
```
##### 假設你希望取得集合中某個屬性值最小的物件 , 或許可以自己寫一個擴充方法. 如下.
```C#
public static TSource MyMin<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
{
if (source == null) throw new Exception("null source");
var enumerator = source.GetEnumerator();
if (enumerator.MoveNext())
{
var comparer = Comparer<TKey>.Default;
var minKey = selector(enumerator.Current);
var value = enumerator.Current;
do
{
var key = selector(enumerator.Current);
if (comparer.Compare(minKey, key) > 0)
{
minKey = key;
value = enumerator.Current;
}
} while (enumerator.MoveNext());
return value;
}
throw new Exception("no item");
}
```
---
### [Max](https://docs.microsoft.com/zh-tw/dotnet/api/system.linq.enumerable.max?view=netframework-4.8)
回傳序列中指定項目的最大值.
常用的多載形式如下(不只)
1. TSource Max<TSource>(IEnumerable<TSource>)
- Returns the maximum value in a generic sequence.
2. TResult Max<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>)
- invokes a transform function on each element of a generic sequence and returns the maximum resulting value.
#### 使用時機
1. 當集合元素類型為 Value Type , 使用建構式 1 取出集合中的最大值
2. 當集合元素類型為 Reference Type , 使用建構式 2 取出集合中指定屬性的最大值.
#### Max 的用處
找出 list 中最大的數字 , 以及 people 中最大的年紀.
```C#
public static List<int> GetList() => new List<int> { 5, 6, 9, 1, 3 };
public static IEnumerable<(string name, int age)> GetPeople()
{
yield return (name: "小王", age: 15);
yield return (name: "老黃", age: 31);
yield return (name: "阿高", age: 74);
}
```
```C#
static void Main(string[] args)
{
var list = GetList();
var maxNum = list.Max();
Console.WriteLine(minNum);
var people = GetPeople();
var maxAge = people.Max(person => person.age);
Console.WriteLine(minAge);
Console.ReadKey();
}
```
##### 輸出結果
1. 9
2. 74
#### 簡單實作自己的 Max
不考慮集合中存在 null 的情況.
```C#
public static TSource MyMax<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw new Exception("null source");
var enumerator = source.GetEnumerator();
TSource value = default;
if (enumerator.MoveNext())
{
Comparer<TSource> comparer = Comparer<TSource>.Default;
bool hasValue = false;
do
{
if (hasValue == false || comparer.Compare(value, enumerator.Current) < 0)
{
value = enumerator.Current;
hasValue = true;
}
} while (enumerator.MoveNext());
}
return value;
}
public static TResult MyMax<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> func)
{
if (source == null) throw new Exception(" null source");
var enumerator = source.GetEnumerator();
var value = default(TResult);
if (enumerator.MoveNext())
{
var comparer = Comparer<TResult>.Default;
bool hasValue = false;
do
{
var temp = func(enumerator.Current);
if (hasValue == false || comparer.Compare(value, temp) < 0)
{
value = temp;
hasValue = true;
}
} while (enumerator.MoveNext());
}
return value;
}
```
---
### [Sum](https://docs.microsoft.com/zh-tw/dotnet/api/system.linq.enumerable.sum?view=netframework-4.8)
回傳序列中指定項目的總和
我在MSDN文件找不到泛型 TResult 的 API 格式 , 不管 , 反正就記成只有下列兩個形式吧 XD
1. TSource Sum<TSource>(this IEnumerable<TSource>)
2. TResult Sum<TSource,TResult>(this IEnumerable<TSource>, Func<TSource,TResult>)
- 雖然是這麼寫 , 但實際上此實作是將 TResult 可能的類型都實作一次. 因為 TResult 是泛型呀~~ , 無法做 + 運算 , 除非自己重載運算子 Orz.
#### 使用時機
1. 當集合元素類型為 Value Type , 使用建構式 1 取出集合總和
2. 當集合元素類型為 Reference Type , 使用建構式 2 取出集合中指定屬性的總和.
#### Sum 的用處
找出 list 中所有數字的總和 , 以及 people 中所有年紀的總和.
```C#
public static List<int> GetList() => new List<int> { 5, 6, 9, 1, 3 };
public static IEnumerable<(string name, int age)> GetPeople()
{
yield return (name: "小王", age: 15);
yield return (name: "老黃", age: 31);
yield return (name: "阿高", age: 74);
}
```
```C#
static void Main(string[] args)
{
var list = GetList();
var minNum = list.Sum(num => num);
Console.WriteLine(minNum);
var people = GetPeople();
var minAge = people.Sum(item => item.age);
Console.WriteLine(minAge);
Console.ReadKey();
}
```
##### 輸出結果
1. 24
2. 120
#### 簡單實作自己的 Sum
實際上 LinQ 對於 Sum 的實作並沒有使用泛型 , 可能是考慮到效能吧?!
但我不想寫那麼多個 Orz.
```C#
// 實務上不建議用 dynamic 實作.
public static TSource MySum<TSource>(this IEnumerable<TSource> source)
{
dynamic sum = 0;
foreach (var item in source)
{
sum += item;
}
return sum;
}
public static TResult MySum<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
dynamic sum = 0;
foreach (var item in source)
{
sum += selector(item);
}
return sum;
}
// 實際上沒有這個形式
public static TResult MySum<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector, Func<TResult, TResult, TResult> add)
{
TResult sum = default;
foreach (var item in source)
{
sum = add(sum, selector(item));
}
return sum;
}
```
---
### [Average](https://docs.microsoft.com/zh-tw/dotnet/api/system.linq.enumerable.average?view=netframework-4.8)
回傳序列中指定項目的平均
我在MSDN文件也找不到泛型 TResult 的 API 格式 , 一樣不管 , 反正就記成只有下列兩個形式吧 XD
1. double Average<TSource>(this IEnumerable<TSource>)
2. double Average<TSource,TResult>(this IEnumerable<TSource>, Func<TSource,TResult>)
- 雖然是這麼寫 , 但實際上此實作是將 TResult 可能的類型都實作一次. 因為 TResult 是泛型呀~~ , 無法做 + 運算 , 除非自己重載 Orz.
#### 使用時機
1. 當集合元素類型為 Value Type , 使用建構式 1 取出集合平均
2. 當集合元素類型為 Reference Type , 使用建構式 2 取出集合中指定屬性的平均.
#### Average 的用處
找出 list 中數字的平均 , 以及 people 中年紀的平均.
```C#
public static List<int> GetList() => new List<int> { 5, 6, 9, 1, 3 };
public static IEnumerable<(string name, int age)> GetPeople()
{
yield return (name: "小王", age: 15);
yield return (name: "老黃", age: 31);
yield return (name: "阿高", age: 74);
}
```
```C#
static void Main(string[] args)
{
var list = GetList();
var minNum = list.Average(num => num);
Console.WriteLine(minNum);
var people = GetPeople();
var minAge = people.Average(item => item.age);
Console.WriteLine(minAge);
Console.ReadKey();
}
```
##### 輸出結果
1. 4.8
2. 40
#### 簡單實作自己的 Average
```C#
public static double MyAverage<TSource>(this IEnumerable<TSource> source)
{
dynamic sum = 0;
int count = 0;
foreach (var item in source)
{
sum += item;
count++;
}
return count > 0 ? 1.0 * sum / count : throw new Exception(" divide zero") ;
}
public static double MyAverage<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
dynamic sum = 0;
int count = 0;
foreach (var item in source)
{
sum += selector(item);
count++;
}
return count > 0 ? 1.0 * sum / count : throw new Exception(" divide zero") ;
}
```
---
### [Count](https://docs.microsoft.com/zh-tw/dotnet/api/system.linq.enumerable.count?view=netframework-4.8)
回傳序列中滿足指定條件的元素數量. **指定條件可以是無條件.**
1. int Count<TSource>(IEnumerable<TSource>)
- Returns the number of elements in a sequence.
2. int Count<TSource>(IEnumerable<TSource>, Func<TSource,Boolean>)
- Returns a number that represents how many elements in the specified sequence satisfy a condition.
#### 使用時機
1. **想知道集合中元素的數量.** 建議使用多載 1 . 另外若是集合的型態有 Count 屬性可以使用 ( 例如 List、Array ) , 則不建議使用 LinQ 的 Count 方法 , 原因是 Count 方法的結果是透過走訪所有集合成員得到.
2. **想要知道集合中滿足指定條件的元素的數量**. 可使用多載 2 .
#### Count 的用處
找出 list 中大於 5 的個數 , 以及 people 的成員個數.
```C#
public static List<int> GetList() => new List<int> { 5, 6, 9, 1, 3 };
public static IEnumerable<(string name, int age)> GetPeople()
{
yield return (name: "小王", age: 15);
yield return (name: "老黃", age: 31);
yield return (name: "阿高", age: 74);
}
```
```C#
static void Main(string[] args)
{
var list = GetList();
var minNum = list.Count(num => num > 5);
Console.WriteLine(minNum);
var people = GetPeople();
var minAge = people.Count();
Console.WriteLine(minAge);
Console.ReadKey();
}
```
##### 輸出結果
1. **2** , 比 5 大的數字有 6 以及 9 兩個.
2. **3** , people 有三個人.
#### 簡單實作自己的 Count
```C#
public static int MyCount<TSource>(this IEnumerable<TSource> source)
{
int count = 0;
foreach (var item in source)
{
checked
{
count++;
}
}
return count;
}
public static int MyCount<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
int count = 0;
foreach (var item in source)
{
checked
{
if (predicate(item))
{
count++;
}
}
}
return count;
}
```
---
### 總結
LinQ 可以用很簡單的語法解決原本需要好幾次迴圈才能解決的問題.
另外硬要講缺點的話 , 大概是會一直點點點下去 , 會寫得很長.
我自己也還在想有沒有比較好的換行邏輯.
最後用兩個例子作為總結.
#### 想知道不同部門關於薪水的各項統計數據
##### 程式碼
```C#
var data = new[]
{
new { DepartmentID = "屌炸天", Name = "Gamma", Salary = 42000 },
new { DepartmentID = "酷到炸", Name = "jeff", Salary = 30000 },
new { DepartmentID = "屌炸天", Name = "mary", Salary = 25000 },
new { DepartmentID = "旋風爆", Name = "eric", Salary = 5000 },
new { DepartmentID = "酷到炸", Name = "lisa", Salary = 65000 },
new { DepartmentID = "旋風爆", Name = "city", Salary = 95000 },
new { DepartmentID = "爆炸酷", Name = "linda", Salary = 12500 },
new { DepartmentID = "爆炸酷", Name = "qoooo", Salary = 35600 },
new { DepartmentID = "旋風爆", Name = "John", Salary = 53210 },
};
var query = data.GroupBy(item => item.DepartmentID).Select(group =>
new
{
DepartmentID = group.Key,
DepartCount = group.Count(),
TotalSalary = group.Sum(groupItem => groupItem.Salary),
AverageSalary = group.Average(groupItem => groupItem.Salary),
MaxSalary = group.Max(groupItem => groupItem.Salary),
MinSalary = group.Min(groupItem => groupItem.Salary),
EmployeeNames = group.Select(groupItem => groupItem.Name)
}
);
foreach (var item in query)
{
Console.WriteLine($"部門編號 : {item.DepartmentID} , 部門人數 : {item.DepartCount}");
Console.WriteLine($"薪水和 : {item.TotalSalary} , 平均薪水 : {item.AverageSalary} , 最高薪水 : {item.MaxSalary} , 最低薪水 : {item.MinSalary}");
item.EmployeeNames.ToList().ForEach(name => Console.WriteLine($"員工姓名 : {name} "));
Console.WriteLine();
}
```
##### 輸出結果
![88iMY5a.png](https://github.com/s0920832252/LinQ-Note/blob/master/Resources/88iMY5a.png?raw=true)
---
#### 薪水的次數分配表
```C#
var data = new[]
{
new { DepartmentID = "屌炸天", Name = "Gamma", Salary = 42000 },
new { DepartmentID = "酷到炸", Name = "jeff", Salary = 30000 },
new { DepartmentID = "屌炸天", Name = "mary", Salary = 25000 },
new { DepartmentID = "旋風爆", Name = "eric", Salary = 5000 },
new { DepartmentID = "酷到炸", Name = "lisa", Salary = 65000 },
new { DepartmentID = "旋風爆", Name = "city", Salary = 95000 },
new { DepartmentID = "爆炸酷", Name = "linda", Salary = 12500 },
new { DepartmentID = "爆炸酷", Name = "qoooo", Salary = 35600 },
new { DepartmentID = "旋風爆", Name = "John", Salary = 53210 },
};
var intervals = new double[] { 80000, 50000, 30000 };
var query = data.GroupBy(employee => intervals.FirstOrDefault(interval => employee.Salary >= interval))
.Select( groupedDate => new
{
groupedDate.Key,
Count = groupedDate.Count(),
}
).OrderBy(item => item.Key);
foreach (var item in query)
{
var label = item.Key != 0 ? $"薪水高於{item.Key.ToString()}的人數" : $"薪水低於{intervals[intervals.Length - 1]}的人數";
Console.WriteLine($"{label} : { item.Count}");
}
```
##### 輸出結果
![vxsFJYd.png](https://github.com/s0920832252/LinQ-Note/blob/master/Resources/vxsFJYd.png?raw=true)
---
### Thank you!
You can find me on
- [GitHub](https://github.com/s0920832252)
- [Facebook](https://www.facebook.com/fourtune.chen)
若有謬誤 , 煩請告知 , 新手發帖請多包涵
# :100: :muscle: :tada: :sheep: