Melhor maneira de remover do NSMutableArray enquanto iterar?

Em Cocoa, se eu quiser fazer um loop através de um NSMutableArray e remover vários objects que se encheckboxm em um determinado critério, qual é a melhor maneira de fazer isso sem reiniciar o loop toda vez que eu removo um object?

Obrigado,

Edit: Só para esclarecer – Eu estava procurando o melhor caminho, por exemplo, algo mais elegante do que atualizar manualmente o índice que estou. Por exemplo, em C ++ eu posso fazer;

iterator it = someList.begin(); while (it != someList.end()) { if (shouldRemove(it)) it = someList.erase(it); } 

Para maior clareza eu gosto de fazer um loop inicial onde eu coleciono os itens para deletar. Então eu os excluo. Aqui está um exemplo usando a syntax do Objective-C 2.0:

 NSMutableArray *discardedItems = [NSMutableArray array]; for (SomeObjectClass *item in originalArrayOfItems) { if ([item shouldBeDiscarded]) [discardedItems addObject:item]; } [originalArrayOfItems removeObjectsInArray:discardedItems]; 

Então, não há dúvidas sobre se os índices estão sendo atualizados corretamente ou se há outros pequenos detalhes contábeis.

Editado para adicionar:

Tem sido notado em outras respostas que a formulação inversa deve ser mais rápida. ou seja, se você percorrer a matriz e compor uma nova matriz de objects para manter, em vez de objects para descartar. Isso pode ser verdade (embora a memory e o custo de processamento de alocar uma nova matriz e descartar a antiga?), Mas mesmo que seja mais rápido, pode não ser tão grande quanto seria para uma implementação ingênua, porque a NSArrays não se comportem como matrizes “normais”. Eles falam a conversa, mas eles andam um passeio diferente. Veja uma boa análise aqui:

A formulação inversa pode ser mais rápida, mas nunca precisei me importar, porque a formulação acima sempre foi rápida o suficiente para as minhas necessidades.

Para mim, a mensagem para levar para casa é usar qualquer formulação que seja mais clara para você. Otimize apenas se necessário. Eu pessoalmente acho a formulação acima mais clara, e é por isso que eu a uso. Mas se a formulação inversa é mais clara para você, vá em frente.

Mais uma variação. Então você tem legibilidade e boa performance:

 NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet]; SomeObjectClass *item; NSUInteger index = 0; for (item in originalArrayOfItems) { if ([item shouldBeDiscarded]) [discardedItems addIndex:index]; index++; } [originalArrayOfItems removeObjectsAtIndexes:discardedItems]; 

Este é um problema muito simples. Você acabou de repetir:

 for (NSInteger i = array.count - 1; i >= 0; i--) { ElementType* element = array[i]; if ([element shouldBeRemoved]) { [array removeObjectAtIndex:i]; } } 

Este é um padrão muito comum.

Algumas das outras respostas teriam desempenho ruim em matrizes muito grandes, porque methods como removeObject: e removeObjectsInArray: envolvem fazer uma pesquisa linear do receptor, o que é um desperdício porque você já sabe onde está o object. Além disso, qualquer chamada para removeObjectAtIndex: terá que copiar valores do índice para o final da matriz por um slot de cada vez.

Mais eficiente seria o seguinte:

 NSMutableArray *array = ... NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]]; for (id object in array) { if (! shouldRemove(object)) { [itemsToKeep addObject:object]; } } [array setArray:itemsToKeep]; 

Como definimos a capacidade de itemsToKeep , não perdemos tempo a copiar valores durante um redimensionamento. Nós não modificamos o array no lugar, então estamos livres para usar Enumeração Rápida. Usando setArray: para replace o conteúdo do array por itemsToKeep será eficiente. Dependendo do seu código, você pode até replace a última linha por:

 [array release]; array = [itemsToKeep retain]; 

Portanto, não há necessidade de copiar valores, apenas trocar um ponteiro.

Você pode usar NSpredicate para remover itens de sua matriz mutável. Isso requer não for loops.

