Подтвердить что ты не робот

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

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

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

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

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

4b9b3361

Ответ 1

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

Когда "сохранить все индексы для удаления, а затем удалить их" из таблицы, нам нужно рассмотреть детали, связанные с первым подходом, и как они будут влиять на правильность и скорость подхода. В этом подходе есть две фатальные ошибки. Первый заключается в том, чтобы удалить оцениваемый объект не на основе его индекса в массиве, а скорее с помощью метода 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 секунд. Поэтому я бы предложил использовать блок-подход, поскольку он более безопасен и чувствует себя более идиоматичным.

Ответ 2

Предполагая, что вы имеете дело с изменяемым массивом и не сортируются/индексируются (т.е. вам приходится сканировать через массив), вы можете перебирать массив в обратном порядке, используя enumerateObjectsWithOptions с опцией NSEnumerationReverse:

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

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

Ответ 3

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/

Ответ 4

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

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

Ответ 5

попробуйте это,

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

Ответ 6

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

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

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 и Guy English). По-видимому, нет, но близко, нужно использовать KVC и NSPredicate в этой несколько словной строке:

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

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

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