tags:

views:

1583

answers:

5

Hi all,

I believe this is another easy one for you LINQ masters out there. Is there any way I can separe a List into several separate lists of SomeObject, using the item index as the delimiter of each split?

Let me exemplify: I have a List<SomeObject> and I need a List<List<SomeObject>> or List<SomeObject>[], so that each of these resulting lists will contain a group of 3 items of the original list (sequentially).

eg.: Original List: [a, g, e, w, p, s, q, f, x, y, i, m, c]

Resulting lists: [a, g, e], [w, p, s], [q, f, x], [y, i, m], [c]

I'd also need the resulting lists size to be a parameter of this function.

Is it possible??

Thanks!

+1  A: 

If the list is of type system.collections.generic you can use the "CopyTo" method available to copy elements of your array to other sub arrays. You specify the start element and number of elements to copy.

You could also make 3 clones of your original list and use the "RemoveRange" on each list to shrink the list to the size you want.

Or just create a helper method to do it for you.

Jobo
+1  A: 

You could use a number of queries that use Take and Skip, but that would add too many iterations on the original list, I believe.

Rather, I think you should create an iterator of your own, like so:

public static IEnumerable<IEnumerable<T>> GetEnumerableOfEnumerables<T>(
  IEnumerable<T> enumerable, int groupSize)
{
   // The list to return.
   List<T> list = new List<T>(groupSize);

   // Cycle through all of the items.
   foreach (T item in enumerable)
   {
     // Add the item.
     list.Add(item);

     // If the list has the number of elements, return that.
     if (list.Length == groupSize)
     {
       // Return the list.
       yield return list;

       // Set the list to a new list.
       list = new List<T>(groupSize);
     }
   }

   // Return the remainder if there is any,
   if (list.Length != 0)
   {
     // Return the list.
     yield return list;
   }
}

You can then call this and it is LINQ enabled so you can perform other operations on the resulting sequences.

casperOne
I think this is the best solution... the only problem is that list doesn't have Length... it has Count. But that's easy to change.We can make this better by not even constructing Lists but returning ienumerables that contain references to the main list with a offset/length combination. So then, if the groupsize is big, we don't waste memory. Comment if you want me to write it up.
Amir
+7  A: 

Try the following code.

public static List<List<object>> Split(List<object> source)
{
    return  source
        .Select((x, i) => new { Index = i, Value = x })
        .GroupBy(x => x.Index / 3)
        .Select(x => x.Select(v => v.Value).ToList())
        .ToList();
}

The idea is to first group the elements by indexes. Dividing by three has the effect of grouping them into groups of 3. Then convert each group to a list and the IEnumerable of List to a List of List's

JaredPar
Ohh, I like this MUCH better.
casperOne
GroupBy does an implicit sort. That can kill performance. What we need is some kind of inverse of SelectMany.
Justice
Great solution. Worked smoothly. Thanks!!
Felipe Lima
@Justice, it would be nice to have a built-in partitioning system for IEnumerable.
JaredPar
@JaredPar You can do that easily enough with an extension method. I suspect it's not in there, in part, because it doesn't integrate well with SQL. 'myEnumerable.InGroupsOf(3).Select(subEnumerable => subEnumerable.Sum()).Average()' plus overloads would be nice.
Justice
@Justice, GroupBy might be implemented by hashing. How do you know GroupBy's implementation "can kill performance"?
David B
+2  A: 

Here's a list splitting routine I wrote a couple months ago:

public static List<List<T>> Chunk<T>(
    List<T> theList,
    int chunkSize
)
{
    List<List<T>> result = theList
        .Select((x, i) => new {
            data = x,
            indexgroup = i / chunkSize
        })
        .GroupBy(x => x.indexgroup, x => x.data)
        .Select(g => new List<T>(g))
        .ToList();

    return result;
}
David B
+1  A: 

We found David B's solution worked the best. But we adapted it to a more general solution:

list.GroupBy(item => item.SomeProperty) 
   .Select(group => new List<T>(group)) 
   .ToArray();
jacko