这个问题似乎很简单,但是我正在寻找最有效和最友好的存储方式。
假设我有一个Person对象数组。每个人的头发颜色都用NSString
表示。然后,让我们说我要从其发色为棕色的数组中删除所有Person对象。
我该如何做?
请记住,您不能从要枚举的数组中删除对象。
有两种一般方法。我们可以测试每个元素,然后在满足测试标准的情况下立即将其删除,或者我们可以测试每个元素并存储满足测试条件的元素的索引,然后立即删除所有此类元素。由于内存使用是一个真正的问题,后一种方法的存储要求可能使其不受欢迎。
通过“存储要删除的所有索引,然后删除它们”的方法,我们需要考虑前一种方法所涉及的细节,以及它们如何影响方法的正确性和速度。这种方法有两个致命错误。第一种是不基于数组中的索引而是通过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秒之间的差。因此,我建议使用块方法,因为它更安全并且感觉更惯用。
假设您要处理的是可变数组,并且没有对它进行排序/索引(即必须扫描整个数组),则可以使用enumerateObjectsWithOptions
和enumerateObjectsWithOptions
选项以相反的顺序遍历数组:
NSEnumerationReverse
通过相反的顺序,您可以从要枚举的数组中删除一个对象。
[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;
关键是使用谓词来过滤数组。参见下面的代码;
http://nshipster.com/nspredicate/
尝试这样,
- (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];
[如果您要复制某个数组的副本,并过滤掉某些项目,请创建一个新的可变数组,遍历原始数组,然后动态添加到副本中,正如其他人对此答案的建议一样。但是您的问题是要从现有的(可能是可变的)阵列中删除。
迭代时,您可以构建要删除的对象数组,然后再删除它们:
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)
NSArray