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[] { 10, 20, 30, 40, 50 };
int sum1 = nums.Where((n, i) => i % 2 == 0).Sum();//10 + 30 + 50
}
这个Where扩展设计的很好,它不但能带出某项的值“n”,还能带出项的位置“i”。
writeline函数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[] { 10, 20, 30, 40, 50 };
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;
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论