PDA

View Full Version : Mutating an array while enumerating




cm0s
Feb 12, 2009, 12:27 PM
Hello,

files is an array of File objects.
I want to enumerate through the file array and check for File objects that has equal md5 digest.
I want to remove the file objects from the array that are copies of currentFile, but I can't remove objects from the files array while it's enumerating. Can I achieve this by some other means?


for (File *currentFile in files) {
NSPredicate *predicate = [NSPredicate predicateWithFormat: @"md5Hash == %@ && filePath != %@", [currentFile md5Hash], [currentFile filePath]]; // This will check for files that has equal md5 digest but not the same filepath (ie true if it's a file duplicate)

if ([[files filteredArrayUsingPredicate:predicate] count] > 0) // If copies are found
{
[currentFile addCopies:[files filteredArrayUsingPredicate:predicate]]; // The file class has an array which stores its copies
[files removeObjectsInArray:[files filteredArrayUsingPredicate:predicate]]; // Can't do this. :(
}
}



HiRez
Feb 12, 2009, 01:59 PM
Instead of using an enumerator, just use a simple while loop with an integer index, which you will use to access the array elements using objectAtIndex:. Increment the index each time through the loop, unless you remove an element, in which case you will not increment the index since the same index will now point to the object after the one you just deleted (it'll move up a spot in the array). You just need to make sure that your loop termination condition, where you check to see if you're at the last index, gets updated with the new size of the array each time you remove an object (or get the array size dynamically each time through the loop). You could also start at the last index and decrement the index, making sure you don't go below zero and checking for nil in case you removed every object in the array.

MacRumors Guy
Feb 12, 2009, 02:06 PM
From the NSEnumerator documentation:

Note: It is not safe to modify a mutable collection while enumerating through it. Some enumerators may currently allow enumeration of a collection that is modified, but this behavior is not guaranteed to be supported in the future.


Using the removeObjectAtIndex method you have no performance guarantees.

Krevnik
Feb 12, 2009, 02:44 PM
The problem here is that mutating an array will give you grief, so you need to move the mutate outside the loop. To do that, you need to keep track of what files need to be removed in a separate collection.


NSMutableArray *filesToRemove = [NSMutableArray array]; // A set would be nicer, but then File needs to have a good hash method.

for (File *currentFile in files) {
if([filesToRemove containsObject:currentFile]) // Skip Dupes
continue;

NSPredicate *predicate = [NSPredicate predicateWithFormat: @"md5Hash == %@ && filePath != %@", [currentFile md5Hash], [currentFile filePath]]; // This will check for files that has equal md5 digest but not the same filepath (ie true if it's a file duplicate)

NSArray *filteredArray = [files filteredArrayUsingPredicate:predicate];

if ([filteredArray count] > 0) // If copies are found
{
[currentFile addCopies:filteredArray];
[filesToRemove addObjectsFromArray:filteredArray];
}
}

[files removeObjectsInArray:filesToRemove];


The code behavior should be identical to the code you had before, but will now work (I think).

kpua
Feb 12, 2009, 03:36 PM
Krevnik's approach is good, but you can also use an NSIndexSet and removeObjectsAtIndexes:. NSIndexSet is a lot lighter weight than another array and it will be more efficient when removing because NSMutableArray won't have to do an -indexOfObject:

cm0s
Feb 13, 2009, 06:38 AM
Thank you very much for the replies.

Krevniks code worked very well, greatly appreciated. :)

I'll read up on NSIndexSet and see how I can implement it to improve performance.