Comment supprimer des éléments d’une liste générique en itérant dessus?

Je cherche un meilleur modèle pour travailler avec une liste d’éléments que chaque besoin a traité et ensuite en fonction du résultat sont retirés de la liste.

Vous ne pouvez pas utiliser .Remove(element) dans un foreach (var element in X) (parce que cela entraîne la 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) car cela perturbe votre position actuelle dans la collection par rapport à i .

Y a-t-il une manière élégante de le faire?

    Itérez votre liste en sens inverse avec une boucle for:

     for (int i = safePendingList.Count - 1; i >= 0; i--) { // some code // safePendingList.RemoveAt(i); } 

    Exemple:

     var list = new List(Enumerable.Range(1, 10)); for (int i = list.Count - 1; i >= 0; i--) { if (list[i] > 5) list.RemoveAt(i); } list.ForEach(i => Console.WriteLine(i)); 

    Vous pouvez également utiliser la méthode RemoveAll avec un prédicat pour tester avec:

     safePendingList.RemoveAll(item => item.Value == someValue); 

    Voici un exemple simplifié pour démontrer:

     var list = new List(Enumerable.Range(1, 10)); Console.WriteLine("Before:"); list.ForEach(i => Console.WriteLine(i)); list.RemoveAll(i => i > 5); Console.WriteLine("After:"); list.ForEach(i => Console.WriteLine(i)); 

    Une solution simple et directe:

    Utilisez un standard for-loop en marche arrière sur votre collection et RemoveAt(i) pour supprimer des éléments.

    L’itération inversée devrait être la première chose à laquelle on pense quand on veut supprimer des éléments d’une collection en itérant dessus.

    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.

     ICollection test = new List(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); foreach (int myInt in test.Reverse()) { if (myInt % 2 == 0) { test.Remove(myInt); } } 
      foreach (var item in list.ToList()) { list.Remove(item); } 

    Si vous ajoutez “.Liste ()” à votre liste (ou aux résultats d’une requête LINQ), vous pouvez supprimer “élément” directement de “liste” sans que la redoutable ” Collection ait été modifiée; l’opération d’énumération peut ne pas s’exécuter “. Erreur. Le compilateur fait une copie de “list”, de sorte que vous pouvez faire le supprimer en toute sécurité sur le tableau.

    Bien que ce modèle ne soit pas très 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 firebase database et le supprimer de la liste uniquement lorsque la sauvegarde de la firebase database réussit.

    L’utilisation du ToArray () sur une liste générique vous permet d’effectuer un Remove (item) sur votre liste générique:

      List ssortingngs = new List() { "a", "b", "c", "d" }; foreach (ssortingng s in ssortingngs.ToArray()) { if (s == "b") ssortingngs.Remove(s); } 

    Sélectionnez les éléments que vous voulez plutôt que d’essayer de supprimer les éléments que vous ne voulez pas . C’est beaucoup plus facile (et généralement plus efficace aussi) que de supprimer des éléments.

     var newSequence = (from el in list where el.Something || el.AnotherThing < 0 select el); 

    Je voulais poster ceci comme un commentaire en réponse au commentaire laissé par Michael Dillon ci-dessous, mais il est trop long et probablement utile d'avoir dans ma réponse de toute façon:

    Personnellement, je ne supprimerais jamais les éléments un par un, si vous devez les supprimer, puis appelez RemoveAll qui prend un prédicat et ne réorganise le tableau interne qu'une seule fois, alors que Remove effectue une opération Array.Copy pour chaque élément supprimé. RemoveAll est beaucoup plus efficace.

    Et lorsque vous effectuez une itération en arrière sur une liste, vous avez déjà l'index de l'élément que vous souhaitez supprimer, il serait donc beaucoup plus efficace d'appeler RemoveAt , car Remove abord une traversée de la liste pour trouver l'index du élément que vous essayez de supprimer, mais vous connaissez déjà cet index.

    Dans l'ensemble, je ne vois aucune raison d'appeler Remove dans un for-loop. Et, idéalement, si cela est possible, utilisez le code ci-dessus pour diffuser des éléments de la liste, si nécessaire, afin de ne pas créer de seconde structure de données.

    Utiliser .ToList () créera 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 effectuez une itération sur une copie.

     foreach (var item in listTracked.ToList()) { if (DetermineIfRequiresRemoval(item)) { listTracked.Remove(item) } } 

    Comme toute suppression est prise sur une condition que vous pouvez utiliser

     list.RemoveAll(item => item.Value == someValue); 

    Si la fonction qui détermine les éléments à supprimer n’a pas d’effets secondaires et ne modifie pas l’élément (c’est une fonction pure), une solution simple et efficace est:

     list.RemoveAll(condition); 

    S’il y a des effets secondaires, j’utiliserai quelque chose comme:

     var toRemove = new HashSet(); foreach(var item in items) { ... if(condition) toRemove.Add(item); } items.RemoveAll(toRemove.Contains); 

    C’est toujours le temps linéaire, en supposant que le hachage est bon. Mais il a une utilisation accrue de la mémoire en raison du hashset.

    Enfin, si votre liste est uniquement un IList au lieu d’une List je suggère ma réponse à Comment puis-je faire cet iterator spécial foreach? . Cela aura un temps d’exécution linéaire avec les implémentations typiques de IList , comparé à l’exécution quadratique de nombreuses autres réponses.

     List TheList = new List(); TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element)); 

    Vous ne pouvez pas utiliser foreach, mais vous pouvez itérer en avant et gérer votre variable d’index de boucle lorsque vous supprimez un élément, comme ceci:

     for (int i = 0; i < elements.Count; i++) { if () { // Decrement the loop counter to iterate this index again, since later elements will get moved down during the remove operation. elements.RemoveAt(i--); } } 

    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 son propre iterator qui permet la suppression d’éléments pendant une boucle foreach.)

    L’utilisation de Remove ou RemoveAt sur une liste lors d’une itération sur cette liste a été intentionnellement rendue difficile, car c’est presque toujours la mauvaise chose à faire . Vous pourriez être en mesure de le faire fonctionner avec un truc intelligent, mais ce serait extrêmement lent. Chaque fois que vous appelez Remove vous devez parcourir la liste entière pour rechercher l’élément à supprimer. Chaque fois que vous appelez RemoveAt il doit déplacer les éléments suivants 1 position vers la gauche. En tant que telle, toute solution utilisant Remove ou RemoveAt nécessiterait un temps quadratique, O (n²) .

    Utilisez RemoveAll si vous le pouvez. Sinon, le modèle suivant filtrera la liste sur place en temps linéaire, O (n) .

     // Create a list to be filtered IList elements = new List(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); // Filter the list int kept = 0; for (int i = 0; i < elements.Count; i++) { // Test whether this is an element that we want to keep. if (elements[i] % 3 > 0) { // Add it to the list of kept elements. elements[kept] = elements[i]; kept++; } } // Unfortunately IList has no Resize method. So instead we // remove the last element of the list until: elements.Count == kept. while (kept < elements.Count) elements.RemoveAt(elements.Count-1); 

    Je souhaite que le “modèle” soit quelque chose comme ceci:

     foreach( thing in thingstack ) { if( /* condition#1 */ ) { foreach.markfordeleting( thing ); } elseif( /* condition#2 */ ) { foreach.markforkeeping( thing ); } } foreachcompleted { // then the programmer's choices would be: // delete everything that was marked for deleting foreach.deletenow(thingstack); // ...or... keep only things that were marked for keeping foreach.keepnow(thingstack); // ...or even... make a new list of the unmarked items others = foreach.unmarked(thingstack); } 

    Cela permettrait d’aligner le code avec le processus qui se passe dans le cerveau du programmeur.

    En supposant que ce prédicat est une propriété booléenne d’un élément, s’il est vrai, l’élément doit être supprimé:

      int i = 0; while (i < list.Count()) { if (list[i].predicate == true) { list.RemoveAt(i); continue; } i++; } 

    Je réaffecterais la liste à partir d’une requête LINQ qui filtrerait les éléments que vous ne souhaitez pas conserver.

     list = list.Where(item => ...).ToList(); 

    À moins que la liste ne soit très volumineuse, cela ne devrait poser aucun problème de performance significatif.

    La meilleure façon de supprimer des éléments d’une liste lors d’une itération est d’utiliser RemoveAll() . Mais la principale préoccupation des personnes est qu’elles doivent faire des choses complexes dans la boucle et / ou avoir des cas de comparaison complexes.

    La solution consiste à toujours utiliser RemoveAll() mais utilisez cette notation:

     var list = new List(Enumerable.Range(1, 10)); list.RemoveAll(item => { // Do some complex operations here // Or even some operations on the items SomeFunction(item); // In the end return true if the item is to be removed. False otherwise return item > 5; }); 
     foreach(var item in list.ToList()) { if(item.Delete) list.Remove(item); } 

    Créez simplement une liste entièrement nouvelle du premier. Je dis “facile” plutôt que “correct” car créer une liste entièrement nouvelle a probablement une prime de performance supérieure à la méthode précédente (je ne me suis pas soucié de toute parsing comparative). Je préfère généralement ce modèle, il peut aussi être utile pour surmonter Limitations de Linq-To-Entities.

     for(i = list.Count()-1;i>=0;i--) { item=list[i]; if (item.Delete) list.Remove(item); } 

    De cette façon, vous faites défiler la liste en arrière avec une ancienne boucle For. Faire cela en avant peut être problématique si la taille de la collection change, mais les retours en arrière doivent toujours être sécurisés.

    Je me suis retrouvé dans une situation similaire où je devais supprimer chaque élément n d’une List donnée List .

     for (int i = 0, j = 0, n = 3; i < list.Count; i++) { if ((j + 1) % n == 0) //Check current iteration is at the nth interval { list.RemoveAt(i); j++; //This extra addition is necessary. Without it j will wrap //down to zero, which will throw off our index. } j++; //This will always advance the j counter } 

    Le coût de la suppression d’un article de la liste est proportionnel au nombre d’éléments suivant celui à supprimer. Dans le cas où la première moitié des éléments sont éligibles à la suppression, toute approche basée sur la suppression individuelle d’éléments devra effectuer environ N * N / 4 opérations de copie d’éléments, ce qui peut devenir très coûteux si la liste est volumineuse. .

    Une approche plus rapide consiste à parcourir la liste pour trouver le premier élément à supprimer (le cas échéant), puis à partir de ce moment-là, à copier chaque élément à conserver à l’endroit auquel 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 à les copier. Par conséquent, si la liste comportait N éléments pour lesquels R éléments, y compris tous les premiers F, étaient conservés, il sera nécessaire de copier des éléments RF et réduire la liste d’un élément NR fois. Tout le temps linéaire

    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:

     var messageList = ...; // Ressortingct your list to certain criteria var customMessageList = messageList.FindAll(m => m.UserId == someId); if (customMessageList != null && customMessageList.Count > 0) { // Create list with positions in origin list List positionList = new List(); foreach (var message in customMessageList) { var position = messageList.FindIndex(m => m.MessageId == message.MessageId); if (position != -1) positionList.Add(position); } // To be able to remove the items in the origin list, we do it backwards // so that the order of indices stays the same positionList = positionList.OrderByDescending(p => p).ToList(); foreach (var position in positionList) { messageList.RemoveAt(position); } } 

    Copiez la liste que vous parcourez. Ensuite, retirez de la copie et entrelacez l’original. Revenir en arrière est déroutant et ne fonctionne pas bien en boucle.

     var ids = new List { 1, 2, 3, 4 }; var iterableIds = ids.ToList(); Parallel.ForEach(iterableIds, id => { ids.Remove(id); }); 
     myList.RemoveAt(i--); simples;