Je cherche un meilleur modèle pour travailler avec une liste d'éléments dont chacun a besoin d'être traité, puis en fonction du résultat sont supprimés de la liste.
Vous ne pouvez pas utiliser à l' .Remove(element)
intérieur d'un foreach (var element in X)
(car cela entraîne une Collection was modified; enumeration operation may not execute.
exception) ... vous ne pouvez pas non plus utiliser for (int i = 0; i < elements.Count(); i++)
et.RemoveAt(i)
parce qu'il perturbe votre position actuelle dans la collection par rapport à i
.
Existe-t-il une manière élégante de procéder?
list.RemoveAll(Function(item) item.Value = somevalue)
.size()
au lieu de.Count
et.remove(i)
au lieu de.removeAt(i)
. Intelligent - merci!RemoveAll()
prend trois fois plus de temps que lafor
boucle arrière . Je reste donc fidèle à la boucle, du moins dans les sections où cela compte.Une solution simple et directe:
Utilisez une boucle for for standard fonctionnant à l' envers sur votre collection et
RemoveAt(i)
pour supprimer des éléments.la source
L'itération inverse doit être la première chose à laquelle vous pensez lorsque vous souhaitez supprimer des éléments d'une collection tout en l'itérant.
Heureusement, il existe une solution plus élégante que l'écriture d'une boucle for qui implique une saisie inutile et peut être source d'erreurs.
la source
Reverse<T>()
crée un itérateur qui parcourt la liste en arrière, mais il lui alloue un tampon supplémentaire de même taille que la liste elle-même ( referencesource.microsoft.com/#System.Core/System/Linq/… ).Reverse<T>
ne se contente pas de parcourir la liste d'origine dans l'ordre inverse (sans allouer de mémoire supplémentaire). Par conséquent, les deuxToList()
etReverse()
ont la même consommation de mémoire (les deux créent une copie), maisToList()
ne font rien aux données. AvecReverse<int>()
, je me demande pourquoi la liste est inversée, pour quelle raison.Reverse<T>()
crée un nouveau tampon, je ne suis pas sûr de comprendre pourquoi cela est nécessaire. Il me semble qu'en fonction de la structure sous-jacente de laEnumerable
, il devrait au moins dans certains cas être possible de réaliser une itération inverse sans allouer de mémoire linéaire.Si vous ajoutez ".ToList ()" à votre liste (ou les résultats d'une requête LINQ), vous pouvez supprimer "item" directement de "list" sans que la redoutable " Collection ne soit modifiée; l'opération d'énumération peut ne pas s'exécuter ." Erreur. Le compilateur fait une copie de "list", afin que vous puissiez effectuer la suppression en toute sécurité sur le tableau.
Bien que ce modèle ne soit pas super efficace, il a une sensation naturelle et est suffisamment flexible pour presque toutes les situations . Par exemple, lorsque vous souhaitez enregistrer chaque "élément" dans une base de données et le supprimer de la liste uniquement lorsque la sauvegarde de la base de données a réussi.
la source
Sélectionnez les éléments que vous ne voulez plutôt que d' essayer de supprimer les éléments que vous ne voulez. C'est tellement plus facile (et généralement plus efficace aussi) que de supprimer des éléments.
Je voulais poster ceci en tant que commentaire en réponse au commentaire laissé par Michael Dillon ci-dessous, mais c'est trop long et probablement utile d'avoir dans ma réponse quand même:
Personnellement, je ne supprimerais jamais les éléments un par un, si vous en avez besoin, appelez alors
RemoveAll
qui prend un prédicat et ne réorganise le tableau interne qu'une seule fois, tandisRemove
qu'uneArray.Copy
opération est effectuée pour chaque élément que vous supprimez.RemoveAll
est beaucoup plus efficace.Et lorsque vous parcourez une liste en arrière, vous avez déjà l'index de l'élément que vous souhaitez supprimer, il serait donc beaucoup plus efficace d'appeler
RemoveAt
, carRemove
commence par parcourir la liste pour trouver l'index de l'élément que vous essayez de supprimer, mais vous connaissez déjà cet index.Donc, dans l'ensemble, je ne vois aucune raison d'appeler
Remove
une boucle for. Et idéalement, si cela est possible, utilisez le code ci-dessus pour diffuser des éléments de la liste selon les besoins afin qu'aucune seconde structure de données ne soit créée du tout.la source
L'utilisation de ToArray () sur une liste générique vous permet de faire un Remove (élément) sur votre liste générique:
la source
L'utilisation de .ToList () fera une copie de votre liste, comme expliqué dans cette question: ToList () - crée-t-il une nouvelle liste?
En utilisant ToList (), vous pouvez supprimer de votre liste d'origine, car vous êtes en train d'itérer sur une copie.
la source
Si la fonction qui détermine les éléments à supprimer n'a aucun effet secondaire et ne mute pas l'élément (c'est une fonction pure), une solution simple et efficace (temps linéaire) est:
S'il y a des effets secondaires, j'utiliserais quelque chose comme:
Il s'agit toujours d'un temps linéaire, en supposant que le hachage est bon. Mais il a une utilisation de mémoire accrue en raison du hachage.
Enfin, si votre liste est seulement un
IList<T>
au lieu d'un,List<T>
je suggère ma réponse à Comment puis-je faire cet itérateur spécial foreach? . Cela aura un temps d'exécution linéaire étant donné les implémentations typiques deIList<T>
, par rapport au temps d'exécution quadratique de nombreuses autres réponses.la source
Comme tout retrait est effectué à une condition que vous pouvez utiliser
la source
la source
Vous ne pouvez pas utiliser foreach, mais vous pouvez parcourir en avant et gérer votre variable d'index de boucle lorsque vous supprimez un élément, comme ceci:
Notez qu'en général, toutes ces techniques reposent sur le comportement de la collection en cours d'itération. La technique présentée ici fonctionnera avec la liste standard (T). (Il est tout à fait possible d'écrire votre propre classe de collection et itérateur qui ne permet le retrait de l' article lors d' une boucle foreach.)
la source
Utiliser
Remove
ouRemoveAt
sur une liste tout en parcourant cette liste a été intentionnellement rendu difficile, car ce n'est presque toujours pas la bonne chose à faire . Vous pourriez peut-être le faire fonctionner avec une astuce intelligente, mais ce serait extrêmement lent. Chaque fois que vous appelez,Remove
il doit parcourir toute la liste pour trouver l'élément que vous souhaitez supprimer. Chaque fois que vous appelez,RemoveAt
il doit déplacer les éléments suivants d'une position vers la gauche. Ainsi, toute solution utilisantRemove
ouRemoveAt
, nécessiterait un temps quadratique, O (n²) .Utilisez
RemoveAll
si vous le pouvez. Sinon, le modèle suivant filtrera la liste en place en temps linéaire, O (n) .la source
Je souhaite que le "modèle" soit quelque chose comme ceci:
Cela alignerait le code sur le processus qui se déroule dans le cerveau du programmeur.
la source
Nullable<bool>
, si vous souhaitez autoriser le non marqué), puis utilisez-le après foreach pour supprimer / conserver les éléments.En supposant que le prédicat est une propriété booléenne d'un élément, que s'il est vrai, l'élément doit être supprimé:
la source
Je voudrais réaffecter la liste à partir d'une requête LINQ qui a filtré les éléments que vous ne vouliez pas conserver.
À moins que la liste ne soit très longue, il ne devrait y avoir aucun problème de performances significatif.
la source
La meilleure façon de supprimer des éléments d'une liste tout en itérant dessus est d'utiliser
RemoveAll()
. Mais la principale préoccupation écrite par les gens est qu'ils doivent faire des choses complexes à l'intérieur de la boucle et / ou avoir des cas de comparaison complexes.La solution est de toujours utiliser
RemoveAll()
mais utilisez cette notation:la source
Créez simplement une liste entièrement nouvelle à partir de la première. Je dis "Facile" plutôt que "Droite" car la création d'une liste entièrement nouvelle vient probablement à une performance supérieure à la méthode précédente (je n'ai pas pris la peine de faire un benchmarking.) Je préfère généralement ce modèle, il peut également être utile pour surmonter Limitations de Linq-To-Entities.
De cette façon, vous parcourez la liste en arrière avec une ancienne boucle For. Faire cela en avant pourrait être problématique si la taille de la collection change, mais en arrière devrait toujours être sûr.
la source
Copiez la liste que vous parcourez. Retirez ensuite de la copie et intérez l'original. Revenir en arrière est déroutant et ne fonctionne pas bien lors d'une boucle en parallèle.
la source
Je voudrais faire ça
PRODUCTION
la source
Je me suis retrouvé dans une situation similaire où je devais supprimer chaque nième élément dans une donnée
List<T>
.la source
Le coût de la suppression d'un élément de la liste est proportionnel au nombre d'éléments suivant celui à supprimer. Dans le cas où la première moitié des éléments peut être supprimée, toute approche basée sur la suppression individuelle des éléments finira par devoir effectuer environ N * N / 4 opérations de copie d'élément, ce qui peut devenir très coûteux si la liste est longue. .
Une approche plus rapide consiste à parcourir la liste pour trouver le premier élément à supprimer (le cas échéant), puis à partir de ce moment, copier chaque élément qui doit être conservé à l'endroit où il appartient. Une fois cela fait, si les éléments R doivent être conservés, les premiers éléments R de la liste seront ces éléments R, et tous les éléments nécessitant une suppression seront à la fin. Si ces éléments sont supprimés dans l'ordre inverse, le système n'aura pas à en copier aucun, donc si la liste avait N éléments dont R éléments, y compris tous les premiers F, ont été conservés, il sera nécessaire de copiez les éléments RF et réduisez la liste d'un élément NR fois. Tout le temps linéaire.
la source
Mon approche est que je crée d'abord une liste d'indices, qui devraient être supprimés. Ensuite, je passe en revue les index et supprime les éléments de la liste initiale. Cela ressemble à ceci:
la source
En C #, un moyen simple consiste à marquer ceux que vous souhaitez supprimer puis à créer une nouvelle liste à parcourir ...
ou encore plus simple utiliser linq ....
mais il vaut la peine de se demander si d'autres tâches ou threads auront accès à la même liste en même temps que vous êtes occupé à les supprimer, et peut-être utiliser une ConcurrentList à la place.
la source
Tracez les éléments à supprimer avec une propriété et supprimez-les tous après le processus.
la source
Je voulais juste ajouter mes 2 cents à cela au cas où cela aiderait quelqu'un, j'avais un problème similaire, mais je devais supprimer plusieurs éléments d'une liste de tableaux pendant qu'elle était itérée. la réponse la plus élevée a fait le plus pour moi jusqu'à ce que je rencontre des erreurs et que je réalise que l'index était supérieur à la taille de la liste de tableaux dans certains cas parce que plusieurs éléments étaient supprimés mais que l'index de la boucle ne tenait pas suivre cela. J'ai corrigé cela avec une simple vérification:
la source
la source
simples;
-on ici?