Aggregate Operators - Count & Sum & Average & Min & Max

Aggregate Operators - Count & Sum & Average & Min & Max

Count、Sum、Average、Min、Max

Count、Sum、Average、Min、Max 是 LinQ 內用來進行統計運算(?)的函數. 其與 First 相同 , 都是立即執行(Immediately execution)查詢. 因此不用擔心延遲執行的問題. 另外需要特別注意的是上述函數的回傳值只可能是 Value Type 以及 Nullable Type . 也就是說 , 像是回傳學生集合中成績最小的學生物件 , 這個動作是無法達成的.

Count、Sum、Average、Min、Max

Count、Sum、Average、Min、Max 是 LinQ 內用來進行統計運算(?)的函數. 其與 First 相同 , 都是立即執行(Immediately execution)查詢. 因此不用擔心延遲執行的問題. 另外需要特別注意的是上述函數的回傳值只可能是 Value Type 以及 Nullable Type . 也就是說 , 像是回傳學生集合中成績最小的學生物件 , 這個動作是無法達成的.

Min

回傳序列中指定項目的最小值.

常用的多載形式如下(不只)

  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 中最小的年紀.

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);
}
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 的情況.

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;
}
假設你希望取得集合中某個屬性值最小的物件 , 或許可以自己寫一個擴充方法. 如下.
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

回傳序列中指定項目的最大值.

常用的多載形式如下(不只)

  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 中最大的年紀.

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);
}
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 的情況.

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

回傳序列中指定項目的總和

我在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 中所有年紀的總和.

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);
}
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.

// 實務上不建議用 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

回傳序列中指定項目的平均

我在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 中年紀的平均.

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);
}
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

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

回傳序列中滿足指定條件的元素數量. 指定條件可以是無條件.

  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 的成員個數.

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);
}
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

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 可以用很簡單的語法解決原本需要好幾次迴圈才能解決的問題. 另外硬要講缺點的話 , 大概是會一直點點點下去 , 會寫得很長. 我自己也還在想有沒有比較好的換行邏輯.

最後用兩個例子作為總結.

想知道不同部門關於薪水的各項統計數據

程式碼
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();
}
輸出結果

薪水的次數分配表

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}");
}
輸出結果

Thank you!

You can find me on

若有謬誤 , 煩請告知 , 新手發帖請多包涵
原始文章將記錄於此
https://github.com/s0920832252/LinQ-Note

:100:

 

:muscle:

 

:tada:

 

:sheep: