198

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);
}
Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
Andrew Grant
  • 58,260
  • 22
  • 130
  • 143

20 Answers20

392

For clarity I like to make an initial loop where I collect the items to delete. Then I delete them. Here's a sample using Objective-C 2.0 syntax:

NSMutableArray *discardedItems = [NSMutableArray array];

for (SomeObjectClass *item in originalArrayOfItems) {
    if ([item shouldBeDiscarded])
        [discardedItems addObject:item];
}

[originalArrayOfItems removeObjectsInArray:discardedItems];

Then there is no question about whether indices are being updated correctly, or other little bookkeeping details.

Edited to add:

It's been noted in other answers that the inverse formulation should be faster. i.e. If you iterate through the array and compose a new array of objects to keep, instead of objects to discard. That may be true (although what about the memory and processing cost of allocating a new array, and discarding the old one?) but even if it's faster it may not be as big a deal as it would be for a naive implementation, because NSArrays do not behave like "normal" arrays. They talk the talk but they walk a different walk. See a good analysis here:

The inverse formulation may be faster, but I've never needed to care whether it is, because the above formulation has always been fast enough for my needs.

For me the take-home message is to use whatever formulation is clearest to you. Optimize only if necessary. I personally find the above formulation clearest, which is why I use it. But if the inverse formulation is clearer to you, go for it.

vellvisher
  • 484
  • 4
  • 12
Christopher Ashworth
  • 4,324
  • 1
  • 18
  • 11
  • 48
    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 Schölly Jun 19 '09 at 20:47
  • 1
    It actually is faster to do the inverse. The performance difference is pretty huge, but if your array isn't that big then the times are going to be trivial either way. – user1032657 Jun 03 '13 at 21:16
  • I used revers... made array as nil.. then only added what I wanted and reload data... done... **created dummyArray which is mirror of main array so that we have main actual data** – Fahim Parkar Nov 13 '13 at 11:45
  • Extra memory needs to be allocated to do inverse. It's not an in-place algorithm and not a good idea in some cases. When you remove directly you just shift the array (which is very efficient since it won't move one by one, it can shift a big chunk), but when you create a new array you need all the allocate and assignment. So if you only delete a few items, inverse will be much slower. It's a typical seems-right algorithm. – jack Sep 17 '14 at 07:05
