24

我正在寻找最有效和记忆友好的方式。

假设我有一个Person对象数组。每个人都有一种头发颜色,用NSString. 然后假设我想Person从数组中删除头发颜色为棕色的所有对象。

我该怎么做呢?

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

4

7 回答 7

34

有两种通用方法。我们可以对每个元素进行测试,如果满足测试条件则立即删除该元素,或者我们可以测试每个元素并存储满足测试条件的元素的索引,然后一次性删除所有此类元素。由于内存使用是一个真正的问题,后一种方法的存储要求可能会使其不受欢迎。

对于“存储所有索引以删除,然后删除它们”的方法,我们需要考虑前一种方法所涉及的细节,以及它们将如何影响该方法的正确性和速度。这种方法有两个致命错误在等待。第一种是删除评估对象不是基于它在数组中的索引,而是使用removeObject:方法。removeObject:对数组进行线性搜索以找到要删除的对象。对于一个大的、未排序的数据集,随着时间随着输入大小的平方而增加,这将破坏我们的性能。顺便说一句,使用indexOfObject:thenremoveObjectAtIndex:也一样糟糕,所以我们也应该避免它。第二个致命错误是在索引 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 秒之间的差异。所以我建议使用块方法,因为它更安全并且感觉更惯用。

于 2013-10-01T09:48:07.780 回答
15

enumerateObjectsWithOptions假设您正在处理一个可变数组并且它没有排序/索引(即您必须扫描数组),您可以使用选项以相反的顺序遍历数组NSEnumerationReverse

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

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

于 2013-10-01T04:30:13.213 回答
9
NSMutableArray * tempArray = [self.peopleArray mutableCopy];

for (Person * person in peopleArray){

 if ([person.hair isEqualToString: @"Brown Hair"])
     [tempArray removeObject: person]

}

self.peopleArray = tempArray;

或者 NSPredicate 也可以:http ://nshipster.com/nspredicate/

于 2013-10-01T04:26:13.220 回答
5

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

- (NSArray*)filterArray:(NSArray*)list
{
    return  [list filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings){
        People *currentObj = (People*)evaluatedObject;
        return (![currentObj.hairColour isEqualToString:@"brown"]);
    }]];
}
于 2013-10-01T04:31:11.090 回答
3

试试这样

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

或者

        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.hair=%@ ",@"Brown Hair"];
        NSArray*   myArray = [personsArray filteredArrayUsingPredicate:predicate];
        NSLog(@"%@",myArray);
于 2013-10-01T04:34:52.337 回答
3

如果您正在制作一个过滤掉某些项目的数组副本,那么制作一个新的可变数组,迭代原始数组并动态添加到副本中,正如其他人对此答案所建议的那样。但是您的问题是从现有(可能是可变的)数组中删除。

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

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:.

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

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

我相信索引集的开销非常低,因此这几乎与您将获得的效率一样高,而且很难搞砸。像这样在最后批量删除的另一件事是,Apple 可能已经优化removeObjectsAtIndexes:到比一系列removeObjectAtIndex:. 因此,即使有创建索引集数据结构的开销,这也可能会在迭代时快速删除。如果数组有重复,这个也很好用。

相反,如果您确实在制作过滤副本,那么我认为KVC您可以使用一些集合运算符(我最近正在阅读这些内容,您可以根据NSHipsterGuy English对那些内容做一些疯狂的事情)。显然没有,但很接近,需要在这个有点罗嗦的行中使用 KVCNSPredicate:

NSArray *subsetOfPeople = [allPeople filteredArrayUsingPredicate:
    [NSPredicate predicateWithFormat:@"SELF.hairColor != %@", hairColorToMatch]];

请继续创建一个类别,NSArray以使您的代码filterWithFormat:或其他内容更简洁。

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

于 2013-10-01T05:52:56.747 回答
0
NSMutableArray *arrayForStuff = ...

[arrayForStuff removeObjectAtIndex:[arrayForStuff indexOfObject:objectToRemove]];
于 2020-03-04T14:28:27.893 回答