views:

66

answers:

2

I am trying to improve some code and I can't think of a better way to do what it currently does. essentially i loop outside for the number of items divided by the number I want to select, then inside that loop I select the item based on an inner loop of the number of items I need to create a single item modifying that by the outerloop value, so in the example below the outer loop loops twice (0,1) and the inner loop twice per outer loop (0,1 then 2,3).

Can anyone see a more readable way to do this or a way to do it without doing the two loops?

Hope that makes sense. Thanks.

Consider XML of

string xml = @"
<MainItem>
 <Select>2</Select>
 <ItemArray>
   <Item>One</Item>
   <Item>Two</Item>
   <Item>Three</Item>
   <Item>Four</Item>
 </ItemArray>
</MainItem>";

var doc = XDocument.Parse(xml);

Here the Select value tells me how many items create one "object" so in this case 2 items are an object (this can be any number but the number of items will always allow for the correct number of objects) currently i am doing somethng like

    List<List<XElement>> items = new List<List<XElement>>();
    for(int i = 0;i < (doc.Descendants("Item").Count() / (int)doc.Element("MainItem").Element("Select"));i++)
    {
      //this is one object
     var singleItem = new List<XElement>();
      for (int j = 0; j <  (int)doc.Element("MainItem").Element("Select"); j++)
     {
         var item =  doc.Descendants("Item").ElementAt(j + (i * (int)doc.Element("MainItem").Element("Select")));
         singleItem.Add(item);
     }
     items.Add(singleItem);
    }

So we end with a list of List with 2 items, each item holds 2 XElements each (One and Two together and Three and Four together) so..

ItemOne
 One
 Two
ItemTwo
 Three
 Four
+2  A: 
public static IEnumerable<List<T>> Batch<T>(
  this IEnumerable<T> source,
  int batchAmount) 
{ 
  List<T> result = new List<T>(); 
  foreach(T t in source) 
  { 
    result.Add(t); 
    if (result.Count == batchSize) 
    { 
      yield return result; 
      result = new List<T>(); 
    } 
  } 
  if (result.Any()) 
  { 
    yield return result; 
  } 
} 

Called By

int batchAmount = (int)doc.Element("MainItem").Element("Select"));

List<List<XElement>> items = doc.Descendants("Item")
  .Batch(batchAmount)
  .ToList();
David B
wish I could accept 2 answers +1 on this it works and achieves what I needed, only difference between yours and the accepted answer is the need for an extension method
Pharabus
Winston Smith's answer is cool (I've written it before), but slow when used over 10,000 elements.
David B
+2  A: 

There's a simple way to do it with a single LINQ statement. You can group by item index / batchSize, as follows:

int batchSize = (int)doc.Element("MainItem").Element("Select");
var items = doc
            .Descendants("Item")
            .Select( (e, i) => new {i,e} )
            .GroupBy( g => g.i / batchSize )
            .Select( g => g.Select( gv => gv.e) )
            ;

This gives an IEnumerable<IEnumerable<XElement>>, in the structure you describe.

Winston Smith
perfect, thanks
Pharabus