dividir - Lista de particiones LINQ en listas de 8 miembros
dividir listas c# (7)
Esta pregunta ya tiene una respuesta aquí:
- Dividir lista en sublistas con LINQ 25 respuestas
¿Cómo tomaría una Lista (usando LINQ) y la dividiría en una Lista de Listas dividiendo la lista original en cada 8ª entrada?
Imagino que algo como esto involucraría a Skip y / o Take, pero todavía soy bastante nuevo en LINQ.
Editar: Usando C # / .Net 3.5
Edit2: esta pregunta tiene una redacción diferente a la otra pregunta "duplicada". Aunque los problemas son similares, las respuestas en esta pregunta son superiores: tanto la respuesta "aceptada" es muy sólida (con la declaración de yield
) como la sugerencia de Jon Skeet de usar MoreLinq (que no se recomienda en la "otra" pregunta). ) A veces, los duplicados son buenos porque obligan a volver a examinar un problema.
La solución más simple es dada por Mel:
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items,
int partitionSize)
{
int i = 0;
return items.GroupBy(x => i++ / partitionSize).ToArray();
}
Conciso pero más lento. El método anterior divide un IEnumerable en fragmentos del tamaño fijo deseado con la cantidad total de fragmentos sin importancia. Para dividir un IEnumerable en N cantidad de trozos de igual tamaño o tamaños casi iguales, podría hacer:
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items,
int numOfParts)
{
int i = 0;
return items.GroupBy(x => i++ % numOfParts);
}
Para acelerar las cosas, un enfoque directo sería:
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items,
int partitionSize)
{
if (partitionSize <= 0)
throw new ArgumentOutOfRangeException("partitionSize");
int innerListCounter = 0;
int numberOfPackets = 0;
foreach (var item in items)
{
innerListCounter++;
if (innerListCounter == partitionSize)
{
yield return items.Skip(numberOfPackets * partitionSize).Take(partitionSize);
innerListCounter = 0;
numberOfPackets++;
}
}
if (innerListCounter > 0)
yield return items.Skip(numberOfPackets * partitionSize);
}
Esto es más rápido que cualquier cosa actualmente en el planeta ahora :) Los métodos equivalentes para una operación Split
here
No es para nada lo que los diseñadores originales de Linq tenían en mente, pero echa un vistazo a este uso indebido de GroupBy:
public static IEnumerable<IEnumerable<T>> BatchBy<T>(this IEnumerable<T> items, int batchSize)
{
var count = 0;
return items.GroupBy(x => (count++ / batchSize)).ToList();
}
[TestMethod]
public void BatchBy_breaks_a_list_into_chunks()
{
var values = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var batches = values.BatchBy(3);
batches.Count().ShouldEqual(4);
batches.First().Count().ShouldEqual(3);
batches.Last().Count().ShouldEqual(1);
}
Creo que gana el premio de "golf" por esta pregunta. ToList
es muy importante ya que desea asegurarse de que la agrupación se haya realizado antes de intentar hacer algo con la salida. Si quita ToList
, obtendrá algunos efectos secundarios extraños.
Take no será muy eficiente, ya que no elimina las entradas realizadas.
¿Por qué no usar un bucle simple?
public IEnumerable<IList<T>> Partition<T>(this/* <-- see extension methods*/ IEnumerable<T> src,int num)
{
IEnumerator<T> enu=src.getEnumerator();
while(true)
{
List<T> result=new List<T>(num);
for(int i=0;i<num;i++)
{
if(!enu.MoveNext())
{
if(i>0)yield return result;
yield break;
}
result.Add(enu.Current);
}
yield return result;
}
}
Use el siguiente método de extensión para dividir la entrada en subconjuntos
public static class IEnumerableExtensions
{
public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
{
List<T> toReturn = new List<T>(max);
foreach(var item in source)
{
toReturn.Add(item);
if (toReturn.Count == max)
{
yield return toReturn;
toReturn = new List<T>(max);
}
}
if (toReturn.Any())
{
yield return toReturn;
}
}
}
Es mejor que MoreLinq una biblioteca como MoreLinq , pero si realmente tienes que hacer esto usando "plain LINQ", puedes usar GroupBy
:
var sequence = new[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
var result = sequence.Select((x, i) => new {Group = i/8, Value = x})
.GroupBy(item => item.Group, g => g.Value)
.Select(g => g.Where(x => true));
// result is: { {1,2,3,4,5,6,7,8}, {9,10,11,12,13,14,15,16} }
Básicamente, usamos la versión de Select()
que proporciona un índice para el valor que se está consumiendo, dividimos el índice entre 8 para identificar a qué grupo pertenece cada valor. Luego, agrupamos la secuencia con esta clave de agrupación. La última Select
solo reduce el IGrouping<>
a un IEnumerable<IEnumerable<T>>
(y no es estrictamente necesario ya que IGrouping
es un IEnumerable
).
Es bastante fácil convertir esto en un método reutilizable al factorizar nuestra constante 8
en el ejemplo y reemplazarlo con un parámetro específico. No es necesariamente la solución más elegante, y ya no es una solución de transmisión lenta, sino que funciona.
También puede escribir su propio método de extensión usando bloques de iteración ( yield return
) que podrían darle mejor rendimiento y usar menos memoria que GroupBy
. Esto es lo que el método Batch()
de MoreLinq hace IIRC.
from b in Enumerable.Range(0,8) select items.Where((x,i) => (i % 8) == b);