83

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
  • 25,929
  • 31
  • 126
  • 154
  • This is awesome. I was trying to remove multiple items from the array and this worked perfectly. Other methods were causing problems :) Thanks man – AbhinavVinay Mar 12 '13 at 07:15
  • Corey, [this answer](http://stackoverflow.com/a/16905821/1603234) (below) said that, `removeObjectsAtIndexes` is the worst method to remove the objects, are you agree with that? I am asking this because your answer is too old now. Still it be good to choose the best? – Hemang Jul 03 '13 at 07:48
  • I like this solution very much in terms of readability. How is it in terms of performance when compared to @HotLicks answer? – Victor Maia Aldecôa Jan 27 '14 at 23:00
  • This method should be definitely encouraged while deleting objects from array in Obj-C. – boreas May 12 '16 at 15:57
  • `enumerateObjectsUsingBlock:` would get you the index increment for free. – pkamb Aug 21 '18 at 20:10
48

This is a very simple problem. You just iterate backwards:

for (NSInteger i = array.count - 1; i >= 0; i--) {
   ElementType* element = array[i];
   if ([element shouldBeRemoved]) {
       [array removeObjectAtIndex:i];
   }
}

This is a very common pattern.

ChrisF
  • 134,786
  • 31
  • 255
  • 325
Hot Licks
  • 47,103
  • 17
  • 93
  • 151
  • would have missed Jens answer, cuz he didnt write out the code :P.. thnx m8 – Just a coder Oct 01 '13 at 23:33
  • 3
    This is the best method. Important to remember that the iteration variable should be a signed one, despite that array indices in Objective-C are declared as unsigned (I can imagine how Apple regrets that now). – mojuba Nov 17 '16 at 10:45
40

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
  • 82,288
  • 22
  • 110
  • 138
  • This algo proves good to be in terms of time complexity. I am trying to understand it in terms of space complexity. I have the same implementation going around where I set the capacity for 'itemsToKeep' with the actual 'Array count'. But say I don't want few objects from array so I don't add it to itemsToKeep. So that capacity of my itemsToKeep is 10, but I actually store only 6 objects in it. Does this mean I am wasting space of 4 objects? P.S. am not finding faults, just trying to understand the algo complexity. :) – tech_human Jan 07 '14 at 20:30
  • Yes, you have space reserved for four pointers that you are not using. Keep in mind the array only contains pointers, not the objects themselves, so for four objects that means 16 bytes (32 bit architecture) or 32 bytes (64 bit). – benzado Jan 08 '14 at 21:01
  • Note that any of the solutions are going to be at best require 0 additional space (you delete items in place) or at worst double the original array size (because you make a copy of the array). Again, because we are dealing with pointers and not full copies of objects, this is pretty cheap under most circumstances. – benzado Jan 08 '14 at 21:03
  • According to [this](http://www.objc.io/issue-7/collections.html) post, the hint of the arrayWithCapacity: method about the size of the array is not actually being used. – Kremk Jan 27 '14 at 00:03
  • @Kremk On the road, yellow paint is powerless to stop oncoming traffic from entering your lane. But we use it all the same. – benzado Jan 28 '14 at 01:48
  • I am not advising against using arrayWithCapacity: as it is indeed helpful for understanding the code :) . What I am saying is that from what I understand, using it does not seem to prevent wasting time copying values during a resize as you suggested. – Kremk Jan 29 '14 at 12:00
  • This is not a good solution. Allocate extra memory and do assignment are much slower than remove and shift array. – jack Sep 17 '14 at 07:15
28

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.

19

I did a performance test using 4 different methods. Each test iterated through all elements in a 100,000 element array, and removed every 5th item. The results did not vary much with/ without optimization. These were done on an iPad 4:

(1) removeObjectAtIndex: -- 271 ms

(2) removeObjectsAtIndexes: -- 1010 ms (because building the index set takes ~700 ms; otherwise this is basically the same as calling removeObjectAtIndex: for each item)

(3) removeObjects: -- 326 ms

(4) make a new array with objects passing the test -- 17 ms

So, creating a new array is by far the fastest. The other methods are all comparable, except that using removeObjectsAtIndexes: will be worse with more items to remove, because of the time needed to build the index set.

user1032657
  • 2,451
  • 6
  • 28
  • 38
  • 1
    Did you count the time to make a new array and deallocate it afterwards. I don't believe it can do that alone in 17 ms. Plus 75,000 assignment? – jack Sep 17 '14 at 07:35
  • The time needed to create and deallocate a new array is minimal – user1032657 Sep 19 '14 at 03:52
  • You apparently didn't measure the reverse loop scheme. – Hot Licks Apr 16 '15 at 16:27
  • The first test is equivalent to the reverse loop scheme. – user1032657 May 08 '15 at 21:44
  • what happens when you are left with your newArray, and your prevArray is Nulled? You'd have to copy the newArray to what the PrevArray was named to ( or rename it ) otherwise how would your other code reference it? – aremvee Aug 10 '15 at 03:24
17

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.

pkamb
  • 33,281
  • 23
  • 160
  • 191
Jens Ayton
  • 14,532
  • 3
  • 33
  • 47
  • 3
    you should write out your answer in code m8. haha almost missed it. – Just a coder Oct 01 '13 at 23:33
  • This is not right. "for(id object in array)" is faster than "for (NSInteger i = array.count - 1; i >= 0; --i)", and is called fast iteration. Using iterator is definitely faster than indexing. – jack Sep 17 '14 at 07:31
  • @jack -- But if you remove an element out from under an iterator you usually create havoc. (And "fast iteration" is not always that much faster.) – Hot Licks Sep 17 '14 at 15:45
  • The answer is from 2008. Fast iteration did not exist. – Jens Ayton Sep 22 '14 at 16:13
