¿Divide una colección en partes `n` con LINQ?

¿Hay alguna manera de dividir una colección en n partes con LINQ? No necesariamente por igual, por supuesto.

Es decir, quiero dividir la colección en subcolecciones, que cada una contiene un subconjunto de los elementos, donde la última colección puede ser irregular.

Un linq puro y la solución más simple es como se muestra a continuación.

 static class LinqExtensions { public static IEnumerable> Split(this IEnumerable list, int parts) { int i = 0; var splits = from item in list group item by i++ % parts into part select part.AsEnumerable(); return splits; } } 

EDITAR: De acuerdo, parece que leí mal la pregunta. Lo leí como “piezas de longitud n” en lugar de “n piezas”. Doh! Teniendo en cuenta eliminar la respuesta …

(Respuesta original)

No creo que haya una forma de partición incorporada, aunque pretendo escribir una en mi conjunto de adiciones a LINQ to Objects. Marc Gravell tiene una implementación aquí, aunque probablemente lo modifique para devolver una vista de solo lectura:

 public static IEnumerable> Partition (this IEnumerable source, int size) { T[] array = null; int count = 0; foreach (T item in source) { if (array == null) { array = new T[size]; } array[count] = item; count++; if (count == size) { yield return new ReadOnlyCollection(array); array = null; count = 0; } } if (array != null) { Array.Resize(ref array, count); yield return new ReadOnlyCollection(array); } } 
 static class LinqExtensions { public static IEnumerable> Split(this IEnumerable list, int parts) { return list.Select((item, index) => new {index, item}) .GroupBy(x => x.index % parts) .Select(x => x.Select(y => y.item)); } } 

Ok, arrojaré mi sombrero al ring. Las ventajas de mi algoritmo:

  1. Sin costosos operadores de multiplicación, división o módulo
  2. Todas las operaciones son O (1) (ver nota a continuación)
  3. Funciona para IEnumerable <> source (no se necesita propiedad Count)
  4. Sencillo

El código:

 public static IEnumerable> Section(this IEnumerable source, int length) { if (length <= 0) throw new ArgumentOutOfRangeException("length"); var section = new List(length); foreach (var item in source) { section.Add(item); if (section.Count == length) { yield return section.AsReadOnly(); section = new List(length); } } if (section.Count > 0) yield return section.AsReadOnly(); } 

Como se señala en los comentarios a continuación, este enfoque en realidad no aborda la pregunta original que solicitó un número fijo de secciones de aproximadamente la misma longitud. Dicho esto, todavía puede usar mi enfoque para resolver la pregunta original llamándolo así:

 myEnum.Section(myEnum.Count() / number_of_sections + 1) 

Cuando se utiliza de esta manera, el enfoque ya no es O (1) ya que la operación de conteo () es O (N).

Esto es lo mismo que la respuesta aceptada, pero una representación mucho más simple:

 public static IEnumerable> Split(this IEnumerable items, int numOfParts) { int i = 0; return items.GroupBy(x => i++ % numOfParts); } 

El método anterior divide un IEnumerable en un número N de fragmentos de igual tamaño o casi iguales.

 public static IEnumerable> Partition(this IEnumerable items, int partitionSize) { int i = 0; return items.GroupBy(x => i++ / partitionSize).ToArray(); } 

El método anterior divide un IEnumerable en fragmentos del tamaño fijo deseado con el número total de fragmentos sin importancia, que no es de lo que se trata la pregunta.

El problema con el método Split , además de ser más lento, es que codifica la salida en el sentido de que la agrupación se realizará sobre la base de un múltiplo de N para cada posición, o en otras palabras, no se obtiene el trozos en el orden original.

Casi todas las respuestas aquí no conservan el orden, o se trata de particionar y no dividir, o simplemente es incorrecto. Pruebe esto que es más rápido, conserva el orden pero un poco más detallado:

 public static IEnumerable> Split(this ICollection items, int numberOfChunks) { if (numberOfChunks <= 0 || numberOfChunks > items.Count) throw new ArgumentOutOfRangeException("numberOfChunks"); int sizePerPacket = items.Count / numberOfChunks; int extra = items.Count % numberOfChunks; for (int i = 0; i < numberOfChunks - extra; i++) yield return items.Skip(i * sizePerPacket).Take(sizePerPacket); int alreadyReturnedCount = (numberOfChunks - extra) * sizePerPacket; int toReturnCount = extra == 0 ? 0 : (items.Count - numberOfChunks) / extra + 1; for (int i = 0; i < extra; i++) yield return items.Skip(alreadyReturnedCount + i * toReturnCount).Take(toReturnCount); } 

