How can I split a list vertically in n parts using LINQ

I would like to split the list in parts , not knowing how many elements I will have in this list . The question is different from the question about you, who wants to split the list into a piece of a fixed size.

int[] a = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 

I would like the values ​​to be separated vertically .

Divided into 2:

 ------------------- | item 1 | item 6 | | item 2 | item 7 | | item 3 | item 8 | | item 4 | item 9 | | item 5 | | 

Divided into 3:

 | item 1 | item 4 | item 7 | | item 2 | item 5 | item 8 | | item 3 | item 6 | item 9 | 

Divided into 4:

 | item 1 | item 4 | item 6 | item 8 | | item 2 | item 5 | item 7 | item 9 | | item 3 | | | | 

I found several C # extensions that can do this, but don't distribute the value the way I want. Here is what I 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 is 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 be able to choose the number of parts I need?

In real life

For those of you who want to know in which situation I would like to split the list vertically, here is a screenshot of a section of my website:

enter image description here

+6
source share
3 answers

S 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 List<int> .


EDIT:

After you slightly changed (clarified, maybe) your question, I think this is what you need:

 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 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 will create:

divided by 2: a.Split(2);

 | item 1 | item 6 | | item 2 | item 7 | | item 3 | item 8 | | item 4 | item 9 | | item 5 | | 

splits into 3: a.Split(3);

 | item 1 | item 4 | item 7 | | item 2 | item 5 | item 8 | | item 3 | item 6 | item 9 | 

divided by 4: a.Split(4);

 | item 1 | item 4 | item 6 | item 8 | | item 2 | item 5 | item 7 | item 9 | | item 3 | | | | 

In addition, if you have:

 int[] b = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // 10 items 

and split into 4: b.Split(4);

 | item 1 | item 4 | item 7 | item 9 | | item 2 | item 5 | item 8 | item 10| | item 3 | item 6 | | | 
+16
source

This seems like a pretty trick. This can probably be done even more efficiently, but it was rather puzzling ... It is 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 initially ignored the LINQ query, as I was having trouble wrapping around it. A solution using normal array manipulation may 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 will look like this:

 //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 elements, it is impossible to calculate the place where it can be placed.

So, after doing a little preliminary calculation, you can use LINQ to achieve the same as above, for this you need to go through IEnumerable twice, it seems that this is not so. The trick is to compute the string for which a value will be assigned.

  //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; } 
+1
source

Use .Take(#OfElements) to indicate the number of elements you want to accept.

You can also use .First and .Last

-2
source

All Articles