迭代时从NSMutableArray中删除的最佳方法是什么?

时间:2008-09-21 19:43:21

标签: objective-c cocoa

在Cocoa中,如果我想循环遍历NSMutableArray并删除符合特定条件的多个对象,那么每次删除对象时,如果不重新启动循环,最好的方法是什么?

谢谢,

编辑:只是为了澄清 - 我一直在寻找最佳方式,例如:比我手动更新索引更优雅的东西。例如,在C ++中我可以做;

iterator it = someList.begin();

while (it != someList.end())
{
    if (shouldRemove(it))   
        it = someList.erase(it);
}

20 个答案:

答案 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)

上面的答案是你应该做的表现。在我的一个应用程序中,removeObjectsInArray花了1分钟的运行时间,只需添加到一个新的数组花费.023秒。

答案 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的方式与我现在的方式最接近,但我从未意识到每次移除后都会进行堆栈重组。

在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];