Por exemplo, se você tem um NSMutableArray de nomes, você pode criar um predicado como este:

 NSPredicate *caseInsensitiveBNames = [NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"]; 

A linha a seguir deixará você com uma matriz que contém apenas os nomes que começam com b.

 [namesArray filterUsingPredicate:caseInsensitiveBNames]; 

Se você tiver problemas para criar os predicados necessários, use este link para desenvolvedores da Apple .

Use a contagem regressiva de loop sobre os índices:

 for (NSInteger i = array.count - 1; i >= 0; --i) { 

ou faça uma cópia com os objects que você deseja manter.

Em particular, não use um loop for (id object in array) ou NSEnumerator .

Eu fiz um teste de desempenho usando 4 methods diferentes. Cada teste foi repetido em todos os elementos em uma matriz de 100.000 elementos e removido a cada 5 item. Os resultados não variaram muito com / sem otimização. Estes foram feitos em um iPad 4:

(1) removeObjectAtIndex:271 ms

(2) removeObjectsAtIndexes:1010 ms (porque a construção do conjunto de índices leva ~ 700 ms; caso contrário, isso é basicamente o mesmo que chamar removeObjectAtIndex: para cada item)

(3) removeObjects:326 ms

(4) faça um novo array com objects passando no teste – 17 ms

Então, criar um novo array é de longe o mais rápido. Os outros methods são todos comparáveis, exceto que usar removeObjectsAtIndexes: será pior com mais itens a serem removidos, devido ao tempo necessário para construir o conjunto de índices.

Para iOS 4+ ou OS X 10.6+, a Apple passingTest série passingTest de APIs no NSMutableArray , como – indexesOfObjectsPassingTest: Uma solução com essa API seria:

 NSIndexSet *indexesToBeRemoved = [someList indexesOfObjectsPassingTest: ^BOOL(id obj, NSUInteger idx, BOOL *stop) { return [self shouldRemove:obj]; }]; [someList removeObjectsAtIndexes:indexesToBeRemoved]; 

Hoje em dia você pode usar a enumeração baseada em blocos invertidos. Um código de exemplo simples:

 NSMutableArray *array = [@[@{@"name": @"a", @"shouldDelete": @(YES)}, @{@"name": @"b", @"shouldDelete": @(NO)}, @{@"name": @"c", @"shouldDelete": @(YES)}, @{@"name": @"d", @"shouldDelete": @(NO)}] mutableCopy]; [array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if([obj[@"shouldDelete"] boolValue]) [array removeObjectAtIndex:idx]; }]; 

Resultado:

 ( { name = b; shouldDelete = 0; }, { name = d; shouldDelete = 0; } ) 

outra opção com apenas uma linha de código:

 [array filterUsingPredicate:[NSPredicate predicateWithFormat:@"shouldDelete == NO"]]; 

De forma mais declarativa, dependendo dos critérios que correspondem aos itens a serem removidos, você pode usar:

 [theArray filterUsingPredicate:aPredicate] 

@ Nathan deve ser muito eficiente

Aqui está o caminho fácil e limpo. Eu gosto de duplicar minha matriz diretamente na chamada de enumeração rápida:

 for (LineItem *item in [NSArray arrayWithArray:self.lineItems]) { if ([item.toBeRemoved boolValue] == YES) { [self.lineItems removeObject:item]; } } 

Desta forma, você enumera através de uma cópia da matriz que está sendo excluída, ambos mantendo os mesmos objects. Um NSArray contém pointers de object apenas, o que faz com que seja uma memory / performance perfeita.

Adicione os objects que você deseja remover a uma segunda matriz e, após o loop, use -removeObjectsInArray :.

Isso deve servir:

  NSMutableArray* myArray = ....; int i; for(i=0; i<[myArray count]; i++) { id element = [myArray objectAtIndex:i]; if(element == ...) { [myArray removeObjectAtIndex:i]; i--; } } 

espero que isto ajude...

Por que você não adiciona os objects a serem removidos para outro NSMutableArray? Quando terminar de iterar, você poderá remover os objects que coletou.

Como sobre a troca dos elementos que você deseja excluir com o ‘n’th elemento,’ n-1’th elemento e assim por diante?

Quando terminar, redimensione a matriz para ‘tamanho anterior – número de trocas’

Se todos os objects em sua matriz forem exclusivos ou se você quiser remover todas as ocorrências de um object quando encontrado, poderá enumerar rapidamente em uma cópia de matriz e usar [NSMutableArray removeObject:] para remover o object do original.

 NSMutableArray *myArray; NSArray *myArrayCopy = [NSArray arrayWithArray:myArray]; for (NSObject *anObject in myArrayCopy) { if (shouldRemove(anObject)) { [myArray removeObject:anObject]; } } 

Anastas de benzado acima é o que você deve fazer para preformace. Em um dos meus aplicativos removeObjectsInArray levou um tempo de execução de 1 minuto, apenas adicionando a um novo array levou 0,023 segundos.

Eu defino uma categoria que me permite filtrar usando um bloco, assim:

 @implementation NSMutableArray (Filtering) - (void)filterUsingTest:(BOOL (^)(id obj, NSUInteger idx))predicate { NSMutableIndexSet *indexesFailingTest = [[NSMutableIndexSet alloc] init]; NSUInteger index = 0; for (id object in self) { if (!predicate(object, index)) { [indexesFailingTest addIndex:index]; } ++index; } [self removeObjectsAtIndexes:indexesFailingTest]; [indexesFailingTest release]; } @end 

que pode então ser usado assim:

 [myMutableArray filterUsingTest:^BOOL(id obj, NSUInteger idx) { return [self doIWantToKeepThisObject:obj atIndex:idx]; }]; 

Uma implementação mais agradável poderia ser usar o método de categoria abaixo no NSMutableArray.

 @implementation NSMutableArray(BMCommons) - (void)removeObjectsWithPredicate:(BOOL (^)(id obj))predicate { if (predicate != nil) { NSMutableArray *newArray = [[NSMutableArray alloc] initWithCapacity:self.count]; for (id obj in self) { BOOL shouldRemove = predicate(obj); if (!shouldRemove) { [newArray addObject:obj]; } } [self setArray:newArray]; } } @end 

O bloco de predicado pode ser implementado para processar em cada object na matriz. Se o predicado retornar true, o object será removido.

Um exemplo de uma matriz de datas para remover todas as datas que estão no passado:

 NSMutableArray *dates = ...; [dates removeObjectsWithPredicate:^BOOL(id obj) { NSDate *date = (NSDate *)obj; return [date timeIntervalSinceNow] < 0; }]; 

Iterar de trás pra frente foi o meu favorito por anos, mas por um longo tempo eu nunca encontrei o caso em que o object mais profundo (maior contagem) foi removido primeiro. Momentaneamente, antes que o ponteiro avance para o próximo índice, não há nada e ele cai.

O caminho do Benzado é o mais próximo do que eu faço agora, mas eu nunca percebi que haveria a remodelação da pilha após cada remoção.

sob Xcode 6 isso funciona

 NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]]; for (id object in array) { if ( [object isNotEqualTo:@"whatever"]) { [itemsToKeep addObject:object ]; } } array = nil; array = [[NSMutableArray alloc]initWithArray:itemsToKeep];