Il modo migliore per rimuovere da NSMutableArray durante l’iterazione?

In Cocoa, se voglio scorrere un NSMutableArray e rimuovere più oggetti che soddisfano determinati criteri, qual è il modo migliore per farlo senza riavviare il ciclo ogni volta che rimuovo un object?

Grazie,

Modifica: solo per chiarire: cercavo il modo migliore, ad esempio qualcosa di più elegante dell’aggiornamento manuale dell’indice in cui mi trovo. Per esempio in C ++ posso fare;

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

Per chiarezza mi piace fare un ciclo iniziale in cui raccolgo gli oggetti da eliminare. Quindi li cancello. Ecco un esempio usando la syntax Objective-C 2.0:

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

Quindi non ci sono dubbi sul fatto che gli indici vengano aggiornati correttamente o su altri piccoli dettagli di contabilità.

Modificato per aggiungere:

È stato notato in altre risposte che la formulazione inversa dovrebbe essere più veloce. cioè Se si scorre l’array e si compone una nuova serie di oggetti da conservare, anziché oggetti da scartare. Può essere vero (anche se riguarda la memoria e il costo di elaborazione di allocare un nuovo array e scartare quello vecchio?) Ma anche se è più veloce potrebbe non essere un grosso problema come sarebbe per un’implementazione ingenua, perché gli NSArrays non comportarti come “normali” matrici. Parlano le chiacchiere, ma camminano per una passeggiata diversa. Vedi una buona analisi qui:

La formulazione inversa può essere più veloce, ma non ho mai avuto bisogno di preoccuparmi se lo è, perché la formulazione di cui sopra è sempre stata abbastanza veloce per le mie esigenze.

Per me il messaggio da portare a casa è usare qualsiasi formulazione sia più chiara per te. Ottimizza solo se necessario. Personalmente trovo la formulazione sopra più chiara, ed è per questo che la uso. Ma se la formulazione inversa ti è più chiara, fallo.

Un’altra variazione. Quindi ottieni leggibilità e buona performance:

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

Questo è un problema molto semplice. Basta scorrere all’indietro:

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

Questo è un modello molto comune.

Alcune delle altre risposte avrebbero prestazioni scadenti su array molto grandi, perché metodi come removeObject: e removeObjectsInArray: comportano una ricerca lineare del ricevitore, che è uno spreco perché sai già dove si trova l’object. Inoltre, qualsiasi chiamata a removeObjectAtIndex: dovrà copiare i valori dall’indice alla fine dell’array di uno slot alla volta.

Più efficiente sarebbe il seguente:

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

Poiché impostiamo la capacità di itemsToKeep , non perdiamo tempo a copiare i valori durante il ridimensionamento. Non modifichiamo la matrice sul posto, quindi siamo liberi di usare Enumerazione veloce. Usare setArray: per sostituire il contenuto array con itemsToKeep sarà efficiente. A seconda del tuo codice, potresti persino sostituire l’ultima riga con:

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

Quindi non c’è nemmeno bisogno di copiare valori, basta scambiare un puntatore.

È ansible utilizzare NSpredicate per rimuovere elementi dall’array mutabile. Questo non richiede cicli di apertura.

Ad esempio, se hai un NSMutableArray di nomi, puoi creare un predicato come questo:

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

La seguente riga ti lascerà con un array che contiene solo nomi che iniziano con b.

 [namesArray filterUsingPredicate:caseInsensitiveBNames]; 

Se hai problemi a creare i predicati di cui hai bisogno, usa questo link per sviluppatori Apple .

O utilizzare il ciclo di conto alla rovescia su indici:

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

oppure creane una copia con gli oggetti che vuoi conservare.

In particolare, non utilizzare un ciclo for (id object in array) o NSEnumerator .

Ho fatto un test delle prestazioni usando 4 metodi diversi. Ogni test ha iterato attraverso tutti gli elementi in un array di 100.000 elementi e rimosso ogni 5 ° elemento. I risultati non variano molto con / senza ottimizzazione. Questi sono stati fatti su un iPad 4:

(1) removeObjectAtIndex:271 ms

(2) removeObjectsAtIndexes:1010 ms (perché la creazione del set di indici richiede ~ 700 ms, altrimenti è praticamente la stessa di chiamare removeObjectAtIndex: per ogni elemento)

(3) removeObjects:326 ms

(4) creare un nuovo array con oggetti che superano il test – 17 ms

Quindi, creare un nuovo array è di gran lunga il più veloce. Gli altri metodi sono tutti paragonabili, tranne che usando removeObjectsAtIndexes: sarà peggio con più elementi da rimuovere, a causa del tempo necessario per creare il set di indici.

Per iOS 4+ o OS X 10.6+, Apple ha aggiunto la serie di API passingTest in NSMutableArray , come – indexesOfObjectsPassingTest: Una soluzione con tale API sarebbe:

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

Al giorno d’oggi è ansible utilizzare l’enumerazione basata su blocchi invertiti. Un semplice codice di esempio:

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

Risultato:

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

un’altra opzione con solo una riga di codice:

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

In un modo più dichiarativo, in base ai criteri che corrispondono agli elementi da rimuovere potresti utilizzare:

 [theArray filterUsingPredicate:aPredicate] 

@Nathan dovrebbe essere molto efficiente

Ecco il modo semplice e pulito. Mi piace duplicare il mio array proprio nella chiamata di enumerazione veloce:

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

In questo modo si enumera attraverso una copia dell’array da cui viene cancellato, entrambi con gli stessi oggetti. Un NSArray mantiene solo i puntatori agli oggetti, quindi questo è assolutamente soddisfacente per quanto riguarda la memoria e le prestazioni.

Aggiungi gli oggetti che vuoi rimuovere ad un secondo array e, dopo il ciclo, usa -removeObjectsInArray :.

questo dovrebbe farlo:

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

spero che questo ti aiuti...

Perché non aggiungere gli oggetti da rimuovere a un altro NSMutableArray. Quando hai finito di ripetere, puoi rimuovere gli oggetti che hai raccolto.

Che ne dici di scambiare gli elementi che vuoi eliminare con l ” n’elemento, ‘n-1 ° elemento e così via?

Quando hai finito ridimensiona la matrice alla ‘dimensione precedente – numero di swap’

Se tutti gli oggetti dell’array sono univoci o si desidera rimuovere tutte le occorrenze di un object quando trovato, è ansible eseguire rapidamente l’enumerazione su una copia dell’array e utilizzare [NSMutableArray removeObject:] per rimuovere l’object dall’originale.

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

L’ansiere di benzado sopra è quello che dovresti fare per preformace. In una delle mie applicazioni removeObjectsInArray ha impiegato un tempo di esecuzione di 1 minuto, solo l’aggiunta a un nuovo array ha richiesto 0,023 secondi.

Definisco una categoria che mi consente di filtrare utilizzando un blocco, come questo:

 @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 

che può quindi essere usato in questo modo:

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

Un’implementazione migliore potrebbe essere quella di utilizzare il metodo di categoria riportato di seguito su 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 

Il blocco dei predicati può essere implementato per eseguire l’elaborazione su ciascun object dell’array. Se il predicato restituisce true, l’object viene rimosso.

Un esempio per un array di date per rimuovere tutte le date che si trovano nel passato:

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

L’iterazione all’indietro è stata la mia preferita da anni, ma per molto tempo non ho mai visto il caso in cui l’object “più profondo” (il più alto) sia stato rimosso per primo. Momentaneamente prima che il puntatore passasse all’indice successivo non c’è nulla e si blocca.

Il modo di Benzado è il più vicino a quello che faccio ora, ma non ho mai realizzato che ci sarebbe stato il rimpasto dello stack dopo ogni rimozione.

sotto Xcode 6 funziona

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