Search code examples
c#.netrazorcollections

How can I split a list or IEnumerable into smaller lists of N size?


I have a whole lot of checkboxes, currently I want to put them in 3 columns with a div containing each column.

However I am highly capricious and might want to put them in 10 or 20 columns tomorrow, I may want to rotate it to be rows, or any other kind of div.

An example for 3 columns:

<div class="column-1">
    <input type="checkbox" id="foo-1">Checkbox # 1
    <input type="checkbox" id="foo-2">Checkbox # 2
    <input type="checkbox" id="foo-3">Checkbox # 3
</div>
<div class="column-2">
    <input type="checkbox" id="foo-4">Checkbox # 4
    <input type="checkbox" id="foo-5">Checkbox # 5
    <input type="checkbox" id="foo-6">Checkbox # 6
</div>
<div class="column-3">
    <input type="checkbox" id="foo-7">Checkbox # 7
    <input type="checkbox" id="foo-8">Checkbox # 8
    <input type="checkbox" id="foo-9">Checkbox # 9
</div>

I first tried to do this using 3 while loops as below:

@{
    int i = 0;
    IEnumerable<foo> bar = new List<foo>(){foo1,foo2,foo3,foo4,foo5,foo6};
 }
    <div id="column-1">
        while (i < bar.Count() / 3)
        {
        baz = bar.ElementAt(i);
        <input type="checkbox" id="foo-@i" checked='@baz.CheckIt' />@baz.DisplayText
        i++;
        }
    </div>
    <div id="column-2">
        @while (i < bar.Count() * 2 / 3)
        {
        baz = bar.ElementAt(i);
        <input type="checkbox" id="foo-@i" checked='@baz.CheckIt' />@baz.DisplayText
        i++;
        }
    </div>
    <div id="column-3">
        @while (i < bar.Count())
        {
        baz = bar.ElementAt(i);
        <input type="checkbox" id="foo-@i" checked='@baz.CheckIt' />@baz.DisplayText
        i++;
        }
    </div>

This works fine, but gets very ugly if I want to use more than 3 columns. It's already pretty ugly honestly. It also doesn't allow the number of columns to be easily changed. It could also be changed to for loops that each start at a different value (0,1,2 respectively) and increment i by 3, but that's still 3 loops.

I also tried to do this in a single loop using some modulus operator like so (pseudocode):

i = 0
foreach(checkbox in list)
{
    if(i % numberOfColumns == 0)
    {
        createNewRow()
    }
    placeCheckBox()
    i++
}

Which is much more concise and readable in my opinion. However this gives a huge number of row divs, which doesn't serve well for my purpose. Also there's the issue of actually getting the checkbox to be inside the div. The two solutions above could be combined to create columns rather than rows, which seems better but still has the same issues as the second solution.

I feel there must be a much simpler and better way to do this, without having to hardcode the number of columns, as no-one wants to write 20 of those basic while loops.

So is there an extensible and concise way to generate html tags for each item in a list seperated into multiple divs? If so what is it?


Solution

  • Solved it using fairly simple nested loops (perhaps should change it to both being while for clarity).

    This traverses the list in place and in sequential order, just stopping each time it reaches a fraction of numberOfColumns. It then creates a new column and increments the fraction to get the next portion of items.

    @{
        int numberOfColumns = 3;
        int currentItemIndex = 0;
        for (int currentColumnIndex = 1; currentColumnIndex <= numberOfColumns; currentColumnIndex++)
        {
            //Create a new column
            <div id="column-@currentColumnIndex">
                //Loop populate column with items
                @while (currentItemIndex < bar.Count() * currentColumnIndex / numberOfColumns)
                {
                    baz = bar.ElementAt(currentItemIndex);
                    //Add each item
                    <input type="checkbox" id="foo-@currentItemIndex" checked='@baz.CheckIt' />@baz.DisplayText
                    currentItemIndex++;
                }
            </div>
        }
    
    }
    

    A step-by-step example for two columns:

    If you made numberOfColumns = 2 the outer loop would execute twice. On the first execution the inner loop goes until currentItemIndex was equal to bar.Count() * 1 / 2, or half of bar.Count() which is half our items in the first column. Then it iterates to the next stage of the outer loop, increasing the maximum counter for our the inner loop to bar.Count(), because the counter is still set at half of bar.Count()this places the second half of our items in the second column. Voila!