views:

65

answers:

3

Does there exist a LINQ method to group a given collection into subgroups with specified number of elements I mean, something like Scala's grouped method.
e.g. in Scala, List(89, 67, 34, 11, 34).grouped(2) gives List(List(89, 67), List(34, 11), List(34)).

In case such a method doesn't exist, what would be the LINQ way to do it?

+1  A: 

You could try the approach shown in this answer to this similar question.

public static class GroupingExtension
{
    public static IEnumerable<IEnumerable<T>> Grouped<T>(
        this IEnumerable<T> input,
        int groupCount)
    {
        if (input == null) throw new ArgumentException("input");
        if (groupCount < 1) throw new ArgumentException("groupCount");

        IEnumerator<T> e = input.GetEnumerator();

        while (true)
        {
            List<T> l = new List<T>();
            for (int n = 0; n < groupCount; ++n)
            {
                if (!e.MoveNext())
                {
                    if (n != 0)
                    {
                        yield return l;
                    }
                    yield break;
                }
                l.Add(e.Current);
            }
            yield return l;
        }
    }
}

Use like this:

List<int> l = new List<int>{89, 67, 34, 11, 34};
foreach (IEnumerable<int> group in l.Grouped(2)) {
    string s = string.Join(", ", group.Select(x => x.ToString()).ToArray());
    Console.WriteLine(s);
}

Result:

89, 67
34, 11
34
Mark Byers
So no built-in available? :|
Jay Sinha
By the way please use some different name for the method. The word tuple already has a meaning in context of C# / LINQ/ FP.
Jay Sinha
Do you have a good suggestion? `Grouped`? Something else? I didn't write the code, but I imagine that you can just change the name of the method without affecting how it works. It doesn't seem to call itself recursively or anything like that.
Mark Byers
No. That's not the issue. Naming a method `Tupled` that doesn't do anything related to tuples would just confuse other people. `Grouped` or `GroupInto` would be fine IMO.
Jay Sinha
Upvoted your answer. Too bad I can't mark multiple answers as correct. Thank you. :)
Jay Sinha
+1  A: 

Here is a website that seems to have some sample code to do what you want: http://www.chinhdo.com/20080515/chunking/

So what you could do is take this method and create an extension method.

Extension method sample:

static class ListExtension
{
    public static List<List<T>> BreakIntoChunks<T>(this List<T> list, int chunkSize)
    {
        if (chunkSize <= 0)
        {
            throw new ArgumentException("chunkSize must be greater than 0.");
        }

        List<List<T>> retVal = new List<List<T>>();

        while (list.Count > 0)
        {
            int count = list.Count > chunkSize ? chunkSize : list.Count;
            retVal.Add(list.GetRange(0, count));
            list.RemoveRange(0, count);
        }

        return retVal;
    }
}
spinon
I'd be happy if you post the code inline with a link to the original source.
Jay Sinha
With the code in the answer above you can not just call BreakIntoChunks(size) off of any List<T> you have. You just need to include the namespace from where you place the extension method.
spinon
Err... Comments is not exactly the right place to post the code. I suggest adding it to your answer instead.
Jay Sinha
Good point it does look ugly. Let me fix.
spinon
Upvoted your answer. Too bad I can't mark multiple answers as correct. Thank you. :)
Jay Sinha
+1  A: 

Yes, you can. But you can argue if it's very pretty...

  Int64[] aValues = new Int64[] { 1, 2, 3, 4, 5, 6 };
  var result = aValues
          .Select( ( x, y ) => new KeyValuePair<Int64, Int32>( x, y ) )
          .GroupBy( x => x.Value / 2 )
          .Select( x => x.Select( y => y.Key ).ToList() ).ToList();

How it works:

Select x and y from the original collection, where x is the actual value and y is the index of it in the given collection. Then group by integer devision of the index and the desired grouping length ( in this example 2 ).

Grouping by integer devision will round up to the lower - so 0 / 2 = 0, 1 / 2 = 0, etc. which will give us the needed grouping category value. This is what we are grouping against here.

For result select only the values grouped in lists and return them as a collection of lists.

Ivan Zlatanov
Beautiful! Now could you please explain in brief how it works? I am not very well versed with LINQ you see.
Jay Sinha
Of course. Let me know if this explanation helps and if not, I'll try to come up with a better one.
Ivan Zlatanov
No, this is enough. I like the trick. Thank you very much for the excellent answer. :)
Jay Sinha