El método equivalente para una operación de Partition aquí

He estado usando la función Partición que publiqué anteriormente bastante a menudo. Lo único malo de esto es que no se transmitió por completo. Esto no es un problema si trabajas con pocos elementos en tu secuencia. Necesitaba una nueva solución cuando comencé a trabajar con más de 100.000 elementos en mi secuencia.

La siguiente solución es mucho más compleja (¡y más código!), Pero es muy eficiente.

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections; namespace LuvDaSun.Linq { public static class EnumerableExtensions { public static IEnumerable> Partition(this IEnumerable enumerable, int partitionSize) { /* return enumerable .Select((item, index) => new { Item = item, Index = index, }) .GroupBy(item => item.Index / partitionSize) .Select(group => group.Select(item => item.Item) ) ; */ return new PartitioningEnumerable(enumerable, partitionSize); } } class PartitioningEnumerable : IEnumerable> { IEnumerable _enumerable; int _partitionSize; public PartitioningEnumerable(IEnumerable enumerable, int partitionSize) { _enumerable = enumerable; _partitionSize = partitionSize; } public IEnumerator> GetEnumerator() { return new PartitioningEnumerator(_enumerable.GetEnumerator(), _partitionSize); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } class PartitioningEnumerator : IEnumerator> { IEnumerator _enumerator; int _partitionSize; public PartitioningEnumerator(IEnumerator enumerator, int partitionSize) { _enumerator = enumerator; _partitionSize = partitionSize; } public void Dispose() { _enumerator.Dispose(); } IEnumerable _current; public IEnumerable Current { get { return _current; } } object IEnumerator.Current { get { return _current; } } public void Reset() { _current = null; _enumerator.Reset(); } public bool MoveNext() { bool result; if (_enumerator.MoveNext()) { _current = new PartitionEnumerable(_enumerator, _partitionSize); result = true; } else { _current = null; result = false; } return result; } } class PartitionEnumerable : IEnumerable { IEnumerator _enumerator; int _partitionSize; public PartitionEnumerable(IEnumerator enumerator, int partitionSize) { _enumerator = enumerator; _partitionSize = partitionSize; } public IEnumerator GetEnumerator() { return new PartitionEnumerator(_enumerator, _partitionSize); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } class PartitionEnumerator : IEnumerator { IEnumerator _enumerator; int _partitionSize; int _count; public PartitionEnumerator(IEnumerator enumerator, int partitionSize) { _enumerator = enumerator; _partitionSize = partitionSize; } public void Dispose() { } public T Current { get { return _enumerator.Current; } } object IEnumerator.Current { get { return _enumerator.Current; } } public void Reset() { if (_count > 0) throw new InvalidOperationException(); } public bool MoveNext() { bool result; if (_count < _partitionSize) { if (_count > 0) { result = _enumerator.MoveNext(); } else { result = true; } _count++; } else { result = false; } return result; } } } 

¡Disfrutar!

Yo uso esto:

 public static IEnumerable> Partition(this IEnumerable instance, int partitionSize) { return instance .Select((value, index) => new { Index = index, Value = value }) .GroupBy(i => i.Index / partitionSize) .Select(i => i.Select(i2 => i2.Value)); } 

Hilo interesante Para obtener una versión de transmisión de Split / Partition, se pueden usar enumeradores y secuencias de rendimiento del enumerador utilizando métodos de extensión. Convertir el código imperativo en código funcional utilizando el rendimiento es una técnica muy poderosa.

Primero una extensión del enumerador que convierte un conteo de elementos en una secuencia perezosa:

 public static IEnumerable TakeFromCurrent(this IEnumerator enumerator, int count) { while (count > 0) { yield return enumerator.Current; if (--count > 0 && !enumerator.MoveNext()) yield break; } } 

Y luego una extensión enumerable que divide una secuencia:

 public static IEnumerable> Partition(this IEnumerable seq, int partitionSize) { var enumerator = seq.GetEnumerator(); while (enumerator.MoveNext()) { yield return enumerator.TakeFromCurrent(partitionSize); } } 

El resultado final es una implementación altamente eficiente, de transmisión y perezosa que se basa en un código muy simple.

¡Disfrutar!

Es eficiente desde el punto de vista de la memoria y pospone la ejecución tanto como sea posible (por lote) y opera en tiempo lineal O (n)

  public static IEnumerable> InBatchesOf(this IEnumerable items, int batchSize) { List batch = new List(batchSize); foreach (var item in items) { batch.Add(item); if (batch.Count >= batchSize) { yield return batch; batch = new List(); } } if (batch.Count != 0) { //can't be batch size or would've yielded above batch.TrimExcess(); yield return batch; } } 

Hay muchas respuestas excelentes para esta pregunta (y sus primos). Lo necesitaba yo mismo y había creado una solución que está diseñada para ser eficiente y tolerante a errores en un escenario donde la colección de origen se puede tratar como una lista. No utiliza ninguna iteración diferida por lo que puede no ser adecuado para colecciones de tamaño desconocido que pueden aplicar presión de memoria.

 static public IList GetChunks(this IEnumerable source, int batchsize) { IList result = null; if (source != null && batchsize > 0) { var list = source as List ?? source.ToList(); if (list.Count > 0) { result = new List(); for (var index = 0; index < list.Count; index += batchsize) { var rangesize = Math.Min(batchsize, list.Count - index); result.Add(list.GetRange(index, rangesize).ToArray()); } } } return result ?? Enumerable.Empty().ToList(); } static public void TestGetChunks() { var ids = Enumerable.Range(1, 163).Select(i => i.ToString()); foreach (var chunk in ids.GetChunks(20)) { Console.WriteLine("[{0}]", String.Join(",", chunk)); } } 

He visto algunas respuestas en esta familia de preguntas que usan GetRange y Math.Min. Pero creo que, en general, esta es una solución más completa en términos de comprobación de errores y eficiencia.

  protected List> MySplit(int MaxNumber, int Divider) { List> lst = new List>(); int ListCount = 0; int d = MaxNumber / Divider; lst.Add(new List()); for (int i = 1; i <= MaxNumber; i++) { lst[ListCount].Add(i); if (i != 0 && i % d == 0) { ListCount++; d += MaxNumber / Divider; lst.Add(new List()); } } return lst; } 

Great Answers, para mi escenario, probé la respuesta aceptada, y parece que no mantiene el orden. también hay una gran respuesta de Nawfal que mantiene el orden. Pero en mi caso, quería dividir el rest de una manera normalizada, todas las respuestas que vi difundieron el rest o al principio o al final.

Mi respuesta también requiere que el rest se extienda de manera más normalizada.

  static class Program { static void Main(string[] args) { var input = new List(); for (int k = 0; k < 18; ++k) { input.Add(k.ToString()); } var result = splitListIntoSmallerLists(input, 15); int i = 0; foreach(var resul in result){ Console.WriteLine("------Segment:" + i.ToString() + "--------"); foreach(var res in resul){ Console.WriteLine(res); } i++; } Console.ReadLine(); } private static List> splitListIntoSmallerLists(List i_bigList,int i_numberOfSmallerLists) { if (i_numberOfSmallerLists <= 0) throw new ArgumentOutOfRangeException("Illegal value of numberOfSmallLists"); int normalizedSpreadRemainderCounter = 0; int normalizedSpreadNumber = 0; //eg 7 /5 > 0 ==> output size is 5 , 2 /5 < 0 ==> output is 2 int minimumNumberOfPartsInEachSmallerList = i_bigList.Count / i_numberOfSmallerLists; int remainder = i_bigList.Count % i_numberOfSmallerLists; int outputSize = minimumNumberOfPartsInEachSmallerList > 0 ? i_numberOfSmallerLists : remainder; //In case remainder > 0 we want to spread the remainder equally between the others if (remainder > 0) { if (minimumNumberOfPartsInEachSmallerList > 0) { normalizedSpreadNumber = (int)Math.Floor((double)i_numberOfSmallerLists / remainder); } else { normalizedSpreadNumber = 1; } } List> retVal = new List>(outputSize); int inputIndex = 0; for (int i = 0; i < outputSize; ++i) { retVal.Add(new List()); if (minimumNumberOfPartsInEachSmallerList > 0) { retVal[i].AddRange(i_bigList.GetRange(inputIndex, minimumNumberOfPartsInEachSmallerList)); inputIndex += minimumNumberOfPartsInEachSmallerList; } //If we have remainder take one from it, if our counter is equal to normalizedSpreadNumber. if (remainder > 0) { if (normalizedSpreadRemainderCounter == normalizedSpreadNumber-1) { retVal[i].Add(i_bigList[inputIndex]); remainder--; inputIndex++; normalizedSpreadRemainderCounter=0; } else { normalizedSpreadRemainderCounter++; } } } return retVal; } } 

Si el orden en estas partes no es muy importante, puedes probar esto:

 int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int n = 3; var result = array.Select((value, index) => new { Value = value, Index = index }).GroupBy(i => i.Index % n, i => i.Value); // or var result2 = from i in array.Select((value, index) => new { Value = value, Index = index }) group i.Value by i.Index % n into g select g; 

Sin embargo, estos no se pueden convertir a IEnumerable > por algún motivo …

Este es mi código, bueno y corto.

   Public Function Chunk(Of T)(ByVal this As IList(Of T), ByVal size As Integer) As List(Of List(Of T)) Dim result As New List(Of List(Of T)) For i = 0 To CInt(Math.Ceiling(this.Count / size)) - 1 result.Add(New List(Of T)(this.GetRange(i * size, Math.Min(size, this.Count - (i * size))))) Next Return result End Function 

Esta es mi manera, enumerando artículos y rompiendo fila por columnas

  int repat_count=4; arrItems.ForEach((x, i) => { if (i % repat_count == 0) row = tbo.NewElement(el_tr, cls_min_height); var td = row.NewElement(el_td); td.innerHTML = x.Name; }); 

Estaba buscando una división como la de una cuerda, por lo que toda la lista se divide de acuerdo con alguna regla, no solo la primera parte, esta es mi solución

 List sequence = new List(); for (int i = 0; i < 2000; i++) { sequence.Add(i); } int splitIndex = 900; List> splitted = new List>(); while (sequence.Count != 0) { splitted.Add(sequence.Take(splitIndex).ToList() ); sequence.RemoveRange(0, Math.Min(splitIndex, sequence.Count)); } 

Aquí hay una pequeña modificación para la cantidad de elementos en lugar de la cantidad de partes:

 public static class MiscExctensions { public static IEnumerable> Split(this IEnumerable list, int nbItems) { return ( list .Select((o, n) => new { o, n }) .GroupBy(g => (int)(gn / nbItems)) .Select(g => g.Select(x => xo)) ); } } 
 int[] items = new int[] { 0,1,2,3,4,5,6,7,8,9, 10 }; int itemIndex = 0; int groupSize = 2; int nextGroup = groupSize; var seqItems = from aItem in items group aItem by (itemIndex++ < nextGroup) ? nextGroup / groupSize : (nextGroup += groupSize) / groupSize into itemGroup select itemGroup.AsEnumerable(); 

Acabo de encontrar este hilo, y la mayoría de las soluciones aquí implican agregar elementos a las colecciones, materializando efectivamente cada página antes de devolverla. Esto es malo por dos razones: primero, si sus páginas son grandes, hay una sobrecarga de memoria para llenar la página, en segundo lugar hay iteradores que invalidan los registros anteriores cuando avanza a la siguiente (por ejemplo, si ajusta un DataReader dentro de un método de enumerador) .

Esta solución utiliza dos métodos de enumerador nesteds para evitar la necesidad de almacenar en caché elementos en colecciones temporales. Dado que los iteradores externo e interno atraviesan el mismo enumerable, necesariamente comparten el mismo enumerador, por lo que es importante no avanzar el externo hasta que haya terminado con el procesamiento de la página actual. Dicho esto, si decide no repetir todo el camino a través de la página actual, cuando pase a la siguiente página, esta solución se repetirá hasta el límite de la página automáticamente.

 using System.Collections.Generic; public static class EnumerableExtensions { ///  /// Partitions an enumerable into individual pages of a specified size, still scanning the source enumerable just once ///  /// The element type /// The source enumerable /// The number of elements to return in each page ///  public static IEnumerable> Partition(this IEnumerable enumerable, int pageSize) { var enumerator = enumerable.GetEnumerator(); while (enumerator.MoveNext()) { var indexWithinPage = new IntByRef { Value = 0 }; yield return SubPartition(enumerator, pageSize, indexWithinPage); // Continue iterating through any remaining items in the page, to align with the start of the next page for (; indexWithinPage.Value < pageSize; indexWithinPage.Value++) { if (!enumerator.MoveNext()) { yield break; } } } } private static IEnumerable SubPartition(IEnumerator enumerator, int pageSize, IntByRef index) { for (; index.Value < pageSize; index.Value++) { yield return enumerator.Current; if (!enumerator.MoveNext()) { yield break; } } } private class IntByRef { public int Value { get; set; } } }