Liste de partition LINQ en listes de 8 membres

Comment prendrait-on une liste (en utilisant LINQ) et la diviserait-elle en une liste de listes partitionnant la liste originale à chaque 8ème entrée?

J’imagine que quelque chose comme ça impliquerait Skip et / ou Take, mais je suis encore assez nouveau pour LINQ.

Edit: Utilisation de C # / .Net 3.5

Edit2: Cette question est formulée différemment de l’autre question “duplicate”. Bien que les problèmes soient similaires, les réponses à cette question sont supérieures: tant la réponse “acceptée” est très solide (avec la déclaration de yield ) que la suggestion de Jon Skeet d’utiliser MoreLinq (qui n’est pas recommandée dans “l’autre” question). ) Parfois, les doublons sont bons car ils obligent à réexaminer un problème.

Utilisez la méthode d’extension suivante pour diviser l’entrée en sous-ensembles

 public static class IEnumerableExtensions { public static IEnumerable> InSetsOf(this IEnumerable source, int max) { List toReturn = new List(max); foreach(var item in source) { toReturn.Add(item); if (toReturn.Count == max) { yield return toReturn; toReturn = new List(max); } } if (toReturn.Any()) { yield return toReturn; } } } 

Nous avons juste une telle méthode dans MoreLINQ comme méthode par lots :

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

ou

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

Vous feriez mieux d’utiliser une bibliothèque comme MoreLinq , mais si vous deviez vraiment le faire en utilisant “plain LINQ”, vous pouvez utiliser 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} } 

Fondamentalement, nous utilisons la version de Select() qui fournit un index pour la valeur consommée, nous divisons l’index par 8 pour identifier le groupe auquel chaque valeur appartient. Ensuite, nous regroupons la séquence par cette clé de regroupement. Le dernier Select réduit simplement le IGrouping<> à un IEnumerable> (et n’est pas ssortingctement nécessaire car IGrouping est un IEnumerable ).

Il est assez facile de transformer ceci en une méthode réutilisable en factorisant notre constante 8 dans l’exemple et en la remplaçant par un paramètre spécifié. Ce n’est pas forcément la solution la plus élégante et ce n’est plus une solution de streaming paresseuse … mais ça marche.

Vous pouvez également écrire votre propre méthode d’extension à l’aide de blocs d’itération ( yield return ) qui pourraient vous donner de meilleures performances et utiliser moins de mémoire que GroupBy . C’est ce que fait la méthode Batch() de MoreLinq en IIRC.

Ce n’est pas du tout ce que les concepteurs originaux de Linq avaient en tête, mais jetez un coup d’œil à cette mauvaise utilisation de GroupBy:

 public static IEnumerable> BatchBy(this IEnumerable 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); } 

Je pense qu’il gagne le prix “golf” pour cette question. Le ToList est très important car vous voulez vous assurer que le regroupement a bien été effectué avant d’essayer de faire quoi que ce soit avec le résultat. Si vous supprimez la ToList , vous obtiendrez des effets secondaires étranges.

Take ne sera pas très efficace, car il ne supprime pas les entrées sockets.

pourquoi ne pas utiliser une simple boucle:

 public IEnumerable> Partition(this/* <-- see extension methods*/ IEnumerable src,int num) { IEnumerator enu=src.getEnumerator(); while(true) { List result=new List(num); for(int i=0;i0)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 solution la plus simple est donnée par Mel:

 public static IEnumerable> Partition(this IEnumerable items, int partitionSize) { int i = 0; return items.GroupBy(x => i++ / partitionSize).ToArray(); } 

Concis mais plus lent. La méthode ci-dessus divise un IEnumerable en morceaux de taille fixe souhaitée, le nombre total de blocs n’ayant aucune importance. Pour diviser un IEnumerable en N nombres de morceaux de tailles égales ou proches de tailles égales, vous pouvez faire:

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

Pour accélérer les choses, une approche simple ferait:

 public static IEnumerable> Partition(this IEnumerable 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); } 

C’est plus rapide que tout ce qui existe actuellement sur la planète 🙂 Les méthodes équivalentes pour une opération Split ici