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

for (SomeObjectClass *item in originalArrayOfItems) {
    if ([item shouldBeDiscarded])
        [discardedItems addObject:item];
}

[originalArrayOfItems removeObjectsInArray:discardedItems];

然后毫无疑问,索引是否正确更新,或其他一些小的簿记细节。

编辑添加:

在其他答案中已经注意到反向公式应该更快。即,如果遍历数组并组合要保留的新对象数组,而不是要丢弃的对象。这可能是真的(虽然分配新阵列的内存和处理成本如何,并丢弃旧阵列?)但即使它更快,它也可能不像天真的实现那样大,因为NSArrays不要表现得像“正常”阵列。他们谈论谈话,但他们走了不同的路。 在此处查看好的分析:

反向配方可能更快,但我从来不需要关心它是否,因为上述配方总是足够快以满足我的需要。

对我来说,带回家的信息是使用最清晰的配方。仅在必要时进行优化。我个人觉得上面的配方最清楚,这就是我使用它的原因。但如果反向公式对你来说更清楚,那就去吧。

其他提示

还有一个变种。因此,您可以获得可读性和良好的性能:

NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet];
SomeObjectClass *item;
NSUInteger index = 0;

for (item in originalArrayOfItems) {
    if ([item shouldBeDiscarded])
        [discardedItems addIndex:index];
    index++;
}

[originalArrayOfItems removeObjectsAtIndexes:discardedItems];

这是一个非常简单的问题。你只是向后迭代:

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从可变数组中删除项目。这不需要循环。

例如,如果你有一个名称的NSMutableArray,你可以像这样创建一个谓词:

NSPredicate *caseInsensitiveBNames = 
[NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];

以下行将为您提供一个仅包含以b开头的名称的数组。

[namesArray filterUsingPredicate:caseInsensitiveBNames];

如果您在创建所需的谓词时遇到问题,请使用此 apple developer链接

我使用4种不同的方法进行了性能测试。每个测试迭代100,000个元素数组中的所有元素,并删除每第5个项目。无论是否优化,结果都没有太大变化。这些是在iPad 4上完成的:

(1) removeObjectAtIndex: - 271 ms

(2) removeObjectsAtIndexes: - 1010 ms (因为构建索引集需要大约700毫秒;否则这与调用removeObjectAtIndex基本相同:对于每个项目)

(3) removeObjects: - 326 ms

(4)创建一个包含通过测试的对象的新数组 - 17 ms

因此,创建一个新阵列是迄今为止最快的。其他方法都是可比较的,除了使用removeObjectsAtIndexes:由于构建索引集所需的时间更多,删除的项目会更多。

使用循环向下计数索引:

for (NSInteger i = array.count - 1; i >= 0; --i) {

或使用您要保留的对象制作副本。

特别是,不要将用于(数组中的id对象)循环或 NSEnumerator

对于iOS 4+或OS X 10.6+,Apple在 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应该非常高效

这是简单而干净的方式。我喜欢在快速枚举调用中复制我的数组:

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。完成迭代后,您可以删除已收集的对象。

如何使用'n'元素,'n-1'元素等交换要删除的元素?

完成后,您将阵列调整为“之前的大小 - 交换次数”

如果数组中的所有对象都是唯一的,或者您希望在找到对象时删除所有出现的对象,则可以快速枚举数组副本并使用[NSMutableArray removeObject:]从原始对象中删除对象。

NSMutableArray *myArray;
NSArray *myArrayCopy = [NSArray arrayWithArray:myArray];

for (NSObject *anObject in myArrayCopy) {
    if (shouldRemove(anObject)) {
        [myArray removeObject:anObject];
    }
}
上面的答案是你应该做的表现。在我的一个应用程序中,removeObjectsInArray花了1分钟的运行时间,只需添加到一个新的数组花费.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,则删除该对象。

日期数组的示例,用于删除过去的所有日期:

NSMutableArray *dates = ...;
[dates removeObjectsWithPredicate:^BOOL(id obj) {
    NSDate *date = (NSDate *)obj;
    return [date timeIntervalSinceNow] < 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];
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top