Pregunta

Esta pregunta ya tiene una respuesta aquí:

¿Cómo se podría tomar una lista (utilizando LINQ) y dividirlo en una lista de listas partición de la lista original de cada entrada octava?

Me imagino algo como esto implicaría Saltar y / o tomar, pero todavía estoy bastante nuevo en LINQ.

Editar: El uso de C # / Net 3.5

Edit2: Esta pregunta se formula de manera diferente que el otro "duplicar" cuestión. A pesar de que los problemas son similares, las respuestas en esta pregunta son superiores: Tanto la respuesta "aceptada" es muy sólida (con la declaración yield), así como la sugerencia de Jon Skeet utilizar MoreLinq (que no se recomienda en la "otra" cuestión. ) a veces duplicados son buenas, pues obliga a un nuevo examen de un problema.

¿Fue útil?

Solución

Utiliza el siguiente método de extensión para romper 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;
        }
    }
}

Otros consejos

Tenemos sólo un método tal en MoreLINQ como el batch método:

// As IEnumerable<IEnumerable<T>>
var items = list.Batch(8);

o

// As IEnumerable<List<T>>
var items = list.Batch(8, seq => seq.ToList());

Usted es mejor usar una biblioteca como MoreLinq , pero si realmente tenía que hacer este uso de "LINQ normal", puede utilizar 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, se utiliza la versión de Select() que proporciona un índice para el valor de ser consumido, dividimos el índice por 8 para identificar qué grupo cada valor pertenece. Luego grupo de la secuencia por esta clave de agrupación. La última Select solo reduce el IGrouping<> hasta una IEnumerable<IEnumerable<T>> (y no es estrictamente necesario, ya que es un IGrouping IEnumerable).

Es bastante fácil de convertir esto en un método reutilizable mediante la factorización nuestra 8 la constante en el ejemplo, y su sustitución por un parámetro especificado. No es necesariamente la solución más elegante, y ya no es una solución perezosa, streaming ... pero funciona.

También puede escribir su propio método de extensión utilizando iterador bloques (yield return), que podrían darle un mejor rendimiento y usar menos memoria que GroupBy. Esto es lo que el método de Batch() MoreLinq hace IIRC.

No es en absoluto lo que los diseñadores originales de Linq tenían en mente, pero echa un vistazo a este mal uso 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 "campo" para esta pregunta. El ToList es muy importante, ya que desea asegurarse de que la agrupación de hecho se ha realizado antes de intentar hacer cualquier cosa con la salida. Si se quita la ToList, obtendrá algunos efectos secundarios raros.

Take no será muy eficiente, ya que no elimina las entradas tomadas.

¿por qué no utilizar 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;  
    }  
}
from b in Enumerable.Range(0,8) select items.Where((x,i) => (i % 8) == b);

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 una IEnumerable en trozos de tamaño fijo deseado con el número total de trozos carece de importancia. Para dividir un IEnumerable en número N de trozos de igual tamaño o cerca de igual tamaño, se puede 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 harí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 aquí

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top