views:

17007

answers:

12

In Cocoa, if I want to loop through an NSMutableArray and remove multiple objects that fit a certain criteria, what's the best way to do this without restarting the loop each time I remove an object?

Thanks,

Edit: Just to clarify - I was looking for the best way, e.g. something more elegant than manually updating the index I'm at. For example in C++ I can do;

iterator it = someList.begin();

while (it != someList.end())
{
    if (shouldRemove(it))   
        it = someList.erase(it);
}
+2  A: 

Add the objects you want to remove to a second array and, after the loop, use -removeObjectsInArray:.

Nathan Kinsinger
A: 

Why don't you add the objects to be removed to another NSMutableArray. When you are finished iterating, you can remove the objects that you have collected.

Paul Croarkin
+6  A: 

Either use loop counting down over indices – for (NSInteger i = array.count - 1; i >= 0; --i) – or make a copy with the objects you want to keep. In particular, do not use a for(id object in array) loop or NSEnumerator.

Ahruman
A: 

this should do it:

 NSMutableArray* myArray = ....;

 int i;
 for(i=0; i<[myArray count]; i++) {
  id element = [myArray objectAtIndex:i];
  if(element == ...) {
   [myArray removeObjectAtIndex:i];
   i--;
  }
 }

hope this helps...

Pokot0
not supposed to be mutating the array during iteration
Corey Floyd
Although unorthodox, I've found iterating backwards and deleting as I go to be a clean and simple solution. Usually one of the fastest methods as well.
rpetrich
What's wrong with this? It's clean, fast, easily readable, and it works like a charm. To me it looks like the best answer. Why does it have a negative score? Am I missing something here?
Steph Thirion
@Steph: The question states "something more elegant than manually updating the index."
Steve Madsen
oh. I had missed the I-absolutely-don't-want-to-update-the-index-manually part. thanks steve.IMO this solution is more elegant than the one chosen (no temporary array needed), so negative votes against it feel unfair.
Steph Thirion
@Steve: If you check the edits, that part was added after I have submitted my answer.... if not, I would have replied with "Iterating backwards IS the most elegant solution" :). Have a nice day!
Pokot0
+5  A: 

In a more declarative way, depending on the criteria matching the items to remove you could use:

[theArray filterUsingPredicate:aPredicate]

@Nathan should be very efficient

A: 

How about swapping the elements you want to delete with the 'n'th element, 'n-1'th element and so on?

When you're done you resize the array to 'previous size - number of swaps'

Leonardo Constantino
+30  A: 
Christopher Ashworth
Beware that this could create bugs if objects are more than once in an array. As an alternative you could use an NSMutableIndexSet and -(void)removeObjectsAtIndexes.
Georg
You linked to ridiculousfish.com. You __must__ be right.
bobobobo
+1  A: 

If all objects in your array are unique or you want to remove all occurrences of an object when found, you could fast enumerate on an array copy and use [NSMutableArray removeObject:] to remove the object from the original.

NSMutableArray *myArray;
NSArray *myArrayCopy = [NSArray arrayWithArray:myArray];

for (NSObject *anObject in myArrayCopy) {
 if (shouldRemove(anObject)) {
  [myArray removeObject:anObject];
 }
}
lajos
+7  A: 

Some of the other answers would have poor performance on very large arrays, because methods like removeObject: and removeObjectsInArray: involve doing a linear search of the receiver, which is a waste because you already know where the object is. Also, any call to removeObjectAtIndex: will have to copy values from the index to the end of the array up by one slot at a time.

More efficient would be the following:

NSMutableArray *array = ...
NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];
for (id object in array) {
    if (! shouldRemove(object)) {
        [itemsToKeep addObject:object];
    }
}
[array setArray:itemsToKeep];

Because we set the capacity of itemsToKeep, we don't waste any time copying values during a resize. We don't modify the array in place, so we are free to use Fast Enumeration. Using setArray: to replace the contents of array with itemsToKeep will be efficient. Depending on your code, you could even replace the last line with:

[array release];
array = [itemsToKeep retain];

So there isn't even a need to copy values, only swap a pointer.

benzado
+4  A: 

You can use NSpredicate to remove items from your mutable array. This requires no for loops.

For example if you have an NSMutableArray of names, you can create a predicate like this one:

NSPredicate *caseInsensitiveBNames = 
[NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];

The following line will leave you with an array that contains only names starting with b.

[namesArray filterUsingPredicate:caseInsensitiveBNames];

If you have trouble creating the predicates you need, use this apple developer link.

+9  A: 

One more variation. So you get readability and good performace:

NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet];
SomeObjectClass *item;
NSUInteger index = 0;

for (item in originalArrayOfItems) {
    if ([item shouldBeDiscarded])
        [discardedItems addIndex:index];
    index++;
}

[originalArrayOfItems removeObjectsAtIndexes:discardedItems];
Corey Floyd
A: 

benzado's anwser above is what you should do for preformace. In one of my applications removeObjectsInArray took a running time of 1 minute, just adding to a new array took .023 seconds.