2010-09-22 9 views
35

Come si può prendere una lista (usando LINQ) e dividerla in una lista di liste partizionando l'elenco originale ogni 8 voci?LINQ Elenco partizioni in liste di 8 membri

Immagino che qualcosa del genere implicherebbe Skip e/o Take, ma sono ancora piuttosto nuovo per LINQ.

Edit: con C#/.Net 3.5

Edit2: Questa domanda è formulata in modo diverso rispetto l'altra domanda "duplicato". Anche se i problemi sono simili, le risposte in questa domanda sono superiori: sia la risposta "accettata" è molto solida (con la dichiarazione yield) sia il suggerimento di Jon Skeet di usare MoreLinq (che non è raccomandato nella domanda "altro".) A volte i duplicati sono buoni in quanto costringono a riesaminare un problema.

+0

Stai utilizzando VB o C#? La presenza di iteratori fa una grande differenza. –

+1

Questo non è un duplicato. L'altra domanda voleva a rompere la lista in sottolisti di ogni n-esimo elemento, quindi una lista con gli elementi 0, 8, 16, 24, ecc. E una lista con gli elementi 1, 9, 17, 25, ecc. E una lista con gli elementi 2, 10, 18, ecc. Questo utente vuole entrare in una lista con 0..7 e una lista con 8..15 e una lista con 16..24, simile al paging –

risposta

46

Usare 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; 
     } 
    } 
} 
+0

Ho intenzione di provarlo ora perché questo sembra abbastanza intelligente ... Il pensiero di "rendimento" mi è saltato in mente mentre ripensavo a questo, ma non riuscivo a vedere un modo chiaro per farlo ... I ' Ti farò sapere come funziona per me. – Pretzel

+0

Wow! E 'davvero figo. Sto andando con questo! Grazie per l'aiuto! :-) – Pretzel

36

Abbiamo solo un tale metodo in MoreLINQ come metodo Batch:

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

o

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

Cool, questo è implementato molto bene, incluso un risultatoSelector (importante per manipolare/ordinare la lista interna). –

+0

Uff. Pensavo che forse ero un po 'stupido nel non riuscire a capirlo. È bello vedere che ci sono alcune cose che "mancano" dai normali LINQ agli Oggetti. :) – Pretzel

+0

@Pretzel: Non è che questo sia impossibile usando il vecchio LINQ ... è solo che non è né troppo efficiente né facile da capire. Vedi la mia risposta per un esempio di "semplice LINQ". – LBushkin

15

È meglio usare una libreria lik e MoreLinq, ma se proprio dovessi farlo usando "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} } 

In sostanza, si usa la versione di Select() che fornisce un indice per il valore di essere consumato, dividiamo l'indice per 8 per identificare a quale gruppo appartiene ciascun valore. Quindi raggruppiamo la sequenza con questa chiave di raggruppamento. L'ultimo Select riduce lo IGrouping<> in uno IEnumerable<IEnumerable<T>> (e non è strettamente necessario poiché IGrouping è un IEnumerable).

È abbastanza semplice trasformarlo in un metodo riutilizzabile prendendo in considerazione la costante 8 nell'esempio e sostituendolo con un parametro specificato. Non è necessariamente la soluzione più elegante e non è più una soluzione di streaming pigra ... ma funziona.

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

+0

Grazie per il tuo contributo. Sì, non sembra efficiente e, come puoi immaginare, stavo cercando di capire come potevo farlo con LINQ regolare. (Sto fissando la tua risposta in questo momento e davvero non la capisco molto bene.) Dovrò giocherellarci più tardi. (Grazie ancora!) – Pretzel

+2

L'approccio usando 'GroupBy()' si interrompe se la sequenza che stai pianificando per il batching sarà estremamente grande (o infinita). Per quanto riguarda il suo funzionamento, crea un oggetto anonimo che associa ogni elemento al suo indice, quindi lo raggruppa in una serie di sequenze basate sulla divisibilità per '8' (o qualsiasi altra costante diversa da zero). – LBushkin

0

Take non sarà molto efficiente, perché non rimuove le voci prese.

perché non utilizzare un semplice ciclo:

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

E 'non è quello che i progettisti originali Linq avevano in mente, ma il check-out questo abuso 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. Lo ToList è molto importante poiché si desidera assicurarsi che il raggruppamento sia stato effettivamente eseguito prima di provare a fare qualcosa con l'output.Se rimuovi il ToList, otterrai alcuni strani effetti collaterali.

+0

Per la cronaca, la versione basata su "yield return" di Handcraftsman si comporta molto meglio, ma mi piace ancora l'aspetto "Hey, non dovresti farlo" di questo codice. – Mel

+0

-1 Questa risposta è sbagliata. Se si sostituisce l'operatore divisione per modulo, si ottiene il numero di partizioni batchSize più adatto per questa discussione http://stackoverflow.com/questions/438188/split-a-collection-into-n-parts-with-linq – nawfal

+0

Non sono d'accordo . Se si utilizza modulo, si assegnano gli elementi ai gruppi per il resto. Otterrai l'articolo 0 nel gruppo 0, l'elemento 1 nel gruppo 1, ecc. Ciò confonderebbe completamente il loro ordine. Usando la divisione intera, come ho fatto qui, significa che gli elementi 0-2 vanno nel gruppo 0, 3-5 vanno nel gruppo 1, ecc. Credo che questo sia ciò che era inteso. Se sei d'accordo, rimuovi il -1 dalla mia risposta. – Mel

0
from b in Enumerable.Range(0,8) select items.Where((x,i) => (i % 8) == b); 
+0

+1 Notate questo gruppo ogni 8 articoli, ovvero ottieni {0,8,16}, {1,9,17}, ... –

+0

Questo è più sulla suddivisione che sul partizionamento - più adatto [qui] (http: //stackoverflow.com/questions/438188/split-a-collection-into-n-parts-with-linq) – nawfal

+0

Inoltre, potrebbe restituire oggetti IEnumerables interni vuoti se il numero 8 è maggiore del numero di elementi in 'items' che potrebbero o potrebbe non essere desiderabile. Ho incorporato la tua risposta nell'altra discussione comunque .. – nawfal

0

La soluzione più semplice è data 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 suddetto metodo divide un oggetto IEnumerable in blocchi di dimensioni fisse desiderate con un numero totale di blocchi non importante. 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 farebbe:

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'operazione Splithere

+0

whew, è molto più veloce! anche se http://stackoverflow.com/a/4835541/1037948 ha iniziato a eliminarti dopo un paio di esecuzioni in linqpad ...;) – drzaus

+0

@drzaus tieni presente che quella risposta è una costruzione pericolosa con effetti collaterali. Poiché le corse interne ed esterne sullo stesso enumeratore, ottieni risultati che non ti aspetti. Se si enumera solo la sequenza esterna, le sequenze interne non sono più valide; oppure non è possibile ripetere le sequenze interne due volte. O semplicemente prova 'var x = returnedResult.ElementAt (n) .ToList();', ottieni risultati imprevisti. – nawfal

+0

Quindi di recente ho provato a eseguire nuovamente alcuni confronti perf ([test + risultati qui] (https://gist.github.com/zaus/399ca9081912bd6efd7f#file-f2-results-md) tra il solo chiamare questo metodo e fare qualcosa con i risultati, e non era così veloce rispetto ad alcuni degli altri suggerimenti. Mi sto perdendo qualcosa? – drzaus

Problemi correlati