Robert McKee Robert McKee - 1 month ago 12
C# Question

Make (mostly) equal length sets

Using LINQ (or morelinq), How do I divide an unknown length (but small) array into even sets with the smaller (uneven) sets at the end, but maintaining the order within each list?

var array = new int[] {1,2,3,4};
var sets = array.something(3);

Looking for a result of:
{1,2},{3},{4}


{1} -> {1},{},{}
{1,2} -> {1},{2},{}
{1,2,3} -> {1},{2},{3}
{1,2,3,4} -> {1,2},{3},{4}
{1,2,3,4,5} -> {1,2},{3,4},{5}
{1,2,3,4,5,6} -> {1,2},{3,4},{5,6}


My original code:

const int MaxPerColumn = 6;
var perColumn = (list.Count + columnCount - 1) / columnCount;
for (var col = 1; col <= columnCount; col++)
{
var items = list.Skip((col - 1) * columnCount).Take(perColumn).Pad(MaxPerColumn, "").ToList();

// DoSomething
}


which did not work, because with a list of
{1,2,3,4}
it created
{1,2},{3,4},{}

Answer

I suggest not using Linq here, but IEnumerator<T>, not even IEnumerable<T> in the implementation:

public static IEnumerable<T[]> Something<T>(this IEnumerable<T> source, int count) {
  if (null == source)
    throw new ArgumentNullException("source");
  else if (count <= 0)
    throw new ArgumentOutOfRangeException("count");

  int c = source.Count();
  int size = c / count + (c % count > 0 ? 1 : 0);
  int large = count - count * size + c;    

  using (var en = source.GetEnumerator()) {
    for (int i = 0; i < count; ++i) {
      T[] chunk = new T[i < large ? size : size - 1];

      for (int j = 0; j < chunk.Length; ++j) {
        en.MoveNext();

        chunk[j] = en.Current;
      }

      yield return chunk;
    }
  }
}

....

var array = new int[] { 1, 2, 3, 4 };

string result = string.Join(", ", array
  .Something(5)
  .Select(item => $"[{string.Join(", ", item)}]"));

// [1], [2], [3], [4], []
Console.Write(result);