views:

2604

answers:

14

I have code that I want to look like this:

List<Type> Os;

...

foreach (Type o in Os)
    if (o.cond)
         return;  // quiting early is important for my case!
    else
        Os.Remove(o);

... // other code

This doesn't work because you can't remove from a list inside a foreach over that list:

Is there a common way to solve the problem? I can switch to a different type if needed.

option 2:

List<Type> Os;

...

while (Os.Count != 0)
     if (Os[0].cond)
         return;
     else
         Os.RemoveAt(0);

... // other code

Ugly, but it should work.

+1  A: 

There is a good discussion of this here.

http://social.msdn.microsoft.com/forums/en-US/csharplanguage/thread/8975c43f-c329-4b95-9c42-9261869bf7c3/

They propose:

for(int i = 0; i < count; i++)
{
int elementToRemove = list.Find(<Predicate to find the element>);

list.Remove(elementToRemove);
}
e5
Can you provide an example?
Neil Barnwell
A little overly general (and it needs a check for a failed Find)
BCS
@BCS Only if the list type is non-nullable, neither Find or Remove break on failure, (though calling Remove when the item isn't there is unnecessarily expensive)
rmoore
+11  A: 

You can iterate through the list backwards:

for (int i = myList.Count - 1; i >= 0; i--)
{
    if (whatever) myList.RemoveAt(i);
}


In response to your comment about wanting to quit when you find an item that you're NOT removing, then just using a while loop would be the best solution.

Jon B
I think the first and second example show that the writer doesn't care about the order of the items.
e5
He iterates backward to keep a correct index "i" into the list. If you iterate forward, you need to backtrack once (i--) for every removal to avoid skipping items.
Lucas
doesn't work, I want to quit as soon as I find something that isn't to be removed. Aside from that minor point +1
BCS
This is a good way to remove elements in a list, but in this case it doesn't work because BCS needs to remove from the start, until a condition is met.
Meta-Knight
Yeah, I missed that in his question. The while loop is the way to go.
Jon B
+7  A: 
 Os.RemoveAll(delegate(int x) { return /// });
dirkgently
How does that help? I want o remove only up to the first failed instance.
BCS
Why'd you want a foreach then? Use List.Remove() if all you are bothered about is the first item. Also, the return statements in your sample code look ominous: you'd return without removing the first incorrect item, if it is not the first item in the list. Is that what you want?
dirkgently
Also, can you tell me how this is any different from the top answer?
dirkgently
It's not. I don't think Jon B answers the question either ;-)
Meta-Knight
What I want is to remove items from the list that fit come condition and quit as soon as I find an item that doesn't.
BCS
@BCS: Look again at your sample code, and your comments here -- they are all different when you read thoroughly. The problem specification is poor to begin with, make up your mind on what you want.
dirkgently
+14  A: 

You should never remove anything from a collection you are iterating over while inside of a foreach loop. It's basically like sawing the branch you are sitting on.

Use your while alternative. It is the way to go.

User
Yup, and .NET's kinda brutal about it when you try and alter the List (it throws)
BCS
+1 for the sawing-off-branch metaphor. Very nice.
Carl Manaster
Iterating a list with foreach is like that hot coworker across the room - great to look at, but you just can't touch!
Cristi Diaconescu
A: 

I'd go with dirkgently's method.

Blake Pettersson
then just vote for it
BCS
+1  A: 

If you know your list isn't very large you can use

foreach (Type o in new List<Type>(Os))
    ....

which will create a temporary duplicate of the list. Your remove() call will then not be interfering with the iterator.

Nik
O(n) copy + O(n^2) removes. (Remove(T) is O(n) and I'd do up to n of them)
BCS
Yeah repeated calls to remove() will never win any awards for performance (and will totally dwarf the list copy cost), though it would 'look' similar to how you want it (as you asked). RemoveRange() is your best bet for performance.
Nik
+3  A: 

I'd try finding the index of first item that does not satisfy the predicate and do RemoveRange(0, index) on it. If nothing else, there should be less Remove calls.

bh213
it's a detail but, evaluating `cond` has side effects and I /must/ remove items that pass.
BCS
Isn't that the same solution as Luke?
Meta-Knight
+4  A: 

I just had that problem with my analysis library. I tried this:

for (int i = 0; i < list.Count; i++)
{                
   if (/*condition*/)
   {
       list.RemoveAt(i);
       i--;
   }
}

It's pretty simple but I haven't thought of any breaking point.

Anzurio
good generic solution +1, could have optimized for this specific case
Lucas
+2  A: 

Update: Added for completeness

As several have answered, you shouldn't modify a collection while iterating it with GetEnumerator() (example foreach). The framework prevent you from doing this by throwing an exception. The generic colution to this is to iterate "manually" with for (see other answers). Be careful with your index so you don't skip items or re-evaluate the same one twice (by using i-- or iterating backward).

However, for your specific case, we can optimize the remove operation(s)... original answer below.


If what you want is to remove all items until one meets a given condition (that's what your code does), you can do this:

bool exitCondition;

while(list.Count > 0 && !(exitCondition = list[0].Condition))
   list.RemoveAt(0);

Or if you want to use a single remove operation:

SomeType exitCondition;
int index = list.FindIndex(i => i.Condition);

if(index < 0)
    list.Clear();
else
{
    exitCondition = list[0].State;
    list.RemoveRange(0, count);
}

Note: since I'm assuming that item.Condition is bool, I'm using item.State to save the exit condition.

Update: added bounds checking and saving exit condition to both examples

Lucas
The while loop will run out of bounds but that's easy to fix. Also I'd need a check after it to know what the termination condition was.
BCS
@Lucas, Your second example should also handle the case where no items match the condition, ie, where FindIndex returns -1.
LukeH
it was just sample code, but i will add bounds checking, thanks :)
Lucas
+13  A: 

Do you really need to do this within a foreach loop?

This will achieve the same results as your examples, ie, remove all items from the list up until the first item that matches the condition (or remove all items if none of them match the condition).

int index = Os.FindIndex(x => x.cond);

if (index > 0)
    Os.RemoveRange(0, index);
else if (index == -1)
    Os.Clear();
LukeH
I like that solution, I'll have to think about how it interacts with my details.
BCS
+4  A: 

I am a java programmer, but something like this works.

List<Type> Os;
List<Type> Temp;
...
foreach (Type o in Os)
   if (o.cond)
     Temp.add(o);
Os.removeAll(Temp);
Milhous
I like this - it's cleaner than the backwards iteration solution.
Cristi Diaconescu
A: 

Use an explicit for(Iterator iter=....) loop and then call iter.remove() inside the loop. It's plain old Java, it has been there ever since (well, since 1.2).

for (Iterator iter=myCollection.iterator(); iter.hasNext(); ) {
   Object myObject = iter.next();
   if (someCondition)
      iter.remove();
}
That works for Java (+1) , but I'm working with .NET (-1)
BCS
A: 

Look at Enumerable.SkipWhile()

Enumerable.SkipWhile( x => condition).ToList()

Generally not mutating a list, makes live a lot easier. :)

leppie
A: 

I know you asked for something else, but if you want to conditionally remove a bunch of elements you can use lambda expression:

Os.RemoveAll(o => !o.cond);
Day_Dreamer