LINQ Partition List in elenchi di 8 membri [duplicato]
-
04-10-2019 - |
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.
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
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