从Objective-C的数组中删除项目

问题描述 投票:21回答:7

这个问题似乎很简单,但是我正在寻找最有效和最友好的存储方式。

假设我有一个Person对象数组。每个人的头发颜色都用NSString表示。然后,让我们说我要从其发色为棕色的数组中删除所有Person对象。

我该如何做?

请记住,您不能从要枚举的数组中删除对象。

ios objective-c nsstring nsmutablearray nsarray
7个回答
30
投票

有两种一般方法。我们可以测试每个元素,然后在满足测试标准的情况下立即将其删除,或者我们可以测试每个元素并存储满足测试条件的元素的索引,然后立即删除所有此类元素。由于内存使用是一个真正的问题,后一种方法的存储要求可能使其不受欢迎。

通过“存储要删除的所有索引,然后删除它们”的方法,我们需要考虑前一种方法所涉及的细节,以及它们如何影响方法的正确性和速度。这种方法有两个致命错误。第一种是不基于数组中的索引而是通过removeObject:方法删除评估的对象。 removeObject:对数组进行线性搜索以找到要删除的对象。对于大型未排序的数据集,随着时间随着输入大小的平方增加,这将破坏我们的性能。顺便说一句,先使用indexOfObject:再使用removeObjectAtIndex:同样不好,因此我们也应避免使用它。第二个致命错误是从索引0开始迭代。NSMutableArray在添加或删除对象后重新排列索引,因此,如果我们从索引0开始,即使有一个对象是索引,也可以保证索引超出范围。在迭代过程中删除。因此,我们必须从数组的后面开始,只删除索引比到目前为止我们检查过的每个索引都低的对象。

进行了说明,实际上有两个明显的选择:for循环始于数组的末尾而不是数组的开头,或者是NSArray方法enumerateObjectsWithOptions:usingBlock:方法。每个示例如下:

[persons enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(Person *p, NSUInteger index, BOOL *stop) {
    if ([p.hairColor isEqualToString:@"brown"]) {
        [persons removeObjectAtIndex:index];
    }
}];

NSInteger count = [persons count];
for (NSInteger index = (count - 1); index >= 0; index--) {
    Person *p = persons[index];
    if ([p.hairColor isEqualToString:@"brown"]) {
        [persons removeObjectAtIndex:index];
    }
}

我的测试似乎显示for循环略快-对于500,000个元素,循环速度可能快约四分之一秒,这基本上是8.5秒和8.25秒之间的差。因此,我建议使用块方法,因为它更安全并且感觉更惯用。


14
投票

假设您要处理的是可变数组,并且没有对它进行排序/索引(即必须扫描整个数组),则可以使用enumerateObjectsWithOptionsenumerateObjectsWithOptions选项以相反的顺序遍历数组:

NSEnumerationReverse

通过相反的顺序,您可以从要枚举的数组中删除一个对象。


6
投票
[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // now you can remove the object without affecting the enumeration
}];

或NSPredicate也可以使用:NSMutableArray * tempArray = [self.peopleArray mutableCopy]; for (Person * person in peopleArray){ if ([person.hair isEqualToString: @"Brown Hair"]) [tempArray removeObject: person] } self.peopleArray = tempArray;


4
投票

关键是使用谓词来过滤数组。参见下面的代码;

http://nshipster.com/nspredicate/

3
投票

尝试这样,

- (NSArray*)filterArray:(NSArray*)list
{
    return  [list filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings){
        People *currentObj = (People*)evaluatedObject;
        return (![currentObj.hairColour isEqualToString:@"brown"]);
    }]];
}

OR

        NSIndexSet *indices = [personsArray indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
            return [[obj objectForKey:@"hair"] isEqual:@"Brown Hair"];
        }];
         NSArray *filtered = [personsArray objectsAtIndexes:indices];

3
投票

[如果您要复制某个数组的副本,并过滤掉某些项目,请创建一个新的可变数组,遍历原始数组,然后动态添加到副本中,正如其他人对此答案的建议一样。但是您的问题是要从现有的(可能是可变的)阵列中删除。

迭代时,您可以构建要删除的对象数组,然后再删除它们:

        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.hair=%@ ",@"Brown Hair"];
        NSArray*   myArray = [personsArray filteredArrayUsingPredicate:predicate];
        NSLog(@"%@",myArray);

但是这会创建一个临时数组,您可能会认为这是浪费的,更重要的是,很难看到NSMutableArray *thePeople = ... NSString *hairColorToMatch = ... NSMutableArray *matchingObjects = [NSMutableArray array]; for (People *person in thePeople) { if (person.hairColor isEqualToString:hairColorToMatch]) [matchingObjects addObject:person]; [thePeople removeObjects:matchingObjects]; 非常有效。另外,有人提到了有关具有重复项的数组的内容,在这种情况下应该可以使用,但并不是最好的,因为每个重复项也都在临时数组中,并且在removeObjects:中具有冗余匹配。

可以改为按索引进行迭代,然后随需删除,但这会使循环逻辑变得很尴尬。相反,我将收集索引集中的索引,然后再次删除:

removeObjects:

我相信索引集的开销确实很低,因此这几乎与您将获得的效率一样高,而且很难搞砸。这样最后分批删除的另一件事是,Apple可能优化了NSMutableIndexSet *matchingIndexes = [NSMutableIndexSet indexSet]; for (NSUInteger n = thePeople.count, i = 0; i < n; ++i) { People *person = thePeople[i]; if ([person.hairColor isEqualToString:hairColorToMatch]) [matchingIndexes addIndex:i]; } [thePeople removeObjectsAtIndexes:matchingIndexes]; ,使其优于removeObjectsAtIndexes:序列。因此,即使有创建索引集数据结构的开销,也可以胜过在迭代时快速删除的操作。如果阵列中有重复项,这也可以很好地工作。

[相反,如果您确实是在制作经过过滤的副本,那么我认为可以使用某些removeObjectAtIndex:集合运算符(我最近在读那些书,您可以根据KVC和[C0 ])。显然没有,但是很接近,需要在这一有点冗长的代码行中使用KVC NSPredicate:

NSHipster

请继续在Guy English上创建一个类别,以使代码,NSArray *subsetOfPeople = [allPeople filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:@"SELF.hairColor != %@", hairColorToMatch]]; 或其他内容更简洁。

(所有未经测试,直接输入SO)


0
投票
NSArray
© www.soinside.com 2019 - 2024. All rights reserved.