谢谢。编辑:只是为了澄清-我正在寻找最好的方法,例如比手动更新我的索引更优雅的方法。例如,在C++中我可以这样做;
iterator it = someList.begin();
while (it != someList.end())
{
if (shouldRemove(it))
it = someList.erase(it);
}
iterator it = someList.begin();
while (it != someList.end())
{
if (shouldRemove(it))
it = someList.erase(it);
}
为了清晰明了,我喜欢先创建一个循环来收集要删除的项。然后再进行删除。以下是使用Objective-C 2.0语法的示例:
NSMutableArray *discardedItems = [NSMutableArray array];
for (SomeObjectClass *item in originalArrayOfItems) {
if ([item shouldBeDiscarded])
[discardedItems addObject:item];
}
[originalArrayOfItems removeObjectsInArray:discardedItems];
那么就不存在关于索引是否被正确更新或其他小细节的问题了。
编辑添加:
在其他答案中已经指出,逆向公式应该更快。即,如果你遍历数组并组成一个新的要保留的对象数组,而不是要丢弃的对象数组。这可能是正确的(虽然分配新数组和丢弃旧数组的内存和处理成本又该如何?),但即使它更快,也可能不像对于一个简单的实现那样重要,因为NSArray不像“普通”的数组一样行动。它们说了一套话,但它们走的是不同的路线。在此处查看良好的分析:
逆向公式可能会更快,但我从未需要关心它是否更快,因为上述公式总是足够快,满足我的需求。
对我来说,重要的是使用对自己最清晰的公式。仅在必要时进行优化。我个人发现上述公式最清晰,所以我使用它。但如果逆向公式对您更清晰,请使用它。
还有一种变体。这样您就可以获得可读性和良好的性能:
NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet];
SomeObjectClass *item;
NSUInteger index = 0;
for (item in originalArrayOfItems) {
if ([item shouldBeDiscarded])
[discardedItems addIndex:index];
index++;
}
[originalArrayOfItems removeObjectsAtIndexes:discardedItems];
enumerateObjectsUsingBlock:
方法可以免费获得索引增量。 - pkamb这是一个非常简单的问题。你只需要反向迭代:
for (NSInteger i = array.count - 1; i >= 0; i--) {
ElementType* element = array[i];
if ([element shouldBeRemoved]) {
[array removeObjectAtIndex:i];
}
}
这是一个非常常见的模式。
其他答案中的一些方法在处理大型数组时性能较差,因为像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];
因此,甚至无需复制值,只需交换指针。
你可以使用NSPredicate从可变数组中删除项目,这不需要使用for循环。
例如,如果你有一个名为names的NSMutableArray,你可以创建一个像下面这样的谓词:
NSPredicate *caseInsensitiveBNames =
[NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];
以下代码将返回一个只包含以字母 b 开头的名字的数组。
[namesArray filterUsingPredicate:caseInsensitiveBNames];
我使用了4种不同的方法进行性能测试。每个测试都遍历了一个包含100,000个元素的数组中的所有元素,并且删除了其中每5个元素。无论是否进行了优化,结果并没有太大差别。这些测试是在iPad 4上完成的:
(1) removeObjectAtIndex:
-- 271毫秒
(2) removeObjectsAtIndexes:
-- 1010毫秒(因为构建索引集需要约700毫秒;否则,这基本上与每个项目单独调用removeObjectAtIndex:相同)
(3) removeObjects:
-- 326毫秒
(4) 创建一个由通过测试的对象组成的新数组 -- 17毫秒
因此,创建一个新数组远比其他方法快。其他方法都是可比较的,除了使用removeObjectsAtIndexes:将随着要删除的项目数量的增加而变得更劣,因为需要构建索引集所需的时间会更长。
可以使用循环下标倒序遍历:
for (NSInteger i = array.count - 1; i >= 0; --i) {
或者使用拷贝来保留你所需的对象。
特别注意,不要使用for (id object in array)
循环或NSEnumerator
。
在iOS 4+或OS X 10.6+上,苹果公司在NSMutableArray
中添加了一系列passingTest
API,例如– indexesOfObjectsPassingTest:
。使用此类API的解决方案如下:
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应该非常高效