在迭代时从NSMutableArray中移除的最佳方法是什么?
在Cocoa中,如果我想循环遍历一个NSMutableArray并删除符合某个条件的多个对象,那么每次删除一个对象时,如果不重新启动循环,最好的方法是什么?
谢谢,
编辑:只是为了澄清 – 我正在寻找最好的方式,例如比我手动更新索引更优雅。 例如在C + +我可以做;
iterator it = someList.begin(); while (it != someList.end()) { if (shouldRemove(it)) it = someList.erase(it); }
为了清晰起见,我喜欢在收集要删除的项目时进行初始循环。 然后我删除它们。 下面是一个使用Objective-C 2.0语法的示例:
NSMutableArray *discardedItems = [NSMutableArray array]; SomeObjectClass *item; for (item in originalArrayOfItems) { if ([item shouldBeDiscarded]) [discardedItems addObject:item]; } [originalArrayOfItems removeObjectsInArray:discardedItems];
那么索引是否正确更新,或其他小簿记细节是没有问题的。
编辑添加:
在其他答案中已经注意到,逆配方应该更快。 即如果你迭代通过数组,并组成一个新的对象数组来保持,而不是丢弃的对象。 这可能是真的(尽pipe如何分配一个新的数组的内存和处理成本,并废弃旧的?),但即使速度更快,它可能不会像一个天真的实现那么大的交易,因为NSArrays不要像“正常”的数组。 他们谈话,但他们走了一个不同的步行。 在这里看到一个好的分析:
http://ridiculousfish.com/blog/archives/2005/12/23/array/
逆配方可能会更快,但我从来不需要关心它是否是因为上述公式一直足以满足我的需求。
对我来说,回家的信息是使用任何最清晰的expression方式。 只在必要时进行优化。 我个人认为上面的表述最清楚,这就是我使用它的原因。 但是,如果反面的说法对你更清楚,就去做吧。
还有一个变化。 所以你得到可读性和良好的性能:
NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet]; SomeObjectClass *item; NSUInteger index = 0; for (item in originalArrayOfItems) { if ([item shouldBeDiscarded]) [discardedItems addIndex:index]; index++; } [originalArrayOfItems removeObjectsAtIndexes:discardedItems];
其他一些答案在非常大的数组上会有很差的性能,因为像removeObject:
和removeObjectsInArray:
这样的方法涉及到对接收器进行线性search,这是很浪费的,因为你已经知道对象在哪里了。 此外,对removeObjectAtIndex:
任何调用都必须removeObjectAtIndex:
将索引中的值复制到数组的末尾。
效率更高的是以下几点:
NSMutableArray *array = ... NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]]; for (id object in array) { if (! shouldRemove(object)) { [itemsToKeep addObject:object]; } } [array setArray:itemsToKeep];
因为我们设置了itemsToKeep
的容量, itemsToKeep
我们不会浪费任何时间在resize的时候复制值。 我们不修改arrays,所以我们可以自由使用快速枚举。 使用setArray:
用itemsToKeep
replacearray
的内容将是高效的。 根据你的代码,你甚至可以用最后一行代替:
[array release]; array = [itemsToKeep retain];
所以甚至不需要复制值,只需交换一个指针即可。
为了大声哭泣! 这是一个非常简单的问题。 你只是向后迭代:
for (NSInteger i = array.count - 1; i >= 0; i--) { ElementType* element = array[i]; if ([element shouldBeRemoved]) { [array removeObjectAtIndex:i]; } }
这是一个非常普遍的模式,但只有延斯似乎已经得到了它。
您可以使用NSpredicate从可变数组中删除项目。 这不需要循环。
例如,如果你有一个NSMutableArray的名字,你可以创build一个这样的谓词:
NSPredicate *caseInsensitiveBNames = [NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];
下面的代码会给你一个只包含以b开头的名字的数组。
[namesArray filterUsingPredicate:caseInsensitiveBNames];
如果您在创build所需的谓词时遇到困难,请使用此Apple开发人员链接 。
要么使用循环倒数索引 – 对于(NSInteger i = array.count – 1; i> = 0; –i) – 或者使用要保留的对象进行复制。 特别是,不要使用for(id对象在数组中)循环或NSEnumerator。
我使用4种不同的方法进行了性能testing。 每个testing迭代100000个元素数组中的所有元素,并移除每个第5个项目。 没有优化的结果没有太大的变化。 这些是在iPad 4上完成的:
(1) removeObjectAtIndex:
– 271毫秒
(2) removeObjectsAtIndexes:
– 1010毫秒 (因为构build索引集需要~700毫秒;否则这基本上与为每个项调用removeObjectAtIndex相同)
(3) removeObjects:
– 326 ms
(4)通过testing的对象创build一个新的arrays – 17毫秒
所以,创build一个新arrays是迄今为止最快的。 除了使用removeObjectsAtIndexes:之外,其他方法都是可比较的,因为构build索引集所需的时间会更多。
对于iOS 4+或OS X 10.6+,Apple在NSMutableArray
添加了passingTest
系列的API,如– indexesOfObjectsPassingTest:
具有这种API的解决scheme将是:
NSIndexSet *indexesToBeRemoved = [someList indexesOfObjectsPassingTest: ^BOOL(id obj, NSUInteger idx, BOOL *stop) { return [self shouldRemove:obj]; }]; [someList removeObjectsAtIndexes:indexesToBeRemoved];
现在,您可以使用反向的基于块的枚举。 一个简单的示例代码:
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]; }];
结果:
( { name = b; shouldDelete = 0; }, { name = d; shouldDelete = 0; } )
另一个选项只有一行代码:
[array filterUsingPredicate:[NSPredicate predicateWithFormat:@"shouldDelete == NO"]];
以更具说明性的方式,根据匹配要删除的项目的条件,您可以使用:
[theArray filterUsingPredicate:aPredicate]
@Nathan应该非常高效
这是简单而干净的方式。 我喜欢在快速枚举调用中复制我的数组:
for (LineItem *item in [NSArray arrayWithArray:self.lineItems]) { if ([item.toBeRemoved boolValue] == YES) { [self.lineItems removeObject:item]; } }
通过这种方式,您可以枚举要从中删除的数组副本,这两个副本保存相同的对象。 一个NSArray只保存对象指针,所以这是非常好的内存/性能明智的。
将要移除的对象添加到第二个数组中,并在循环之后使用-removeObjectsInArray :.
这应该做到这一点:
NSMutableArray* myArray = ....; int i; for(i=0; i<[myArray count]; i++) { id element = [myArray objectAtIndex:i]; if(element == ...) { [myArray removeObjectAtIndex:i]; i--; } }
希望这可以帮助…
为什么不把要移除的对象添加到另一个NSMutableArray。 完成迭代后,可以删除已收集的对象。
如果数组中的所有对象都是唯一的,或者想要在find对象时删除所有对象,则可以快速枚举数组副本并使用[NSMutableArray removeObject:]从原始对象中删除对象。
NSMutableArray *myArray; NSArray *myArrayCopy = [NSArray arrayWithArray:myArray]; for (NSObject *anObject in myArrayCopy) { if (shouldRemove(anObject)) { [myArray removeObject:anObject]; } }
上面的benzado的anwser是你应该做的preformace。 在我的一个应用程序中removeObjectsInArray花了1分钟的运行时间,只是添加到一个新的数组花了0.023秒。
我定义了一个让我使用块进行过滤的类,如下所示:
@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
然后可以像这样使用它:
[myMutableArray filterUsingTest:^BOOL(id obj, NSUInteger idx) { return [self doIWantToKeepThisObject:obj atIndex:idx]; }];
更好的实现可能是在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
谓词块可以被实现来对数组中的每个对象进行处理。 如果谓词返回true,则删除该对象。
date数组的一个例子是删除过去的所有date:
NSMutableArray *dates = ...; [dates removeObjectsWithPredicate:^BOOL(id obj) { NSDate *date = (NSDate *)obj; return [date timeIntervalSinceNow] < 0; }];
把你想要删除的元素换成第n个元素,第n-1个元素等等如何?
完成后,将数组大小调整为“以前的大小 – 交换次数”
迭代迭代是我多年来的最爱,但是很长一段时间里,我从来没有遇到过“最深”(最高统计量)对象被首先删除的情况。 在指针移动到下一个索引之前的一瞬间,没有任何东西崩溃。
Benzado的方式是最接近我现在做的,但我从来没有意识到每次删除后会有堆栈重新洗牌。
在Xcode 6下这工作
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];