Malisa Ncube - .NET Delights

.NET Development ideas and things
posts - 41 , comments - 86 , trackbacks - 0

My Links

News



I LOVE DataObject.NET
http://xceed.com
http://www.sharpcrafters.com/
http://www.telerik.com

Get this blog as a slideshow!
Powered by feedmap.net

Twitter












Tag Cloud

Archives

Post Categories

Demystifying LINQ Aggregates

This post aims to dissect the power that exists in the Aggregate LINQ operator. I have realized that most programmers use it sparingly and have decided to

put a couple of examples to clarify how you can use the Aggregate operator to perform a few tricks.

The screenshot below relates to the small examples that follow.

image

 

The aggregate operator is interesting in that it enables accumulation of items to form some result. This comes in handy when we need to create new data items from others.

In my examples i start with a simple addition of two numbers to comma delimited examples. In the example below we can see that line number 6 has a lambda expression

which will be executed for each item encountered by the aggregate operator, resulting in a sum.

We can see that the signature is of the extension method is as follows

public static T Aggregate<T>(this IEnumerable<T> source, Func<T, T, T> func);

The first parameter and the second represent the input and the last T, the result. Therefore we can represent an expression like the one on line 6.

 
   1: var items = new List<int> { 4, 7, 8, 9, 0, 3, 4, 6, 7, 3, 1 };
   2:  
   3: // 1st example
   4: // This enables you to simply submit a Func delegate that will be acted upon by the lambda
   5: // expression assigned to it. 
   6: Func<int, int, int> add = (x, y) => x + y;
   7: Console.WriteLine("Computation using Func<> delegate inside aggregate: {0}", items.Aggregate(add));

 

The example that follows below, is for developers who had been used to using the delegate keyword, but gives exactly the same result as the example above. When we use

the delegate keyword we need to also include the parameter types. I would certainly choose the lambda over the delegate keyword because of its simplicity.

 

   1: // 2nd example
   2:  // This enables your to place another form of delagate and this time we are not using a lambda expression
   3:  // to evaluate the result.
   4:  var add2 = items.Aggregate(delegate(int x, int y) { return x + y; });
   5:  Console.WriteLine("Computation using delegate inside aggregate: {0}", add2);
 

In the following example we want to create a list of delimited items as shown in the screenshot above on example 3. We use the class which has a constructor which takes

the delimiter symbol string. The aggregate extension method signature is as follows.

 

public static TAccumulate Aggregate<TSource, TAccumulate>(
    this IEnumerable<TSource> source,
    TAccumulate seed, 
    Func<TAccumulate, TSource, TAccumulate> func);
 

The source parameter maps to the items collection and the Func<T, T, T> takes int as the first parameter type, DelimitedstringBuilder as the second and returns a

DelimitedStringBuilder. We can see from the return type within the anonymous method. The seed is the initial value that you want to assign to the aggregate.

 

   1: // 3rd example
   2:  // In this example we are using the Aggregate extension with a custom class which enables us to 
   3:  // concatenate numbers delimited by a symbol of choice.
   4:  var csv1 = items.Aggregate<int, DelimitedStringBuilder>(new DelimitedStringBuilder(";"), delegate(DelimitedStringBuilder accum, int elem)
   5:  {
   6:      return accum.Append(string.Format("{0}", elem));
   7:  }).ToString();
   8:  Console.WriteLine("Using a class to create delimited string: {0}", csv1);

 

The DelimitedStringBuilder class thanks to http://aspnetgarage.blogspot.com/2009/02/delimited-string-helper-class-in-c.html is the engine which

appends the custom delimiter to the StringBuilder resulting in one string. In your case you could have some other class which performs different operations

imaginable. Some examples may include conditional

statements to determine whether the element is prime, encyption, and many others.

 

   1: /// <summary>
   2: /// DelimitedStringBuilder enables concenating string delimited by custom string
   3: /// </summary>
   4: public class DelimitedStringBuilder
   5: {
   6:     protected string delimiter = null;
   7:     protected StringBuilder sb = new StringBuilder();
   8:  
   9:     // inner StringBuilder, as StringBuilder class cannot be inherited
  10:     /// <summary>
  11:     /// New instance of a DelimitedStringBuilder with delimiter between elements
  12:     /// </summary>
  13:     /// <param name="delimiter">delemiter to use (",", "\n", etc)</param>
  14:     public DelimitedStringBuilder(string delimiter)
  15:     {
  16:         this.delimiter = delimiter;
  17:     }
  18:     public DelimitedStringBuilder Append(string stringToAppend)
  19:     {
  20:         if (sb.Length > 0)
  21:             if (delimiter != null) sb.Append(delimiter);
  22:         sb.Append(stringToAppend);
  23:         return this;
  24:     }
  25:     public override string ToString()
  26:     {
  27:         return sb.ToString();
  28:     }
  29:  
  30:     public StringBuilder InnerStringBuilder
  31:     {
  32:         get
  33:         {
  34:             return this.sb;
  35:         }
  36:     }
  37: }

 

