LINQ to combine all n elements in a series using a function and then re-aggregate these results into a new list?

Not sure if the question is clearly entitled, but here it is. I have some pretty dirty code in my ASP.NET MVC 3 application to wrap every 2 (but make it custom from 1-n) elements in a list in a div tag when they are printed. I would like to create a LINQ function as follows:

// Can't think of a better name IEnumerable<V> FormattedSubsetList<T, V>(IEnumerable<T> items, int every = 2, [delegate to a method to join the N elements]) { } 

Sorry if this is quite complicated ... I'll try an example. Say I have a Widget list. I want to print the names of all my widgets (let's say I have 7), and I want every 2 widgets to be inside a div. Therefore, if I have ...

 var list = new List<Widget>(); list.Add(new Widget() { Name = "One" }); //... you get the picture list.Add(new Widget() { Name = "Seven" }); IEnumerable<string> newList = FormattedSubsetList<Widget, string>(list, 2, (one, two) => (return "<div>" + string.Join(" ", one.Name, two.Name) + "</div>"); string finalString = string.Join(string.Empty, newList); // finalString == <div>One Two</div><div>Three Four</div><div>Five Six</div><div>Seven</div> 

I apologize if something is unclear, but I just don’t know what it is and I don’t know how to do it. I know that my LINQ syntax is also a bit in some places.

+4
source share
3 answers

Here are two extension methods that will do exactly what you need. Split method is reusable, it simply splits any IEnumerable into a list of enumerable objects, so each subenumerable has at most size elements. The second ToFormattedList method is a client method that does what you requested.

 public static class Extenstions { public static IEnumerable<TRestul> ToFormattedList<TElement, TRestul>( this IEnumerable<TElement> source, int count, Func<List<TElement>, TRestul> formatter) { return source.Split(count).Select(arg => formatter(arg.ToList())); } public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int size) { var i = 0; return from element in source group element by i++ / size into splitGroups select splitGroups.AsEnumerable(); } } 

How to use:

 var list = new List<Widget> { new Widget { Name = "One" }, new Widget { Name = "Two" }, new Widget { Name = "Three" }, new Widget { Name = "Four" }, new Widget { Name = "Five" }, new Widget { Name = "Six" }, new Widget { Name = "Seven" }, new Widget { Name = "Eight" } }; var newList = list.ToFormattedList(2, args => "<div>" + args[0].Name + args[1].Name + "</div>"); var finalString = string.Join(string.Empty, newList); // finalString = <div>OneTwo</div><div>ThreeFour</div><div>FiveSix</div><div>SevenEight</div> 

However, a problem will arise if there is an odd number of elements in the list, because args [1] will throw an exception for the last element. So you can do:

 var newList = list.ToFormattedList(2, args => "<div>" + string.Join(" ", args.Select(arg => arg.Name)) + "/<div>"); 
+4
source

You can achieve grouping in a simple Select call. Here's a small console application that does what you want:

 var list = new List<String>(); for (int i = 0; i < 7; i++) list.Add("Widget #" + (i + 1)); var groupedWidgets = list .Select((w, i) => new { Widget = w, Index = i }) .GroupBy(x => (int)(x.Index / 2)); foreach (var g in groupedWidgets) { Console.WriteLine("<div>"); foreach (var w in g) { Console.WriteLine(" " + w.Widget); } Console.WriteLine("</div>"); } 

So, I am grouping "Index / 2" as an int, which means that elements 1 and 2 will be merged together, and then items 3 and 4, etc. Changing β€œ2” to β€œ3” groups widgets in triples.

It makes sense?

Refresh . Here is a generic function that returns a list of grouped items:

 public static class Extensions { public static IEnumerable<IEnumerable<T>> GroupSelect<T>(this IEnumerable<T> list, int groupSize) { return list .Select((t, i) => new { t, i }) .GroupBy(x => (int)(xi / groupSize), x => xt); } } 

So now you can simply write:

 foreach (var g in list.GroupSelect(3)) { Console.WriteLine("<div>"); foreach (var w in g) { Console.WriteLine(" " + w); } Console.WriteLine("</div>"); } 
+2
source

This uses an extension method that should suit your needs.

  public static IEnumerable<string> FormatSubsetList<T>(this IEnumerable<T> input, int every, Func<IEnumerable<T>,string> formatter) { List<T[]> list = new List<T[]> (); int index = 0; foreach (T i in input) { T[] array; if (index % every == 0) list.Add (array = new T[every]); else array = list[list.Count - 1]; array[index++ % every] = i; } return list.Select(t => t.Where (i => i != null)).Select(formatter); } static Program() { List<Widget> widgets = new List<Widget> (); Func<IEnumerable<Widget>,string> formatter = items => items.Aggregate (new StringBuilder ("<div>"), (sb,w) => sb.Append(" ").Append (w.Name), sb => sb.Append ("</div>").ToString ()); widgets.FormattedSubsetList(3, formatter); } 
0
source

All Articles