Existe-t-il un moyen de séparer un List<SomeObject>
en plusieurs listes distinctes de SomeObject
, en utilisant l'index des éléments comme délimiteur de chaque division?
Permettez-moi d'illustrer:
J'ai un List<SomeObject>
et j'ai besoin d'un List<List<SomeObject>>
ou List<SomeObject>[]
, de sorte que chacune de ces listes résultantes contienne un groupe de 3 éléments de la liste d'origine (séquentiellement).
par exemple.:
Liste originale:
[a, g, e, w, p, s, q, f, x, y, i, m, c]
Listes résultantes:
[a, g, e], [w, p, s], [q, f, x], [y, i, m], [c]
J'aurais également besoin que la taille des listes résultantes soit un paramètre de cette fonction.
la source
[a,g,e]
avant d'énumérer davantage la liste d'origine.GroupBy(x=>f(x)).First()
ne donnera jamais un groupe. OP a posé des questions sur les listes, mais si nous écrivons pour travailler avec IEnumerable, en ne faisant qu'une seule itération, nous récoltons l'avantage des performances.Cette question est un peu ancienne, mais je viens de l'écrire, et je pense qu'elle est un peu plus élégante que les autres solutions proposées:
la source
if (chunksize <= 0) throw new ArgumentException("Chunk size must be greater than zero.", "chunksize");
O(n²)
. Vous pouvez parcourir la liste et obtenir unO(n)
temps.source
est remplacé par un enveloppé àIEnumerable
chaque fois.source
Skip
En général, l'approche suggérée par CaseyB fonctionne bien, en fait, si vous passez dans un,
List<T>
il est difficile de le blâmer, je le changerais peut-être en:Ce qui évitera des chaînes d'appel massives. Néanmoins, cette approche présente un défaut général. Il matérialise deux énumérations par bloc, pour mettre en évidence le problème en cours d'exécution:
Pour surmonter cela, nous pouvons essayer l' approche de Cameron , qui réussit le test ci-dessus avec brio car elle ne parcourt l'énumération qu'une seule fois.
Le problème est qu'il a un défaut différent, il matérialise chaque élément de chaque morceau, le problème avec cette approche est que vous manquez de mémoire.
Pour illustrer cela, essayez de lancer:
Enfin, toute implémentation devrait être capable de gérer l'itération dans le désordre des morceaux, par exemple:
De nombreuses solutions hautement optimales comme ma première révision de cette réponse ont échoué là-bas. Le même problème peut être vu dans la réponse optimisée de casperOne .
Pour résoudre tous ces problèmes, vous pouvez utiliser les éléments suivants:
Il existe également une série d'optimisations que vous pourriez introduire pour une itération hors service des morceaux, ce qui est hors de portée ici.
Quant à quelle méthode choisir? Cela dépend totalement du problème que vous essayez de résoudre. Si vous n'êtes pas concerné par le premier défaut, la réponse simple est incroyablement attrayante.
Notez que comme avec la plupart des méthodes, ce n'est pas sûr pour le multi-threading, les choses peuvent devenir bizarres si vous souhaitez le rendre sûr pour le thread, vous devrez le modifier
EnumeratorWrapper
.la source
Vous pourriez utiliser un certain nombre de requêtes qui utilisent
Take
etSkip
, mais cela ajouterait trop d'itérations sur la liste d'origine, je crois.Je pense plutôt que vous devriez créer votre propre itérateur, comme ceci:
Vous pouvez ensuite appeler cela et il est activé LINQ afin que vous puissiez effectuer d'autres opérations sur les séquences résultantes.
À la lumière de la réponse de Sam , j'ai senti qu'il y avait un moyen plus facile de le faire sans:
Cela dit, voici un autre passage, que j'ai codifié dans une méthode d'extension à
IEnumerable<T>
appelerChunk
:Rien de surprenant là-haut, juste une vérification des erreurs de base.
Passons à
ChunkInternal
:Fondamentalement, il obtient le
IEnumerator<T>
et itère manuellement chaque élément. Il vérifie s'il y a actuellement des éléments à énumérer. Après l'énumération de chaque morceau, s'il ne reste aucun élément, il éclate.Une fois qu'il détecte qu'il y a des éléments dans la séquence, il délègue la responsabilité de l'
IEnumerable<T>
implémentation interne àChunkSequence
:Comme
MoveNext
a déjà été appelé sur leIEnumerator<T>
passé àChunkSequence
, il renvoie l'élément renvoyé parCurrent
, puis incrémente le nombre, en veillant à ne jamais renvoyer plus que leschunkSize
éléments et en passant à l'élément suivant dans la séquence après chaque itération (mais court-circuité si le nombre de les articles produits dépassent la taille des morceaux).S'il ne reste aucun élément, la
InternalChunk
méthode effectuera une autre passe dans la boucle externe, mais lorsqu'elleMoveNext
est appelée une deuxième fois, elle retournera toujours false, selon la documentation (c'est moi qui souligne):À ce stade, la boucle se rompra et la séquence de séquences se terminera.
Ceci est un test simple:
Production:
Remarque importante, cela ne fonctionnera pas si vous ne videz pas la séquence enfant entière ou ne vous interrompez à aucun moment de la séquence parent. Ceci est une mise en garde importante, mais si votre cas d'utilisation est que vous consommerez chaque élément de la séquence de séquences, alors cela fonctionnera pour vous.
De plus, cela fera des choses étranges si vous jouez avec l'ordre, tout comme Sam l'a fait à un moment donné .
la source
List<T>
, vous allez évidemment avoir des problèmes de mémoire à cause de la mise en mémoire tampon. Rétrospectivement, j'aurais dû le noter dans la réponse, mais il semblait à l'époque que l'accent était mis sur trop d'itérations. Cela dit, votre solution est en effet plus velue. Je ne l'ai pas testé, mais maintenant, je me demande s'il existe une solution moins velue.Ok, voici mon point de vue:
Exemple d'utilisation
Explications
Le code fonctionne en imbriquant deux
yield
itérateurs basés.L'itérateur externe doit garder une trace du nombre d'éléments qui ont été effectivement consommés par l'itérateur interne (bloc). Cela se fait en terminant
remaining
avecinnerMoveNext()
. Les éléments non consommés d'un bloc sont rejetés avant que le bloc suivant ne soit fourni par l'itérateur externe. Ceci est nécessaire car sinon vous obtenez des résultats incohérents, lorsque les énumérables internes ne sont pas (complètement) consommés (par exemplec3.Count()
renverraient 6).la source
complètement paresseux, sans compter ni copier:
la source
Je pense que la suggestion suivante serait la plus rapide. Je sacrifie la paresse de la source Enumerable pour pouvoir utiliser Array.Copy et connaître à l'avance la longueur de chacune de mes sous-listes.
la source
Nous pouvons améliorer la solution de @ JaredPar pour faire une véritable évaluation paresseuse. Nous utilisons une
GroupAdjacentBy
méthode qui produit des groupes d'éléments consécutifs avec la même clé:Étant donné que les groupes sont générés un par un, cette solution fonctionne efficacement avec des séquences longues ou infinies.
la source
J'ai écrit une méthode d'extension Clump il y a plusieurs années. Fonctionne très bien et est la mise en œuvre la plus rapide ici. : P
la source
System.Interactive prévoit
Buffer()
à cet effet. Certains tests rapides montrent que les performances sont similaires à la solution de Sam.la source
Buffer()
renvoieIEnumerable<IList<T>>
donc oui, vous auriez probablement un problème là-bas - il ne diffuse pas comme le vôtre.Voici une routine de fractionnement de liste que j'ai écrite il y a quelques mois:
la source
Je trouve que ce petit extrait fait très bien l'affaire.
la source
Et celui-ci?
Pour autant que je sache, GetRange () est linéaire en termes de nombre d'éléments pris. Cela devrait donc bien fonctionner.
la source
C'est une vieille question mais c'est ce avec quoi j'ai fini; il énumère l'énumérable une seule fois, mais crée des listes pour chacune des partitions. Il ne souffre pas d'un comportement inattendu lorsqu'il
ToArray()
est appelé comme le font certaines implémentations:la source
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int chunkSize)
Nous avons trouvé que la solution de David B fonctionnait le mieux. Mais nous l'avons adapté à une solution plus générale:
la source
Cette solution suivante est la plus compacte que j'ai pu trouver, c'est O (n).
la source
Ancien code, mais voici ce que j'utilise:
la source
Si la liste est de type system.collections.generic, vous pouvez utiliser la méthode "CopyTo" disponible pour copier des éléments de votre tableau vers d'autres sous-tableaux. Vous spécifiez l'élément de départ et le nombre d'éléments à copier.
Vous pouvez également créer 3 clones de votre liste d'origine et utiliser le "RemoveRange" sur chaque liste pour réduire la liste à la taille souhaitée.
Ou créez simplement une méthode d'aide pour le faire pour vous.
la source
C'est une vieille solution mais j'avais une approche différente. J'utilise
Skip
pour passer à l'offset souhaité etTake
extraire le nombre d'éléments souhaité:la source
Pour toute personne intéressée par une solution packagée / maintenue, la bibliothèque MoreLINQ fournit la
Batch
méthode d'extension qui correspond à votre comportement demandé:L'
Batch
implémentation est similaire à la réponse de Cameron MacFarland , avec l'ajout d'une surcharge pour transformer le bloc / lot avant de revenir, et fonctionne assez bien.la source
Utilisation du partitionnement modulaire:
la source
Je mets juste mes deux cents. Si vous vouliez "regrouper" la liste (visualiser de gauche à droite), vous pouvez procéder comme suit:
la source
Une autre façon utilise l' opérateur Rx Buffer
la source
la source
J'ai pris la réponse principale et en ai fait un conteneur du CIO pour déterminer où diviser. ( Pour qui cherche vraiment à ne diviser que sur 3 éléments, en lisant ce post tout en recherchant une réponse? )
Cette méthode permet de diviser sur n'importe quel type d'élément selon les besoins.
Donc, pour l'OP, le code serait
la source
Aussi performatique que l' approche de Sam Saffron .
}
la source
Peut fonctionner avec des générateurs infinis:
Code de démonstration: https://ideone.com/GKmL7M
Mais en fait, je préférerais écrire la méthode correspondante sans linq.
la source
Regarde ça! J'ai une liste d'éléments avec un compteur de séquence et une date. Pour chaque redémarrage de la séquence, je souhaite créer une nouvelle liste.
Ex. liste des messages.
Je souhaite diviser la liste en listes distinctes au redémarrage du compteur. Voici le code:
la source
Pour insérer mes deux cents ...
En utilisant le type de liste pour la source à fragmenter, j'ai trouvé une autre solution très compacte:
la source