In our next example we still use the delimited string to construct a simpler result using lambda and the results are the same as example 3. I have

included type information because it will help us in the next few examples.

 

   1: // 4th example
   2: // Here we have re-written example 3 using a lambda expression to replace delegate code 
   3: // and this off-course results in much more readable code. 
   4: Func<DelimitedStringBuilder, int, DelimitedStringBuilder> delimiter = (x, y) => x.Append(y.ToString());
   5: var csv2 = items.Aggregate<int, DelimitedStringBuilder>(new DelimitedStringBuilder(";"), delimiter); //.ToString();
   6: Console.WriteLine("Using a class to create delimited string: {0} {1}", csv2, csv2.GetType());
 

The next example shows how we can use the following aggregate signature, which is the 3rd overload of the Aggregate extension method.

 

public static TAccumulate Aggregate<TSource, TAccumulate>(
    this IEnumerable<TSource> source, 
    TAccumulate seed, 
    Func<TAccumulate, TSource, TAccumulate> func
    Func<TAccumulate, TResult> resultSelector);
 
This enables us to specify pass the result through another lambda “resultSelector” which may perform other operations e.g. conversion or other
form of transformation. In my example i just changed the type from DelimitedStringBuilder to a formatted string.
 
 
   1: // 5th example
   2: // We have included a ResultSelector in this example so that we can transform the resulf into
   3: // any other desired type or representation.
   4: Func<DelimitedStringBuilder, string> resultSelector = x => string.Format("<{0}>", x);
   5: var csv3 = items.Aggregate<int, DelimitedStringBuilder, string>(new DelimitedStringBuilder(";"), delimiter, resultSelector);
   6: Console.WriteLine("Using a class to create delimited string: {0} {1}", csv3, csv3.GetType());

In the following example i tried to use an example which is not just simple integers but a class with a collection in it.  Seems straight forward.

 

   1: // 6th example
   2: // We are now using an example using a more practical class where you may want to use aggregate
   3: Student.GetAll().ForEach(student => Console.WriteLine("{0} | {1} | {2:f}",
   4:                                                       student.FullName,
   5:                                                       student.Marks.Aggregate<int, DelimitedStringBuilder>(new DelimitedStringBuilder(";"), delimiter),
   6:                                                       student.Marks.Average()

 


The difference with the next example is that we have a collection of subjects for each student, where each subject is a class made up of two  properties

(the SubjectType, and Mark).

 

   1: // 7th example
   2:  // We are now using an example using a more practical class where you may want to use aggregate
   3:  Func<DelimitedStringBuilder, Subject, DelimitedStringBuilder> delimiter2 =
   4:      (x, y) => x.Append(string.Format("{0} = {1}", y.SubjectType, y.Mark));
   5:  StudentEx.GetAll().ForEach(student => Console.WriteLine("{0} | {1} | {2:f}",
   6:                                                          student.FullName,
   7:                                                          student.Subjects.Aggregate<Subject, DelimitedStringBuilder>(new DelimitedStringBuilder(";"), delimiter2),
   8:                                                          student.Subjects.Average(subject => subject.Mark)

 

Download the source from here

Print | posted on Wednesday, December 9, 2009 10:51 PM |

Feedback

Gravatar

# re: Demystifying LINQ Aggregates

What is the purpose of 3rd and 4th examples when you have String.Join()?
12/11/2009 2:17 AM | centur
Gravatar

# re: Demystifying LINQ Aggregates

These examples are just to show Aggregate and to simply show that you can put together items delimited by a particular item. It may not be string type, but other types of classes where join is not implemented.

Cheers

12/11/2009 3:14 AM | Malisa Ncube
Gravatar

# re: Demystifying LINQ Aggregates

That 5th example is pretty interesting. I can see some potential uses there. There are often situations where the last or first element has special handling. Still hard to get my mind wrapped around this concept though.
12/11/2009 5:42 PM | John Sonmez
Post A Comment
Title:
Name:
Email:
Comment:
Verification:
 
 

Powered by: