c# - query - linq syntax




LINQ聚合算法解釋 (8)

這可能聽起來很蹩腳,但我一直無法找到對Aggregate很好解釋。

良好意味著簡短,描述性,全面,小而明確的例子。


一張圖片勝過千言萬語

提醒: Func<A, B, C>是一個帶有兩個AB輸入的函數,返回一個C

Enumerable.Aggregate有三個重載:


過載1:

A Aggregate<A>(IEnumerable<A> a, Func<A, A, A> f)

例:

new[]{1,2,3,4}.Aggregate((x, y) => x + y);  // 10


這種過載很簡單,但它有以下限制:

  • 該序列必須包含至少一個元素,
    否則該函數將拋出InvalidOperationException
  • 元素和結果必須是相同的類型。


過載2:

B Aggregate<A, B>(IEnumerable<A> a, B bIn, Func<B, A, B> f)

例:

var hayStack = new[] {"straw", "needle", "straw", "straw", "needle"};
var nNeedles = hayStack.Aggregate(0, (n, e) => e == "needle" ? n+1 : n);  // 2


這種過載更為普遍:

  • 必須提供種子值( bIn )。
  • 集合可以是空的,
    在這種情況下,該函數將產生結果的種子值。
  • 元素和結果可以有不同的類型。


過載3:

C Aggregate<A,B,C>(IEnumerable<A> a, B bIn, Func<B,A,B> f, Func<B,C> f2)


IMO的第三次過載並不是很有用。
通過使用重載2,然後轉換其結果的函數,可以更簡潔地編寫相同的代碼。


插圖來自這個優秀的博客帖子


Aggregate最容易理解的定義是它考慮到之前的操作,對列表中的每個元素執行操作。 也就是說,它對第一個和第二個元素執行操作並將結果向前傳遞。 然後它對前一個結果和第三個元素進行操作並繼續。 等等

示例1.總結數字

var nums = new[]{1,2,3,4};
var sum = nums.Aggregate( (a,b) => a + b);
Console.WriteLine(sum); // output: 10 (1+2+3+4)

這增加了12使3 。 然後將3 (前一個結果)和3 (序列中的下一個元素)加上6 。 然後加6410

示例2.從一個字符串數組中創建一個csv

var chars = new []{"a","b","c", "d"};
var csv = chars.Aggregate( (a,b) => a + ',' + b);
Console.WriteLine(csv); // Output a,b,c,d

這種工作方式非常相似。 連接a逗號和b來創建a,b 。 然後將a,b與逗號和c ,形成a,b,c 。 等等。

示例3.使用種子來乘數

為了完整overloadAggregateoverload需要一個種子值。

var multipliers = new []{10,20,30,40};
var multiplied = multipliers.Aggregate(5, (a,b) => a * b);
Console.WriteLine(multiplied); //Output 1200000 ((((5*10)*20)*30)*40)

就像上面的例子一樣,它的值從5開始,然後乘以序列10的第一個元素,結果為50 。 該結果繼續前進,並乘以序列20的下一個數字,得出1000的結果。 這繼續通過序列的剩餘2個元素。

現場示例: http://rextester.com/ZXZ64749 : http://rextester.com/ZXZ64749
文檔: http://msdn.microsoft.com/en-us/library/bb548651.aspxhttp://msdn.microsoft.com/en-us/library/bb548651.aspx

附錄

上面的示例2使用字符串連接來創建用逗號分隔的值列表。 這是一個簡單的方式來解釋這個答案的用意是什麼。 但是,如果使用這種技術來實際創建大量逗號分隔的數據,那麼使用StringBuilder會更合適,並且這與使用種子重載啟動StringBuilder Aggregate完全兼容。

var chars = new []{"a","b","c", "d"};
var csv = chars.Aggregate(new StringBuilder(), (a,b) => {
    if(a.Length>0)
        a.Append(",");
    a.Append(b);
    return a;
});
Console.WriteLine(csv);

更新示例: http://rextester.com/YZCVXV6464 : http://rextester.com/YZCVXV6464


一個簡短而重要的定義可能是這樣的:Linq Aggregate extension方法允許聲明一種應用於列表元素的遞歸函數,其操作數是兩個:元素按列表中的順序排列,一次一個元素,以及先前的遞歸迭代的結果,或者如果還沒有遞歸,則為空。

用這種方法你可以計算數字的階乘,或者連接字符串。


從Jamiec's回答中學到了很多東西。

如果唯一的需要是生成CSV字符串,您可以試試這個。

var csv3 = string.Join(",",chars);

這是一個100萬字符串的測試

0.28 seconds = Aggregate w/ String Builder 
0.30 seconds = String.Join 

源代碼在here


聚合基本上用於分組或匯總數據。

根據MSDN“聚合函數在序列上應用累加器函數”。

示例1:添加數組中的所有數字。

int[] numbers = new int[] { 1,2,3,4,5 };
int aggregatedValue = numbers.Aggregate((total, nextValue) => total + nextValue);

*重要:默認情況下,初始聚合值是集合序列中的1個元素。 即:默認情況下總變量初始值為1。

變量的解釋

