tags:

views:

425

answers:

3

I have an IEnumerable<string> which I would like to split into groups of three so if my input had 6 items i would get a IEnumerable<IEnumerable<string>> returned with two items each of which would contain an IEnumerable<string> which my string contents in it.

I am looking for how to do this with Linq rather than a simple for loop

Thanks

+10  A: 
var result = sequence.Select((s, i) => new { Value = s, Index = i })
                     .GroupBy(item => item.Index / 3, item => item.Value);

Note that this will return an IEnumerable<IGrouping<int,string>> which will be functionally similar to what you want. However, if you strictly need to type it as IEnumerable<IEnumerable<string>> (to pass to a method that expects it in C# 3.0 which doesn't support generics variance,) you should use Enumerable.Cast:

var result = sequence.Select((s, i) => new { Value = s, Index = i })
                     .GroupBy(item => item.Index / 3, item => item.Value)
                     .Cast<IEnumerable<string>>();
Mehrdad Afshari
That was unbelivably quick, thanks
Kev Hunter
Does the GroupBy have to iterate the whole sequence before you get any results, or do you still get deferred execution here?
Don Kirkby
@Don Kirkby: For LINQ to Objects, `.GroupBy` doesn't enumerate the sequence. It enumerates the whole sequence as soon as `.GetEnumerator` is called on it (e.g. when used in `foreach` or something).
Mehrdad Afshari
A: 

I came up with a different approach. It uses a while iterator alright but the results are cached in memory like a regular LINQ until needed.
Here's the code.

[Runtime.CompilerServices.Extension()]
public IEnumerable<IEnumerable<T>> Paginate<T>(IEnumerable<T> source, int pageSize)
{
    List<IEnumerable<T>> pages = new List<IEnumerable<T>>();
    int skipCount = 0;

    while (skipCount * pageSize < source.Count) {
        pages.Add(source.Skip(skipCount * pageSize).Take(pageSize));
        skipCount += 1;
    }

    return pages;
}
Alex Essilfie
+3  A: 

I know this has already been answered, but if you plan on taking slices of IEnumerables often, then I recommend making a generic extension function like this:

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int chunkSize)
{
    return source.Where((x,i) => i % chunkSize == 0).Select((x,i) => source.Skip(i * chunkSize).Take(chunkSize));
}

Then you can use sequence.Split(3) to get what you want.

(you can name it something else like 'slice', or 'chunk' if you don't like that 'split' has already been defined for strings. 'Split' is just what I happened to call mine.)

diceguyd30
+1. I like the fact that you're able to achieve the same result as I did with one line of code.
Alex Essilfie