Domanda

    

Questa domanda ha già una risposta qui:

    
            
  •              Split List in sottoliste con LINQ                                      29 risposte                          
  •     
    

Come si potrebbe fare un elenco (utilizzando LINQ) e rompere in una lista di liste partizionamento lista originale su ogni ingresso 8?

immagino qualcosa come questo comporterebbe Skip e / o prendere, ma sono ancora abbastanza nuovo per LINQ.

Edit: con C # / .Net 3.5

Edit2: Questa domanda è formulata in modo diverso rispetto agli altri "duplicato" questione. Anche se i problemi sono simili, le risposte in questa domanda sono superiori: Sia la risposta "accettato" è molto solido (con l'istruzione yield), così come il suggerimento di Jon Skeet utilizzare MoreLinq (che non è raccomandato nel "altro" questione. ) a volte i duplicati sono buone, nel senso che costringono un riesame di un problema.

È stato utile?

Soluzione

Utilizzare il seguente metodo di estensione per rompere l'ingresso in sottoinsiemi

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;
        }
    }
}

Altri suggerimenti

Abbiamo solo un tale metodo nella MoreLINQ come il Batch metodo:

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

o

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

Sei meglio utilizzare una libreria come MoreLinq , ma se proprio doveva fare questo utilizzando "LINQ plain", è possibile utilizzare 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} }

Fondamentalmente, usiamo la versione di Select() che fornisce un indice per il valore consumata, si divide l'indice 8 per identificare quale gruppo ogni valore appartiene. Poi abbiamo gruppo la sequenza da questa chiave di raggruppamento. L'ultima Select riduce solo il IGrouping<> fino a giungere alla IEnumerable<IEnumerable<T>> (e non è strettamente necessaria in quanto IGrouping è un IEnumerable).

E 'abbastanza facile da trasformare questo in un metodo riutilizzabile dal factoring nostro il 8 costante nell'esempio, e la sua sostituzione con un parametro specificato. Non è necessariamente la soluzione più elegante, e non è più una soluzione pigra lo streaming ... ma funziona.

Si potrebbe anche scrivere il proprio metodo di estensione utilizzando iteratore blocchi (yield return) che potrebbero dare migliori prestazioni e utilizzare meno memoria rispetto GroupBy. Questo è ciò che il metodo di Batch() MoreLinq fa IIRC.

Non è affatto quello che i progettisti originali Linq avevano in mente, ma controllare questo uso improprio di 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);
}

penso che vince il premio "Golf" per questa domanda. Il ToList è molto importante in quanto si vuole assicurarsi che il raggruppamento è stato effettivamente eseguita prima di provare a fare qualsiasi cosa con l'uscita. Se si rimuove il ToList, si otterrà alcuni effetti collaterali strano.

Prendere non sarà molto efficace, perché non rimuove le voci prese.

perché non utilizzare un ciclo semplice:

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 soluzione più semplice è dato da 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 ma più lento. Il metodo sopra divide un IEnumerable in blocchi di dimensione fissa desiderata con il numero totale di blocchi è rilevante. Per dividere un IEnumerable in numero N di pezzi di dimensioni pari o vicino al pari dimensioni, si potrebbe fare:

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

Per accelerare le cose, un approccio diretto avrebbe fatto:

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);
}

Questo è più veloce di qualsiasi cosa attualmente sul pianeta ora :) I metodi equivalenti per un Split operazione qui

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top