C# List - Removing items while looping / iterating [duplicate]
Asked Answered
J

13

58

Suppose that I have the following code snippet:

var data=new List<string>(){"One","Two","Three"};
for(int i=0 ; i<data.Count ; i++){
  if(data[i]=="One"){
    data.RemoveAt(i);
  }
}

The following code throws exception.

My question is what is the best way to avoid this exception and to remove the element while looping?

Janellejanene answered 7/9, 2011 at 21:33 Comment(4)
This compiles for you? There's no Length property on a list.Domeniga
Follow that code very closely and think about what it would really do when you removed one item from it.... and what if the very next item was also supposed to be removed?Servomotor
Hmm really? A foreach would throw an error (you can't change the collection while you are enumerating it). One way around it to not enumerate but loop through it with a for loop like you do in your example (which runs perfectly once you fix the bug data.Length --> data.Count) Little addition: It runs but it is still buggy though since you won't test all elementsMarroquin
If I fix the mistake that you have Lenght instead of Count, your code seems to work fine. (In fact, it does have a bug, but it really shouldn't throw an exception.)Tuddor
A
117

If you need to remove elements then you must iterate backwards so you can remove elements from the end of the list:

var data=new List<string>(){"One","Two","Three"};
for(int i=data.Count - 1; i > -1; i--)
{
    if(data[i]=="One")
    {
        data.RemoveAt(i);
    }
}

However, there are more efficient ways to do this with LINQ (as indicated by the other answers).

Adjacent answered 7/9, 2011 at 21:35 Comment(5)
Or you could iterate forwards and simply not increment the counter if you remove an element. But iterating backwards is probably better.Chilt
I've always heard it to be faster to compare against 0: for(int i=data.Count - 1; i >= 0; i--)Plough
@Plough Only if you would otherwise be evaluating Count every iteration. Generally, if Count() is a method, it will be faster to only evaluate it once, but if it is a field Count there won't be any difference in performance since it's already cached. If it's a property, it depends on whether the property is accessing a field (cached, fast) or calling a method (calculated, slow).Sikko
> there are more efficient ways to do this with LINQ (as indicated by the other answers). - anyone actually measured those indicated by others? I don't think so. Since when LINQ became faster than a for loop? hint: amount of SO points does not amount for better knowledge quite oftenMullens
For large lists, the LINQ may be faster, because RemoveAt is O(n) in the size of the list. So the algorithm is O(n*m), where m is the number of items to remove.Stoichiometry
V
51

You can use List<T>.RemoveAll to handle this:

data.RemoveAll(elem => elem == "One");
Voleta answered 7/9, 2011 at 21:36 Comment(5)
FWIW this is a List<T> operation only.Clubbable
This is good but I need the algorithm that works fine. I understood that looping in reverse manner will not affect the enumerator job and then it will not throw an exceptionJanellejanene
@user, nowhere in your code sample are you using an enumerator.Silica
Enumerator as a concept.Janellejanene
@user931589: You're not enumerating - that's not the problem. THe problem in your code is you go out of bounds. Do you need to use a for loop in ascending order for some reason? What is your actual code doing/trying to do?Voleta
N
12

You can also use a forward moving loop like:

var data = new List<string>() { "One", "Two", "Three", "One", "One", "Four" };
for (int i = 0; i < data.Count; i++)
{
    if (data[i] == "One")
    {
        data.RemoveAt(i--);
    }
}

This line data.RemoveAt(i--); is stopping the effect of increment in the iteration variable at the end of the loop, in case of item being removed from the list.

It will remove the item from index at the current iteration value and then after removing the item, the iterator would be set to one less value than the current one. At the end of loop, the increment in loop body will move it to next valid index.

Here is a working dotfiddle

(Please note, that I personally use reverse loop for situation like these because IMO, they are easier to understand, this answer here is just for displaying another way of achieving it).

Nabal answered 15/4, 2015 at 19:46 Comment(3)
this fires error if "i" is the first element, i mean 0Rosariorosarium
@MertSerimer, why do you think it will throw an exception ? did you try the code ? The code above removes the first element or at index 0, and it shouldn't throw the exception. The reason is that i-- will return 0 and the effect of decrement would be visible on next usage of i. Try the code in VS or in the linked fiddleNabal
Actually, this is nice to go forward sometimes like if you want to update all subsequent elements in the same iteration, feels more natural than thinking backwards.Amie
C
11

I happen to come across a simple solution for this using foreach and .ToArray()

  var data=new List<string>(){"One","Two","Three"};
   foreach ( var d in data.ToArray()){
      if(d =="One"){
        data.Remove(d);
      }
    }
Campground answered 8/7, 2016 at 15:0 Comment(1)
note that .toArray() creates a full copy of the list. So there is an impact on performance and memory consumption when having a heavy list.Cincinnatus
B
4

You could try ChrisF's reverse iteration method to remove your item.

You could also simply:

List.Remove("One");

Or:

List.RemoveAll(i => i == "One"); // removes all instances

And be done with it. There's really no point in iterating over the collection to remove a single item.

Balkan answered 7/9, 2011 at 21:37 Comment(0)
C
3

Why don't you just simply decrement the iterator variable?

var data=new List<string>(){"One","Two","Three"};
for(int i=0 ; i<data.Count ; i++){
  if(data[i]=="One"){
    data.RemoveAt(i);
    i--; // <<<<<<<<<<<
  }
}
Cucurbit answered 26/6, 2014 at 12:7 Comment(3)
I think there would be a bug in i<data.Count evaluation with an emptied ListBlomquist
@Greenlight: Almost a year after my reply the user @Nabal answered pretty much the same. There is also a person indicating an error with sole items and empty lists. Neither that person nor you offer any explanation regarding that claim. Please provide further details, since I can see no error, neither with new List<string>(){"One"} nor with new List<string>(){}.Cucurbit
I checked it again and I think it is correct. Well, I'm not sure, I'm sorry. :)Blomquist
G
1

Here is a dirty trick and I wonder what the critique of it will be?

var data=new List<string>(){"One","Two","Three"};
foreach (string itm in (data.ToArray()))
{
  if string.Compare(name, "one", true) == 0) data.Remove(name);
}
Gilus answered 21/3, 2013 at 2:33 Comment(3)
I used this technique in C++ fairly often- iterate over a copy whilst mutating the source structure. This reminded me of it at an important time.Shirleeshirleen
This has O(n^2) behavior. The Remove() call has to perform another linear lookup for the object you want removed. I would not recommend this technique.Ululate
And, as mentioned in an earlier reply, copies every element.Milklivered
I
1
var data=new List<string>(){"One","Two","Three"};
for(int i=0; i<data.Count; ){
  if(data[i]=="One") data.RemoveAt(i);
  else ++i;
}
Impossibility answered 10/4, 2015 at 21:20 Comment(0)
E
1