13

For iOS 4+ or OS X 10.6+, Apple added passingTest series of APIs in NSMutableArray, like – indexesOfObjectsPassingTest:. A solution with such API would be:

NSIndexSet *indexesToBeRemoved = [someList indexesOfObjectsPassingTest:
    ^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self shouldRemove:obj];
}];
[someList removeObjectsAtIndexes:indexesToBeRemoved];
zavié
  • 4,301
  • 2
  • 34
  • 46
12

Nowadays you can use reversed block-based enumeration. A simple example code:

NSMutableArray *array = [@[@{@"name": @"a", @"shouldDelete": @(YES)},
                           @{@"name": @"b", @"shouldDelete": @(NO)},
                           @{@"name": @"c", @"shouldDelete": @(YES)},
                           @{@"name": @"d", @"shouldDelete": @(NO)}] mutableCopy];

[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if([obj[@"shouldDelete"] boolValue])
        [array removeObjectAtIndex:idx];
}];

Result:

(
    {
        name = b;
        shouldDelete = 0;
    },
    {
        name = d;
        shouldDelete = 0;
    }
)

another option with just one line of code:

[array filterUsingPredicate:[NSPredicate predicateWithFormat:@"shouldDelete == NO"]];
vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
  • Is there any guarantee that array would not be reallocated? – Cfr Oct 07 '13 at 08:01
  • What do you mean with reallocation? – vikingosegundo Oct 07 '13 at 08:37
  • While removing element from linear structure it might be effective to allocate new contiguous memory block and move all elements there. In this case all pointers would be invalidated. – Cfr Oct 07 '13 at 09:42
  • As the deletion operation wouldn't know how many elements will bet deleted at the end, it wold not make sense to do so during removal. Anyway: implementation detail, w have to trust apple to write reasonable code. – vikingosegundo Oct 07 '13 at 11:09
  • First is not nicer (because you have to think to much about how it works in the first place) and second is not refactoring safe. So in the end I ended with classic accepted solution that is quite readable actually. – Renetik Jul 13 '17 at 17:48
  • @ReneDohan I have no idea what you mean with “refactoring safe”. And „thinking too much“ is a very personal experience that depends on personal capacities. – vikingosegundo Jul 13 '17 at 19:15
  • @vikingosegundo 1 "refactoring safe" means that I use objective c editor that can do safe refactorings like rename change signature etc and if I use something like this "obj[@"shouldDelete"]" it will just confuse editor because to rename correctly it has to search for strings and I have to be more careful to not introduce bugs by refactoring if I use constructs like this that are unsafe. 2 "Thinking to much is not so personal" its alway better to write your code most simple and understandable way possible and save your memory and energy for important difficult parts and app logic. – Renetik Jul 18 '17 at 19:06
  • @ReneDohan: if you write must understandable code, you would just write your code from top to bottom. But code reusability, testability and maintainability requires modular code that you can stick together, exchange and put into libraries. This is not possible without some indirections. Also you seem to have a problem with enumeration. It is preferable as it eliminates several problems that might occur in simple for looping. Also subscripting is a valid and easy to understand language syntax. If you don’t use it because of refactoring, maybe you are refactoring too often. – vikingosegundo Jul 18 '17 at 19:19
8

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

elp
  • 8,021
  • 7
  • 61
  • 120
6

Here's the easy and clean way. I like to duplicate my array right in the fast enumeration call:

for (LineItem *item in [NSArray arrayWithArray:self.lineItems]) 
{
    if ([item.toBeRemoved boolValue] == YES) 
    {
        [self.lineItems removeObject:item];
    }
}

This way you enumerate through a copy of the array being deleted from, both holding the same objects. An NSArray holds object pointers only so this is totally fine memory/performance wise.

