在Cocoa中,如果我想循环遍历NSMutableArray并删除符合特定条件的多个对象,那么每次删除对象时,如果不重新启动循环,最好的方法是什么?
谢谢,
编辑:只是为了澄清 - 我一直在寻找最佳方式,例如:比我手动更新索引更优雅的东西。例如,在C ++中我可以做;
iterator it = someList.begin();
while (it != someList.end())
{
if (shouldRemove(it))
it = someList.erase(it);
}
答案 0 :(得分:383)
为了清楚起见,我想做一个初始循环,我收集要删除的项目。然后我删除它们。这是使用Objective-C 2.0语法的示例:
NSMutableArray *discardedItems = [NSMutableArray array];
for (SomeObjectClass *item in originalArrayOfItems) {
if ([item shouldBeDiscarded])
[discardedItems addObject:item];
}
[originalArrayOfItems removeObjectsInArray:discardedItems];
然后毫无疑问,索引是否正确更新,或其他一些小的簿记细节。
编辑添加:
在其他答案中已经注意到反向公式应该更快。即,如果遍历数组并组合要保留的新对象数组,而不是要丢弃的对象。这可能是真的(虽然分配新阵列的内存和处理成本如何,并丢弃旧阵列?)但即使它更快,它也可能不像天真的实现那样大,因为NSArrays不要表现得像“普通”数组。他们谈论谈话,但他们走了不同的路。 See a good analysis here:
反向配方可能更快,但我从来不需要关心它是否,因为上述配方总是足够快以满足我的需要。
对我来说,带回家的信息是使用最清晰的配方。仅在必要时进行优化。我个人觉得上面的配方最清楚,这就是我使用它的原因。但如果反向公式对你来说更清楚,那就去吧。
答案 1 :(得分:82)
还有一个变种。因此,您可以获得可读性和良好的性能:
NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet];
SomeObjectClass *item;
NSUInteger index = 0;
for (item in originalArrayOfItems) {
if ([item shouldBeDiscarded])
[discardedItems addIndex:index];
index++;
}
[originalArrayOfItems removeObjectsAtIndexes:discardedItems];
答案 2 :(得分:41)
这是一个非常简单的问题。你只是向后迭代:
for (NSInteger i = array.count - 1; i >= 0; i--) {
ElementType* element = array[i];
if ([element shouldBeRemoved]) {
[array removeObjectAtIndex:i];
}
}
这是一种非常常见的模式。
答案 3 :(得分:39)
其他一些答案在非常大的数组上表现不佳,因为像removeObject:
和removeObjectsInArray:
这样的方法涉及对接收器进行线性搜索,这是一种浪费,因为你已经知道了对象是。此外,对removeObjectAtIndex:
的任何调用都必须将索引中的值一次复制一个插槽。
效率更高如下:
NSMutableArray *array = ...
NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];
for (id object in array) {
if (! shouldRemove(object)) {
[itemsToKeep addObject:object];
}
}
[array setArray:itemsToKeep];
因为我们设置了itemsToKeep
的容量,所以在调整大小期间我们不会浪费任何时间复制值。我们不会修改数组,因此我们可以自由使用快速枚举。使用setArray:
将array
的内容替换为itemsToKeep
将非常有效。根据您的代码,您甚至可以用以下内容替换最后一行:
[array release];
array = [itemsToKeep retain];
因此甚至不需要复制值,只需交换指针。
答案 4 :(得分:28)
您可以使用NSpredicate从可变数组中删除项目。这不需要循环。
例如,如果你有一个名称的NSMutableArray,你可以像这样创建一个谓词:
NSPredicate *caseInsensitiveBNames =
[NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];
以下行将为您提供一个仅包含以b开头的名称的数组。
[namesArray filterUsingPredicate:caseInsensitiveBNames];
如果您在创建所需谓词时遇到问题,请使用此apple developer link。
答案 5 :(得分:18)
我使用4种不同的方法进行了性能测试。每个测试迭代100,000个元素数组中的所有元素,并删除每第5个项目。无论是否优化,结果都没有太大变化。这些是在iPad 4上完成的:
(1)removeObjectAtIndex:
- 271 ms
(2)removeObjectsAtIndexes:
- 1010 ms (因为构建索引集大约需要700毫秒;否则这与调用removeObjectAtIndex:每个项目基本相同)
(3)removeObjects:
- 326 ms
(4)创建一个包含通过测试的对象的新数组 - 17 ms
因此,创建一个新阵列是迄今为止最快的。其他方法都是可比较的,除了使用removeObjectsAtIndexes:由于构建索引集所需的时间更多,删除的项目会更多。
答案 6 :(得分:17)
使用循环向下计数索引:
for (NSInteger i = array.count - 1; i >= 0; --i) {
或使用您要保留的对象制作副本。
特别是,请勿使用for (id object in array)
循环或NSEnumerator
。
答案 7 :(得分:12)
现在您可以使用基于块的反转枚举。一个简单的示例代码:
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"]];
答案 8 :(得分:12)
对于iOS 4+或OS X 10.6 +,Apple在passingTest
中添加了NSMutableArray
系列API,例如– indexesOfObjectsPassingTest:
。这种API的解决方案是:
NSIndexSet *indexesToBeRemoved = [someList indexesOfObjectsPassingTest:
^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [self shouldRemove:obj];
}];
[someList removeObjectsAtIndexes:indexesToBeRemoved];
答案 9 :(得分:8)
以更具说明性的方式,根据与要删除的项匹配的条件,您可以使用:
[theArray filterUsingPredicate:aPredicate]
@Nathan应该非常高效
答案 10 :(得分:6)
这是简单而干净的方式。我喜欢在快速枚举调用中复制我的数组:
for (LineItem *item in [NSArray arrayWithArray:self.lineItems])
{
if ([item.toBeRemoved boolValue] == YES)
{
[self.lineItems removeObject:item];
}
}
这样,您可以枚举要删除的数组的副本,两者都包含相同的对象。 NSArray只保存对象指针,因此这是完全精细的内存/性能。
答案 11 :(得分:5)
将要删除的对象添加到第二个数组中,在循环之后,使用-removeObjectsInArray:。
答案 12 :(得分:5)
这应该这样做:
NSMutableArray* myArray = ....;
int i;
for(i=0; i<[myArray count]; i++) {
id element = [myArray objectAtIndex:i];
if(element == ...) {
[myArray removeObjectAtIndex:i];
i--;
}
}
希望这会有所帮助...
答案 13 :(得分:1)
更好的实现可能是在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,则删除该对象。
日期数组的示例,用于删除过去的所有日期:
NSMutableArray *dates = ...;
[dates removeObjectsWithPredicate:^BOOL(id obj) {
NSDate *date = (NSDate *)obj;
return [date timeIntervalSinceNow] < 0;
}];
答案 14 :(得分:1)
我定义了一个允许我使用块过滤的类别,如下所示:
@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];
}];
答案 15 :(得分:1)
答案 16 :(得分:1)
如果数组中的所有对象都是唯一的,或者您希望在找到对象时删除所有出现的对象,则可以快速枚举数组副本并使用[NSMutableArray removeObject:]从原始对象中删除对象。
NSMutableArray *myArray;
NSArray *myArrayCopy = [NSArray arrayWithArray:myArray];
for (NSObject *anObject in myArrayCopy) {
if (shouldRemove(anObject)) {
[myArray removeObject:anObject];
}
}
答案 17 :(得分:1)
如何使用'n'元素,'n-1'元素等交换要删除的元素?
完成后,您将阵列调整为“之前的大小 - 交换次数”
答案 18 :(得分:1)
为什么不将要删除的对象添加到另一个NSMutableArray。完成迭代后,您可以删除已收集的对象。
答案 19 :(得分:0)
向后迭代是我多年来最喜欢的,但很长一段时间我从未遇到过“最深”(最高计数)对象首先被删除的情况。在指针移动到下一个索引之前,它几乎没有任何东西,它会崩溃。
Benzado的方式与我现在的方式最接近,但我从未意识到每次移除后都会进行堆栈重组。
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];