The general solution below makes a copy of the list and handles the negative index:

foreach (void item_loopVariable in MyList.ToList) {
    item = item_loopVariable;

}
Eras answered 27/11, 2015 at 14:14 Comment(0)
P
0
        var data = new List<string>() { "One", "Two", "Three" };
        data.RemoveAll(p=>p=="One");
Popedom answered 7/9, 2011 at 21:40 Comment(0)
N
0

You can use Stack class

        Stack<string> myStack = new Stack<string>();

        foreach (var item in Enumerable.Range(1,1001))
            myStack.Push("Str " + item.ToString());

        while (myStack.Any())
            Console.WriteLine("Now {0} items in Stack, removed item is {1}",myStack.Count,myStack.Pop());

        Console.ReadKey();
Nativeborn answered 1/6, 2015 at 7:7 Comment(0)
D
0

I had to remove more than one item from the list. so,I reinitialized the list count. Is there any other better option?

for (int i = dtList.Count - 1; dtList.Count > 0; )
{
     DateTime tempDate = dtList[i].Item1.Date;
     var selectDates = dtList.FindAll(x => x.Item1.Date == tempDate.Date);
     selectDates.Sort((a, b) => a.Item1.CompareTo(b.Item1));
     dtFilteredList.Add(Tuple.Create(selectDates[0].Item1, selectDates[0].Item2));
     dtList.RemoveAll(x => x.Item1.Date == tempDate.Date);
     i = dtList.Count - 1;
}
Downtime answered 5/10, 2015 at 20:34 Comment(1)
You should format your answer.Pylle
M
-2
List<string> list = new List<string>();
     list.Add("sasa");
     list.Add("sames");
     list.Add("samu");
     list.Add("james");
     for (int i = list.Count - 1; i >= 0; i--)
     {

         list.RemoveAt(i);

    }
Meter answered 29/5, 2016 at 7:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.