I would like to split a list in parts, without knowing how much items I will have in that list. The question is different from those of you who wants to split a list into chunk of fixed size.
int[] a = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
I would like the values to be splitted vertically.
Splitted in 2 :
-------------------
| item 1 | item 6 |
| item 2 | item 7 |
| item 3 | item 8 |
| item 4 | item 9 |
| item 5 | |
Splitted in 3:
| item 1 | item 4 | item 7 |
| item 2 | item 5 | item 8 |
| item 3 | item 6 | item 9 |
Splitted in 4:
| item 1 | item 4 | item 6 | item 8 |
| item 2 | item 5 | item 7 | item 9 |
| item 3 | | | |
I've found a few c# extensions that can do that but it doesn't distribute the value the way I want. Here's what I've found:
// this technic is an horizontal distribution
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
{
int i = 0;
var splits = from item in list
group item by i++ % parts into part
select part.AsEnumerable();
return splits;
}
the result is this but my problem is that the value are distributed horizontally:
| item 1 | item 2 |
| item 3 | item 4 |
| item 5 | item 6 |
| item 7 | item 8 |
| item 9 | |
or
| item 1 | item 2 | item 3 |
| item 4 | item 5 | item 6 |
| item 7 | item 8 | item 9 |
any idea how I can distribute my values vertically and have the possibility to choose the number of parts that i want?
For those of you who want to know in which situation I would like to split a list vertically, here's a screenshot of a section of my website:
With .Take() and .Skip() you can:
int[] a = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int splitIndex = 4; // or (a.Length / 2) to split in the middle.
var list1 = a.Take(splitIndex).ToArray(); // Returns a specified number of contiguous elements from the start of a sequence.
var list2 = a.Skip(splitIndex).ToArray(); // Bypasses a specified number of elements in a sequence and then returns the remaining elements.
You can use .ToList()
instead of .ToArray()
if you want a List<int>
.
EDIT:
After you changed (clarified maybe) your question a bit, I guess this is what you needed:
public static class Extensions
{
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int parts)
{
var list = new List<T>(source);
int defaultSize = (int)((double)list.Count / (double)parts);
int offset = list.Count % parts;
int position = 0;
for (int i = 0; i < parts; i++)
{
int size = defaultSize;
if (i < offset)
size++; // Just add one to the size (it's enough).
yield return list.GetRange(position, size);
// Set the new position after creating a part list, so that it always start with position zero on the first yield return above.
position += size;
}
}
}
Using it:
int[] a = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var lists = a.Split(2);
This would generate:
split in 2 : a.Split(2);
| item 1 | item 6 |
| item 2 | item 7 |
| item 3 | item 8 |
| item 4 | item 9 |
| item 5 | |
split in 3 : a.Split(3);
| item 1 | item 4 | item 7 |
| item 2 | item 5 | item 8 |
| item 3 | item 6 | item 9 |
split in 4 : a.Split(4);
| item 1 | item 4 | item 6 | item 8 |
| item 2 | item 5 | item 7 | item 9 |
| item 3 | | | |
Also, if you would have:
int[] b = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // 10 items
and split in 4 : b.Split(4);
| item 1 | item 4 | item 7 | item 9 |
| item 2 | item 5 | item 8 | item 10|
| item 3 | item 6 | | |
This seems to do the trick quite nicely. It can probably be done even more efficient, but this was puzzling enough... It's much easier to do:
1|4|7|10
2|5|8
3|6|9
Than:
1|4|7|9
2|5|8|10
3|6|
I ignored the LINQ request first, since I had trouble wrapping my head around it. The solution using normal array manipulation could result in something like this:
public static IEnumerable<IEnumerable<TListItem>> Split<TListItem>(this IEnumerable<TListItem> items, int parts)
where TListItem : struct
{
var itemsArray = items.ToArray();
int itemCount = itemsArray.Length;
int itemsOnlastRow = itemCount - ((itemCount / parts) * parts);
int numberOfRows = (int)(itemCount / (decimal)parts) + 1;
for (int row = 0; row < numberOfRows; row++)
{
yield return SplitToRow(itemsArray, parts, itemsOnlastRow, numberOfRows, row);
}
}
private static IEnumerable<TListItem> SplitToRow<TListItem>(TListItem[] items, int itemsOnFirstRows, int itemsOnlastRow,
int numberOfRows, int row)
{
for (int column = 0; column < itemsOnFirstRows; column++)
{
// Are we on the last row?
if (row == numberOfRows - 1)
{
// Are we within the number of items on that row?
if (column < itemsOnlastRow)
{
yield return items[(column + 1) * numberOfRows -1];
}
}
else
{
int firstblock = itemsOnlastRow * numberOfRows;
int index;
// are we in the first block?
if (column < itemsOnlastRow)
{
index = column*numberOfRows + ((row + 1)%numberOfRows) - 1;
}
else
{
index = firstblock + (column - itemsOnlastRow)*(numberOfRows - 1) + ((row + 1)%numberOfRows) - 1;
}
yield return
items[index];
}
}
}
The LINQ pseudo code would be:
//WARNING: DOES NOT WORK
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
{
int itemOnIndex = 0;
var splits = from item in list
group item by MethodToDefineRow(itemOnIndex++) into row
select row.AsEnumerable();
return splits;
}
But without knowing the number of items, there is no way to calculate the place where to put it.
So by doing a little pre-calculation, you can use LINQ to achieve the same thing as above this requires going through the IEnumerable twice, there doesn't seem to be a way around that. The trick is to calculate the row each value will be assigned to.
//WARNING: Iterates the IEnumerable twice
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
{
int itemOnIndex = 0;
int itemCount = list.Count();
int itemsOnlastRow = itemCount - ((itemCount / parts) * parts);
int numberOfRows = (int)(itemCount / (decimal)parts) + 1;
int firstblock = (numberOfRows*itemsOnlastRow);
var splits = from item in list
group item by (itemOnIndex++ < firstblock) ? ((itemOnIndex -1) % numberOfRows) : ((itemOnIndex - 1 - firstblock) % (numberOfRows - 1)) into row
orderby row.Key
select row.AsEnumerable();
return splits;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With