C#累加器函數Aggregate用法 講解


Enumerable.Aggregate 擴展方法在System.Linq命名空間中,是Enumerable類的第一個方法(按字母順序排名),但確是Enumerable里面相對復雜的方法。

MSDN對它的說明是:對序列應用累加器函數。備注中還有一些說明,大意是這個方法比較復雜,一般情況下用Sum、Max、Min、Average就可以了。
看看下面的代碼,有了Sum,誰還會用Aggregate呢!

也很簡單吧,就是一個循環!前面lambda表達式中參數a, n 分別對應current, enumerator.Current,對照一下,還是很好理解的。

現在我們想求整數數組中位置為偶數的數的和(間隔求和),可以用Where配合Sum:

public static void Test5()
        {
            
int[] nums = new int[] { 1020304050 };
            
int sum1 = nums.Where((n, i) => i % 2 == 0).Sum();//10 + 30 + 50
        }

這個Where擴展設計的很好,它不但能帶出某項的值“n”,還能帶出項的位置“i”。
Aggregate可不行!我們來改進一下:

//改進的Aggerate擴展(示例代碼,實際使用請添加空值檢查)
public static TSource Aggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, int, TSource> func)
{
  int index = 0;
  using (IEnumerator<TSource> enumerator = source.GetEnumerator())
  {
    enumerator.MoveNext();
    index++;
    TSource current = enumerator.Current;
    while (enumerator.MoveNext())
    current = func(current, enumerator.Current, index++);
    return current;
  }
}

改進后的Aggregate更加強大,前面的求偶數位置數和的算法可以寫成下面的樣子:

public static void Test6()
        {
            
int[] nums = new int[] { 1020304050 };
            
int sum2 = nums.Aggregate((a, c, i) => a + i%2 == 0 ? c : 0 );//10 + 30 + 50
        }

可能不夠簡潔,但它一個函數代替了Where和Sum。所在位置“i“的引入給Aggregate帶來了很多新的活力,也增加了它的應用范圍!

使用“初中知識”實現查找重復最優算法 + 最終極限算法》中最后提出的“最終極限算法”,用上這里改進的Aggregate擴展,也可以甩開Select和Sum,更加精簡一步了: 

         public   static   void  Test7()
        {
            
// 1~n放在含有n+1個元素的數組中,只有唯一的一個元素值重復,最簡算法找出重復的數
             int [] array  =   new   int [] {  1 3 2 3 4 5  };
            
// 原極限算法
             int  repeatedNum1  =  array.Select((i, j)  =>  i  -  j).Sum();
            
//最新極限算法
            int repeatedNum2 = array.Aggregate((a, n, i) => a + n -  i);
        }

 

         public   static   void  Test1()
        {
            
int [] nums  =   new   int [] {  1 2 3 4 5 6 7 8 9 10 };

            
int  sum1  =  nums.Sum();
            
int  sum2  =  nums.Aggregate((i,j) => i + j);
        }

同是求和,Sum不再需要額外參數,而Aggregate確還要將一個lambda作為參數。因為用起來麻煩,操作太低級,Aggregate漸漸被大多人忽視了...
實際上Aggregate因為“低級”,功能確是很強大的,通過它可以簡化很多聚合運算。

首先來看對Aggregate組裝字符串的問題:
 

         public   static   void  Test2()
        {
            
string [] words  =   new   string [] {  " Able " " was " " I " " ere " " I " " saw " " Elba " };
            
string  s  =  words.Aggregate((a, n)  =>  a  +   "   "   +  n);
            Console.WriteLine(s);
        }

輸出結果是:Able was I ere I saw Elba (注:出自《大國崛起》,狄娜最后講述了拿破侖一句經典)。
當然考慮性能的話還是用StringBuilder吧,這里主要介紹用法。這個Sum做不到吧!

Aggregate還可以將所有字符串倒序累加,配合String.Reverse擴展可以實現整個句子的倒序輸出:

         public   static   void  Test3()
        
{
            
string[] words = new string[] "Able""was""I""ere""I""saw""Elba"};
            
string normal = words.Aggregate((a, n) => a + " " + n);
            
string reverse = words.Aggregate((a, n) => n.Reverse() + " " + a);

            Console.WriteLine(
"正常:" + normal);
            Console.WriteLine(
"倒置:" + reverse);
        }

        
//  倒置字符串,輸入"abcd123",返回"321dcba"
         public   static   string  Reverse( this   string  value)
        
{
            
char[] input = value.ToCharArray();
            
char[] output = new char[value.Length];
            
for (int i = 0; i < input.Length; i++)
                output[input.Length 
- 1 - i] = input[i];
            
return new string(output);
        }

看下面,輸出結果好像不太對:

怎么中間的都一樣,兩的單詞首尾字母大小寫發生轉換了呢?!
仔細看看吧,不是算法有問題,是輸入“有問題”。搜索一下“Able was I ere I saw Elba”,這可是很有名的英文句子噢!

Aggregate還可以實現異或(^)操作

         public   static   void  Test4()
        {
            
byte [] data  =   new   byte [] {  0x31 0x32 0x33 0x34 0x35  };
            
byte  checkSum  =  data.Aggregate((a, n)  =>  ( byte )(a  ^  n));
        }

對經常作串口通信的朋友比較實用。

看來Aggregate也是比較“簡單易用”的,深入一步來看看它是怎么實現的吧,使用Reflector,反編譯一下System.Core.dll。
以下代碼取自反編譯結果,為了演示刪除了其中的空值判斷代碼:

 public static TSource Aggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func)
        {
            
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
            {
                enumerator.MoveNext();
                TSource current 
= enumerator.Current;
                
while (enumerator.MoveNext())
                    current 
= func(current, enumerator.Current);
                
return current;
            }
        }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM