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);
}
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.
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];
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.
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.
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.
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.
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.
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];
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"]];
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
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.
Add the objects you want to remove to a second array and, after the loop, use -removeObjectsInArray:.
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...
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.
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'
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];
}
}
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.
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];
}];
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;
}];
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];
Related
I have an array in an old objective-C app that I am using to learn more "complicated" coding. It is back from the old days of OS X and was very much broken. I have gotten it to work (mostly)! However, the app has an NSMutableArray of images, 7 in total. I use a random number generator to insert the images on the screen, some code to allow them to fall, and then, using screen bounds, when they reach "0" on the Y axis they are removed from the array.
I initially just had:
if( currentFrame.origin.y+currentFrame.size.height <= 0 )
{
[flakesArray removeObject:myItem];
I have read when removing objects from an array it is best practice to iterate in reverse...so I have this bit of code:
for (NSInteger i = myArray.count - 1; i >= 0; i--)
{ //added for for statement
if( currentFrame.origin.y+currentFrame.size.height <= 0 )
{
[myArray removeObjectAtIndex:i];
}
Sadly both methods result in the same mutated while enumerated error. Am I missing something obvious?
If I add an NSLog statement I can get, I think, the index of the item that needs to be removed:
NSLog (#"Shazam! %ld", (long)i);
2017-01-07 14:39:42.086667 MyApp[45995:7500033] Shazam! 2
I have looked through a lot and tried several different methods including this one, which looks to be the most popular with the same error.
Thank you in advance! I will happily provide any additional information!
Adding more:
Sorry guys I am not explicitly calling NSFastEnumeration but I have this:
- (void) drawRectCocoa:(NSRect)rect
{
NSEnumerator* flakesEnum = [flakesArray objectEnumerator];
then
for( i = 0; i < numberToCreate; i++ )
{
[self newObject:self];
}
while( oneFlake = [flakesEnum nextObject] )
It is here where:
if( currentFrame.origin.y+currentFrame.size.height <= 0 )
{
NSLog (#"Shazam! %i", oneFlake);
[flakesArray removeObject:oneFlake];
}
Thank you all. I am learning a lot from this discussion!
There are two ways to go: (1) collect the objects to remove then remove them with removeObjectsInArray:.
NSMutableArray *removeThese = [NSMutableArray array];
for (id item in myArray) {
if (/* item satisfies some condition for removal */) {
[removeThese addObject:item];
}
}
// the following (and any other method that mutates the array) must be done
// *outside of* the loop that enumerates the array
[myArray removeObjectsInArray:removeThese];
Alternatively, reverseObjectEnumeration is tolerant of removes during iteration...
for (id item in [myArray reverseObjectEnumerator]) {
if (/* item satisfies some condition for removal */) {
[myArray removeObject: item];
}
}
As per the error, you may not mutate any NSMutableArray (or any NSMutable... collection) while it is being enumerated as part of any fast enumeration loop (for (... in ...) { ... }).
#danh's answer works as well, but involves allocating a new array of elements. There are two simpler and more efficient ways to filter an array:
[array filterUsingPredicate:[NSPredicate predicateWithBlock:^(id element, NSDictionary<NSString *,id> *bindings) {
// if element should stay, return YES; if it should be removed, return NO
}];
or
NSMutableIndexSet *indicesToRemove = [NSMutableIndexSet new];
for (NSUInteger i = 0; i < array.count; i += 1) {
if (/* array[i] should be removed */) {
[indicesToRemove addIndex:i];
}
}
[array removeObjectsAtIndexes:indicesToRemove];
filterUsingPredicate: will likely be slightly faster (since it uses fast enumeration itself), but depending on the specific application, removeObjectsAtIndexes: may be more flexible.
No matter what, if you're using your array inside a fast enumeration loop, you will have to perform the modification outside of the loop. You can use filterUsingPredicate: to replace the loop altogether, or you can keep the loop and keep track of the indices of the elements you want to remove for later.
I am having some issues trying to wrap my head around why we can not mutate a collection during enumeration... Apparently, if you are doing any sort of fast enumeration, the system should throw an exception if you try to mutate. Below I have three examples where I am mutating during enumeration. One is a simple C-style loop, and the other two use some form of fast-enumeration. I am only getting enumeration exceptions thrown for case 2. Shouldn't I also be getting exceptions thrown for case 1? Why is case 1 valid? Also, people throughout stack overflow say my case 3 is bad practice, but why? It is simple and seems to work. Inconsistency in how the two different fast-enumeration loops are behaving and the general disgust with the C-style loop is screwing with my understanding here. Instead of vague generic rules of thumb, if someone can really break this down to a science this would really help. From a fundamental level I want to know why the exceptions are not consistent here and why case 3 works for me when it apparently "shouldn't" or is "bad practice."
//Case 1:
NSMutableArray *array = [NSMutableArray arrayWithObjects:#"phuj", #"whub", #"adgh", nil];
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[array removeObjectAtIndex:idx];
}];
//Case 2:
NSMutableArray *array = [NSMutableArray arrayWithObjects:#"phuj", #"whub", #"adgh", nil];
for (NSString *string in array) {
[array removeObject:string];
}
//Case 3:
NSMutableArray *array = [NSMutableArray arrayWithObjects:#"phuj", #"whub", #"adgh", nil];
for (int i = 0; i < [array count]; i++) {
[array removeObjectAtIndex:i];
}
We love simple and straightforward code.
I won't talk about case 2, because it throws an exception and you can't mutate the array in it.
I want to start from an example, suppose we want to remove the "whub" item from a mutable array and the content of the array is "phuj, whub, whub, adgh", it has two whub as you see.
Let's start to write the code using c style loop:
for (int i = 0; i < [mArray1 count]; i++) {
NSString *str = mArray1[i] ;
if ([str isEqualToString:#"whub"]) {
[mArray1 removeObjectAtIndex:i] ;
}
}
The code has a bug, after it removes the first "whub", all the indexes of item located after it will decrease by 1, so the index of second whub should be 1 and i is 1 at the moment. In the next loop, i is 2, so it skips the second whub. You can change the code to make it correct.
for (int i = 0; i < [mArray1 count]; i++) {
NSString *str = mArray1[i] ;
if ([str isEqualToString:#"whub"]) {
[mArray1 removeObjectAtIndex:i] ;
--i ;
}
}
It works, but it's not straightforward, we modify the index i and it makes the code complex.
Let's try to write the code using enumerateObjectsUsingBlock method like case 1:
NSMutableArray *array = [NSMutableArray arrayWithObjects:#"phuj", #"whub", #"whub", #"adgh", nil] ;
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isEqualToString:#"whub"]) {
[array removeObjectAtIndex:idx] ;
}
}];
If you build and run it, you will find the result array still contains one "whub" and this time, we can't modify the index to fix it.
Conclude: You talk much about that you can mutate the array in the loop, the fact you can mutate the array doesn't mean you will get the correct answer. In fact, with c style loop you can do what ever you want, it won't crash, but we say it is bad because we have a better way to go. In practice, we'd like to use simple code to achieve the target, so others can understand easily, ourselves can also benefit from it.
I'd like to use this way, I think it is simple and straightforward.
NSMutableArray *arrayRemove = [NSMutableArray array] ;
for (NSString *str in mArray1) {
if ([str isEqualToString:#"whub"]) {
[arrayRemove addObject:str] ;
}
}
[mArray1 removeObjectsInArray:arrayRemove] ;
I have an NSMutableArray of NSNumbers, I want to enumerate through all of them with Objective-C styled enumeration. Here's what I've done so far.
for ( NSNumber* number in array )
{
//some code
}
I want to be able to recognize the first object fast, I am able to do this of course,
if ( [array indexOfObject:number] == 0 )
{
//if it's the first object
}
Is there any way to do this faster? There's of course the old-fashioned C style way, and remove the object from array first, and then put it back after enumeration. Just want to know if there's a better technic.
You can try using a method that provides the index of the object currently being enumerated:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (idx == 0) {
// this is the first object
}
}];
Or if you simply want to access the first object of an array:
id obj = [array objectAtIndex:0];
or with the new Objective-C style/syntax:
id obj = array[0];
This solution is faster than accessing and comparing the first array element:
BOOL firstIteration = YES;
for (NSNumber *number in array) {
if (firstIteration) {
// Handle first iteration
firstIteration = NO;
}
// Do something
}
In fast enumeration you cant alter the array. So if you want to remove you have to use old style for(;;) loop.
To find the first object simply use [array objectAtIndex:0]
I've been trying to figure out a way of checking how many of a certain object are in an NSArray.
I've looked through the docs and I'm pretty sure there is no premade method for this. Also I can't find anything here on SO.
Do anybody know about a good way to do this? Because I seriously can't come up with anything.
In this specific case I have an array with strings (most cases several of each) and I want to count how many strings in the array that matches to whatever I ask for.
If this is a primary use of the data structure and order doesn't matter, consider switching to an NSCountedSet which is specifically for solving this problem efficiently.
If you need an ordered collection, and you don't have a huge set of objects, than the fast enumeration answers are the best approach.
If you want to know where the objects are, then use indexesOfObjectsPassingTest:.
If you have a huge number of object, I would look at indexesOfObjectsWithOptions:passingTest: with the NSEnumerationConcurrent option. This will allow you to search the array on multiple cores. (This is only possibly faster on a multi-core device, and even then is probably only faster if you have a very large collection. You should absolutely test before assuming that concurrent will be faster.) Even if you just need the final count, it may be faster for certain data sets to use this method and then use count on the final index set.
There actually is a method for this: - (NSIndexSet *)indexesOfObjectsPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
NSIndexSet *indexes = [array indexesOfObjectsPassingTest:^(id obj, NSUInteger index, BOOL *stop) {
return [obj isEqualTo:myOtherObject];
}];
Sounds like a case for NSCountedSet, which does what you are after with its initWithArray: initializer:
// Example array of strings
NSArray *array = [NSArray arrayWithObjects:
#"Joe", #"Jane", #"Peter", #"Paul",
#"Joe", #"Peter", #"Paul",
#"Joe",
#"Jane", #"Peter",
nil];
NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray: array];
// for-in will let you loop over the counted set
for (NSString *str in countedSet) {
NSLog(#"Count of %#: %ld", str, (long)[countedSet countForObject:str]);
}
One approach would be to iterate and check.
- (int)repeatsOf:(NSString *)repeater inArray:(NSArray *)array {
int count = 0;
for (NSString *item in array) {
if ([item isEqualToString:repeater]) {
count++;
}
}
return count;
}
You could try a simple loop. Suppose needle is your reference string and array is your NSArray of strings:
unsigned int n = 0;
for (NSString * str in array)
{
if ([needle isEqualToString:str])
{
++n;
}
}
Now n holds the count of strings in equal to needle.
You could define a function like this:
- (int)countStringsThatMatch:(NSString*)match inArray:(NSArray*)array
{
int matches = 0;
for (id string in array) {
if ([string isEqualToString:match]) {
matches++;
}
}
return matches;
}
And then use it like:
int count = [self countStringsThatMatch:#"someString" inArray:someArray];
- (NSUInteger) objectCountInArray:(NSArray *)array
matchingString:(NSString *)stringToMatch {
NSUInteger count = 0;
for (NSString *string in array) {
count += [string isEqualToString:stringToMatch] ? 1 : 0;
}
return count;
}
You can try to expand this to use a block that gets an object and returns a BOOL. Then you can use it to compare an array of whatever you want.
I am using block based enumeration similar to the following code:
[[[rows objectForKey:self.company.coaTypeCode] objectForKey:statementType]
enumerateObjectsWithOptions:NSEnumerationConcurrent
usingBlock:^(id coaItem, NSUInteger idx, BOOL *stop) {
// block code here
}]
I would like to remove some of the objects during the enumeration process depending on the their object values.
How could I do this? I know that manipulating an mutable array or dictionary (NSMutableArray or NSMutableDictionary) during enumeration is usually not possible.
What would be the best way to implement this?
Thank you!
Since you can't remove objects from an array or dictionary during enumeration, you'll have to accumulate the items you want to delete, and then delete them all after the enumeration.
If you're dealing with an array, you can just accumulate the indices.:
NSMutableIndexSet *indexesToDelete = [NSMutableIndexSet indexSet];
NSUInteger currentIndex = 0;
for (id obj in yourArray) {
//do stuff with obj
if (shouldBeDeleted(obj)) {
[indexesToDelete addIndex:currentIndex];
}
currentIndex++;
}
[yourArray removeObjectsAtIndexes:indexesToDelete];
Since the order of the keys in an NSDictionary is undefined, for an NSMutableDictionary you'll have to accumulate keys instead:
NSMutableArray *keysToDelete = [NSMutableArray array];
for (id obj in [yourDictionary keyEnumerator]) {
//do stuff with obj
if (shouldBeDeleted(obj)) {
[keysToDelete addObject:obj];
}
}
[yourDictionary removeObjectsForKeys:keysToDelete];
It's the same thing if you're enumerating with a block. Declare the enumerator in the same scope where you declare the block and it will be retained and just work.
Also worth looking at this question from 3 years ago: Best way to remove from NSMutableArray while iterating?.
Whether you build up an index set during enumeration, or modify the array itself during enumeration, you will have to give up NSEnumerationConcurrent, because most Cocoa objects cannot safely be modified simultaneously from multiple threads.
Anyway, the simplest (but maybe not most efficient) approach is to just enumerate a copy of the container.
For an array, you can enumerate a copy in reverse. I assume that as each item is being enumerated, you may decide to remove that item, but not other items previously enumerated or yet to be enumerated.
NSMutableArray *array = [[rows objectForKey:self.company.coaTypeCode] objectForKey:statementType];
[[array copy] enumerateObjectsWithOptions: NSEnumerationReverse
usingBlock:^(id coaItem, NSUInteger idx, BOOL *stop) {
if ([self objectIsTooUglyToExist:coaItem])
[array removeObjectAtIndex:idx];
}]
You have to enumerate the array in reverse to avoid changing the not-yet-enumerated part of the array.
For a dictionary, you can just enumerate a copy with no special options:
NSMutableDictionary *dictionary = someDictionary;
[[dictionary copy] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([self object:obj isTooUglyToExistAtKey:key])
[dictionary removeObjectForKey:key];
}];
Another option, with an array, is to use a conventional for loop, with the array's count as the limit. Then one needs to be cognizant of whether an element is removed from a location <= the index (in which case the index should be decremented) or > than the index (in which case the index is left unmodified other than the for statement's increment).
For a dictionary you can first create an array with allKeys, and then iterate through the array. In this case no fiddling with index values is required.