11

I have a list of strings which I send to a queue. I need to split up the list so that I end up with a list of lists where each list contains a maximum (user defined) number of strings. So for example, if I have a list with the following A,B,C,D,E,F,G,H,I and the max size of a list is 4, I want to end up with a list of lists where the first list item contains: A,B,C,D, the second list has: E,F,G,H and the last list item just contains: I. I have looked at the “TakeWhile” function but am not sure if this is the best approach. Any solution for this?

3 Answers 3

24

You can set up a List<IEnumerable<string>> and then use Skip and Take to split the list:

IEnumerable<string> allStrings = new[] { "A", "B", "C", "D", "E", "F", "G", "H", "I" };

List<IEnumerable<string>> listOfLists = new List<IEnumerable<string>>();
for (int i = 0; i < allStrings.Count(); i += 4)
{                
    listOfLists.Add(allStrings.Skip(i).Take(4)); 
}

Now listOfLists will contain, well, a list of lists.

Sign up to request clarification or add additional context in comments.

2 Comments

I voted for this as it's just what I needed. RPM1984 also gave a good answer but this one fitted straight into my code.
what if i need to split list to 5 lists no matter how many strings those lists would end up ?
21
/// <summary>
/// Splits a <see cref="List{T}"/> into multiple chunks.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list">The list to be chunked.</param>
/// <param name="chunkSize">The size of each chunk.</param>
/// <returns>A list of chunks.</returns>
public static List<List<T>> SplitIntoChunks<T>(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>>();
    int index = 0;
    while (index < list.Count)
    {
        int count = list.Count - index > chunkSize ? chunkSize : list.Count - index;
        retVal.Add(list.GetRange(index, count));

        index += chunkSize;
    }

    return retVal;
}

Reference: http://www.chinhdo.com/20080515/chunking/

1 Comment

Your method is damn Great! It does what I exactly wanted.
4

Some related reading:

Otherwise, minor variation on accepted answer to work with enumerables (for lazy-loading and processing, in case the list is big/expensive). I would note that materializing each chunk/segment (e.g. via .ToList or .ToArray, or simply enumerating each chunk) could have sideeffects -- see tests.

Methods

// so you're not repeatedly counting an enumerable
IEnumerable<IEnumerable<T>> Chunk<T>(IEnumerable<T> list, int totalSize, int chunkSize) {
    int i = 0;
    while(i < totalSize) {
        yield return list.Skip(i).Take(chunkSize);
        i += chunkSize;
    }
}
// convenience for "countable" lists
IEnumerable<IEnumerable<T>> Chunk<T>(ICollection<T> list, int chunkSize) {
    return Chunk(list, list.Count, chunkSize);
}
IEnumerable<IEnumerable<T>> Chunk<T>(IEnumerable<T> list, int chunkSize) {
    return Chunk(list, list.Count(), chunkSize);
}

Test (Linqpad)

(note: I had to include the Assert methods for linqpad)

void Main()
{
    var length = 10;
    var size = 4;

    test(10, 4);
    test(10, 6);
    test(10, 2);
    test(10, 1);

    var sideeffects = Enumerable.Range(1, 10).Select(i => {
        string.Format("Side effect on {0}", i).Dump();
        return i;
    });

    "--------------".Dump("Before Chunking");
    var result = Chunk(sideeffects, 4);
    "--------------".Dump("After Chunking");
    result.Dump("SideEffects");
    var list = new List<int>();
    foreach(var segment in result) {
        list.AddRange(segment);
    }
    list.Dump("After crawling");

    var segment3 = result.Last().ToList();
    segment3.Dump("Last Segment");
}

// test
void test(int length, int size) {
    var list = Enumerable.Range(1, length);

    var c1 = Chunk(list, size);

    c1.Dump(string.Format("Results for [{0} into {1}]", length, size));

    Assert.AreEqual( (int) Math.Ceiling( (double)length / (double)size), c1.Count(), "Unexpected number of chunks");
    Assert.IsTrue(c1.All(c => c.Count() <= size), "Unexpected size of chunks");
}

1 Comment

The best solution I've found is from MoreLinq -- code.google.com/p/morelinq/source/browse/MoreLinq/Batch.cs

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.