Удаление элемента из массива в Objective-C

этот вопрос может показаться простым, но я ищу наиболее эффективный и памяти дружески.

предположим, у меня есть массив объектов Person. У каждого человека есть цвет волос, представленный NSString. Предположим, я хочу удалить все объекты Person из массива, где их цвет волос коричневый.

Как это сделать?

имейте в виду, что вы не можете удалить объект из массива, перечисляется на.

6 ответов


существует два общих подхода. Мы можем протестировать каждый элемент, а затем немедленно удалить элемент, если он соответствует критериям теста, или мы можем протестировать каждый элемент и сохранить индексы элементов, соответствующих критериям теста, а затем удалить все такие элементы сразу. При использовании памяти реальная проблема, требования к хранению для последнего подхода могут сделать его нежелательным.

С" хранить все индексы для удаления, а затем удалить их " подход из таблицы, нам нужно рассмотрим детали, связанные с первым подходом, и как они повлияют на правильность и скорость подхода. В этом подходе есть две фатальные ошибки. Первый-удалить оцениваемый объект не на основе его индекса в массиве, а с помощью removeObject: метод. removeObject: выполняет линейный поиск в массиве, чтобы найти объект для удаления. При большом несортированном наборе данных это разрушит нашу производительность по мере увеличения времени с квадратом входного размера. По способом, используя indexOfObject: а то removeObjectAtIndex: так же плохо, поэтому мы должны избегать его. Второй фатальной ошибкой будет запуск нашей итерации с индексом 0. NSMutableArray переставляет индексы после добавления или удаления объекта, поэтому, если мы начнем с индекса 0, нам будет гарантировано исключение индекса за пределами границ, если даже один объект будет удален во время итерации. Итак, мы должны начать с задней части массива и удалять только объекты с более низкими индексами, чем каждый индекс, который мы проверили так далеко.

выложив это, есть действительно два очевидных выбора: a 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 С :

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

идя в обратном порядке, вы можете удалить объект из массива, перечисленных.


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/


попробуйте вот так,

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

ключ должен использовать предикаты для фильтрации массива. См. код ниже;

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

если вы делаете копию массива с определенными отфильтрованными элементами, то сделайте новый изменяемый массив, повторите оригинал и добавьте его в копию на лету, как предлагали другие для этого ответа. Но ваш вопрос сказал, что удаление из существующего (предположительно изменяемого) массива.

во время итерации вы можете создать массив объектов для удаления, а затем удалить их впоследствии:

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 оператор сбора вы можете использовать (я недавно читал об этом, вы можете делать некоторые сумасшедшие вещи с теми, кто согласно NSHipster & Парень Английский). По-видимому, нет, но близко, нужно использовать KVC и NSPredicate в этой несколько многословной строке:

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

продолжайте и создайте категорию на NSArray сделать вещи более краткие для вашего кода,filterWithFormat: или что-то в этом роде.

(все непроверенные, набранные непосредственно в SO)