總數:它將保存func返回的總和值(總值)。

nextValue:它是數組序列中的下一個值。 這個數值會被添加到總值中,即總數。

示例2:添加數組中的所有項目。 還要將初始累加器值設置為從10開始添加。

int[] numbers = new int[] { 1,2,3,4,5 };
int aggregatedValue = numbers.Aggregate(10, (total, nextValue) => total + nextValue);

參數說明:

第一個參數是初始值(起始值,即種子值),它將用於開始添加數組中的下一個值。

第二個參數是一個func,它是一個需要2個int的func。

總計:這與計算後func返回的總計值(合計值)之前保持相同。

2.nextValue ::它是數組序列中的下一個值。 這個數值會被添加到總值中,即總數。

另外調試這段代碼會讓你更好地理解聚合工作的方式。


聚合用於對多維整數數組中的列進行求和

        int[][] nonMagicSquare =
        {
            new int[] {  3,  1,  7,  8 },
            new int[] {  2,  4, 16,  5 },
            new int[] { 11,  6, 12, 15 },
            new int[] {  9, 13, 10, 14 }
        };

        IEnumerable<int> rowSums = nonMagicSquare
            .Select(row => row.Sum());
        IEnumerable<int> colSums = nonMagicSquare
            .Aggregate(
                (priorSums, currentRow) =>
                    priorSums.Select((priorSum, index) => priorSum + currentRow[index]).ToArray()
                );

使用索引選擇用於聚合函數內,對匹配列進行求和並返回一個新數組; {3 + 2 = 5,1 + 4 = 5,7 + 16 = 23,8 + 5 = 13}。

        Console.WriteLine("rowSums: " + string.Join(", ", rowSums)); // rowSums: 19, 27, 44, 46
        Console.WriteLine("colSums: " + string.Join(", ", colSums)); // colSums: 25, 24, 45, 42

但是,由於累積類型(int)與源類型(bool)不同,因此計算布爾數組中trues的數量會更困難; 這裡種子是必要的,以便使用第二次過載。

        bool[][] booleanTable =
        {
            new bool[] { true, true, true, false },
            new bool[] { false, false, false, true },
            new bool[] { true, false, false, true },
            new bool[] { true, true, false, false }
        };

        IEnumerable<int> rowCounts = booleanTable
            .Select(row => row.Select(value => value ? 1 : 0).Sum());
        IEnumerable<int> seed = new int[booleanTable.First().Length];
        IEnumerable<int> colCounts = booleanTable
            .Aggregate(seed,
                (priorSums, currentRow) =>
                    priorSums.Select((priorSum, index) => priorSum + (currentRow[index] ? 1 : 0)).ToArray()
                );

        Console.WriteLine("rowCounts: " + string.Join(", ", rowCounts)); // rowCounts: 3, 1, 2, 2
        Console.WriteLine("colCounts: " + string.Join(", ", colCounts)); // colCounts: 3, 2, 1, 2

這部分取決於您所談論的超載,但基本思想是:

  • 以種子作為“當前值”開始
  • 迭代序列。 對於序列中的每個值:
    • 應用用戶指定的函數將(currentValue, sequenceValue)轉換為(nextValue)
    • 設置currentValue = nextValue
  • 返回最終的currentValue

您可能會發現我的Edulinq系列中Aggregate post很有用 - 它包含更詳細的描述(包括各種重載)和實現。

一個簡單的例子是使用Aggregate作為Count的替代方法:

// 0 is the seed, and for each item, we effectively increment the current value.
// In this case we can ignore "item" itself.
int count = sequence.Aggregate(0, (current, item) => current + 1);

或者,也許總結字符串序列中所有長度的字符串:

int total = sequence.Aggregate(0, (current, item) => current + item.Length);

就我個人而言,我很少發現Aggregate有用 - “量身定制”的聚合方法通常對我來說足夠好。


除了這裡所有的重要答案之外,我還用它來通過一系列轉換步驟來演示項目。

如果轉換是作為Func<T,T> ,則可以向List<Func<T,T>>添加多個轉換List<Func<T,T>>並使用Aggregate遍歷每個步驟的T實例。

一個更具體的例子

你想獲取一個string值,並通過一系列可以編程方式構建的文本轉換。

var transformationPipeLine = new List<Func<string, string>>();
transformationPipeLine.Add((input) => input.Trim());
transformationPipeLine.Add((input) => input.Substring(1));
transformationPipeLine.Add((input) => input.Substring(0, input.Length - 1));
transformationPipeLine.Add((input) => input.ToUpper());

var text = "    cat   ";
var output = transformationPipeLine.Aggregate(text, (input, transform)=> transform(input));
Console.WriteLine(output);

這將創建一個轉換鏈:刪除前導和尾隨空格 - >刪除第一個字符 - >刪除最後一個字符 - >轉換為大寫。 可以根據需要添加,刪除或重新排序此鏈中的步驟,以創建需要的任何類型的轉換管道。

這個特定管道的最終結果是, " cat "變成了"A"

一旦你意識到T可以是任何東西 ,這可以變得非常強大。 這可以用於圖像轉換,比如使用BitMap作為例子的過濾器;





linq