Matjan
  • 3,591
  • 1
  • 33
  • 31
5

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

Nathan Kinsinger
  • 23,641
  • 2
  • 30
  • 19
5

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
  • 562
  • 1
  • 7
  • 12
  • 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 Aug 01 '09 at 07:03
  • 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 Dec 20 '09 at 18:29
  • 1
    @Steph: The question states "something more elegant than manually updating the index." – Steve Madsen May 11 '10 at 16:45
  • 1
    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 May 12 '10 at 19:16
  • @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 Jun 01 '10 at 15:16
  • This gets my vote, too. I don't think using a much more inefficient method is warranted to avoid the "complexity" of this one. – elsurudo Jun 01 '12 at 09:20
1

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
  • 14,496
  • 14
  • 79
  • 118
1

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'

Cyber Oliveira
  • 8,178
  • 4
  • 28
  • 18
1

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
  • 25,525
  • 19
  • 65
  • 75
  • what happens if original *myArray* gets updated while `+arrayWithArray`is being executed? – bioffe Mar 16 '11 at 19:01
  • 1
    @bioffe: Then you have a bug in your code. NSMutableArray is not thread-safe, you are expected to control access via locks. See [this answer](http://stackoverflow.com/questions/12098011/objective-c-is-nsmutablearray-thread-safe) – dreamlax Aug 28 '13 at 03:52
1

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.

Kaiser
  • 688
  • 4
  • 12
1

A nicer implementation could be to use the category method below on NSMutableArray.

@implementation NSMutableArray(BMCommons)

- (void)removeObjectsWithPredicate:(BOOL (^)(id obj))predicate {
    if (predicate != nil) {
        NSMutableArray *newArray = [[NSMutableArray alloc] initWithCapacity:self.count];
        for (id obj in self) {
            BOOL shouldRemove = predicate(obj);
            if (!shouldRemove) {
                [newArray addObject:obj];
            }
        }
        [self setArray:newArray];
    }
}

@end

The predicate block can be implemented to do processing on each object in the array. If the predicate returns true the object is removed.

An example for a date array to remove all dates that lie in the past:

NSMutableArray *dates = ...;
[dates removeObjectsWithPredicate:^BOOL(id obj) {
    NSDate *date = (NSDate *)obj;
    return [date timeIntervalSinceNow] < 0;
}];
Werner Altewischer
  • 10,080
  • 4
  • 53
  • 60
1

I define a category that lets me filter using a block, like this:

@implementation NSMutableArray (Filtering)

- (void)filterUsingTest:(BOOL (^)(id obj, NSUInteger idx))predicate {
    NSMutableIndexSet *indexesFailingTest = [[NSMutableIndexSet alloc] init];

    NSUInteger index = 0;
    for (id object in self) {
        if (!predicate(object, index)) {
            [indexesFailingTest addIndex:index];
        }
        ++index;
    }
    [self removeObjectsAtIndexes:indexesFailingTest];

    [indexesFailingTest release];
}

@end

which can then be used like this:

[myMutableArray filterUsingTest:^BOOL(id obj, NSUInteger idx) {
    return [self doIWantToKeepThisObject:obj atIndex:idx];
}];
Kristopher Johnson
  • 81,409
  • 55
  • 245
  • 302
0

Iterating backwards-ly was my favourite for years , but for a long time I never encountered the case where the 'deepest' ( highest count) object was removed first. Momentarily before the pointer moves on to the next index there ain't anything and it crashes.

Benzado's way is the closest to what i do now but I never realised there would be the stack reshuffle after every remove.

under Xcode 6 this works

NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];

    for (id object in array)
    {
        if ( [object isNotEqualTo:@"whatever"]) {
           [itemsToKeep addObject:object ];
        }
    }
    array = nil;
    array = [[NSMutableArray alloc]initWithArray:itemsToKeep];
aremvee
  • 179
  • 1
  • 13