objective-c - programación - versiones de objective c
¿La mejor manera de eliminar de NSMutableArray mientras se itera? (20)
¿Por qué no agrega los objetos que se eliminarán a otro NSMutableArray? Cuando haya finalizado la iteración, puede eliminar los objetos que ha recopilado.
En Cocoa, si deseo realizar un ciclo a través de un NSMutableArray y eliminar varios objetos que cumplan con ciertos criterios, ¿cuál es la mejor manera de hacerlo sin reiniciar el ciclo cada vez que elimino un objeto?
Gracias,
Edición: Solo para aclarar: estaba buscando la mejor manera, por ejemplo, algo más elegante que actualizar manualmente el índice en el que estoy. Por ejemplo en C ++ puedo hacer;
iterator it = someList.begin();
while (it != someList.end())
{
if (shouldRemove(it))
it = someList.erase(it);
}
¿Qué hay de intercambiar los elementos que desea eliminar con el ''n'' elemento, ''n-1'' elemento y así sucesivamente?
Cuando haya terminado, cambie el tamaño de la matriz al ''tamaño anterior - número de swaps''
Agregue los objetos que desea eliminar a una segunda matriz y, después del bucle, use -removeObjectsInArray :.
Algunas de las otras respuestas tendrían un rendimiento deficiente en arreglos muy grandes, porque los métodos como removeObject:
y removeObjectsInArray:
implican hacer una búsqueda lineal del receptor, que es un desperdicio porque ya sabe dónde está el objeto. Además, cualquier llamada a removeObjectAtIndex:
tendrá que copiar los valores del índice al final de la matriz en una ranura a la vez.
Más eficiente sería el siguiente:
NSMutableArray *array = ...
NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];
for (id object in array) {
if (! shouldRemove(object)) {
[itemsToKeep addObject:object];
}
}
[array setArray:itemsToKeep];
Debido a que configuramos la capacidad de itemsToKeep
, no perdemos tiempo copiando valores durante un cambio de tamaño. No modificamos la matriz en su lugar, por lo que somos libres de usar la enumeración rápida. Usando setArray:
para reemplazar el contenido de la array
con itemsToKeep
será eficiente. Dependiendo de su código, incluso podría reemplazar la última línea con:
[array release];
array = [itemsToKeep retain];
Así que ni siquiera es necesario copiar valores, solo intercambiar un puntero.
Aquí está la manera fácil y limpia. Me gusta duplicar mi matriz directamente en la llamada de enumeración rápida:
for (LineItem *item in [NSArray arrayWithArray:self.lineItems])
{
if ([item.toBeRemoved boolValue] == YES)
{
[self.lineItems removeObject:item];
}
}
De esta manera, se enumera a través de una copia de la matriz que se está eliminando, ambos con los mismos objetos. Un NSArray contiene punteros a objetos solo por lo que esta es una memoria / rendimiento totalmente finos.
Cualquiera de los dos bucles de cuenta regresiva sobre los índices:
for (NSInteger i = array.count - 1; i >= 0; --i) {
o hacer una copia con los objetos que desea conservar.
En particular, no utilice un bucle for (id object in array)
o NSEnumerator
.
De una manera más declarativa, según los criterios que coincidan con los elementos a eliminar, podría usar:
[theArray filterUsingPredicate:aPredicate]
@Nathan debería ser muy eficiente
Defino una categoría que me permite filtrar usando un bloque, como este:
@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 luego se puede utilizar de esta manera:
[myMutableArray filterUsingTest:^BOOL(id obj, NSUInteger idx) {
return [self doIWantToKeepThisObject:obj atIndex:idx];
}];
Este es un problema muy simple. Usted simplemente itera hacia atrás:
for (NSInteger i = array.count - 1; i >= 0; i--) {
ElementType* element = array[i];
if ([element shouldBeRemoved]) {
[array removeObjectAtIndex:i];
}
}
Este es un patrón muy común.
Hice una prueba de rendimiento utilizando 4 métodos diferentes. Cada prueba iteró a través de todos los elementos en una matriz de 100,000 elementos, y se eliminó cada quinto elemento. Los resultados no variaron mucho con / sin optimización. Estos se hicieron en un iPad 4:
(1) removeObjectAtIndex:
- 271 ms
(2) removeObjectsAtIndexes:
- 1010 ms (porque la creación del conjunto de índices toma ~ 700 ms; de lo contrario, esto es básicamente lo mismo que llamar a removeObjectAtIndex: para cada elemento)
(3) removeObjects:
- 326 ms
(4) haga una nueva matriz con objetos que pasen la prueba - 17 ms
Por lo tanto, crear una nueva matriz es, con mucho, el más rápido. Los otros métodos son todos comparables, excepto que el uso de removeObjectsAtIndexes: será peor con más elementos para eliminar, debido al tiempo necesario para crear el conjunto de índices.
Hoy en día se puede utilizar la enumeración basada en bloques invertida. Un código de ejemplo simple:
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;
}
)
Otra opción con solo una línea de código:
[array filterUsingPredicate:[NSPredicate predicateWithFormat:@"shouldDelete == NO"]];
Iterar hacia atrás fue mi favorito durante años, pero durante mucho tiempo nunca encontré el caso donde el objeto "más profundo" (conteo más alto) se eliminó primero. Momentáneamente, antes de que el puntero pase al siguiente índice, no hay nada y se bloquea.
El modo de Benzado es el más cercano a lo que hago ahora, pero nunca me di cuenta de que habría una reorganización de la pila después de cada eliminación.
bajo Xcode 6 esto 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];
La respuesta anterior de Benzado es lo que debe hacer para la preformación. En una de mis aplicaciones, removeObjectsInArray tomó un tiempo de ejecución de 1 minuto, solo agregar a una nueva matriz tomó .023 segundos.
Para iOS 4+ o OS X 10.6+, Apple agregó la serie de API que NSMutableArray
en NSMutableArray
, como – indexesOfObjectsPassingTest:
Una solución con dicha API sería:
NSIndexSet *indexesToBeRemoved = [someList indexesOfObjectsPassingTest:
^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [self shouldRemove:obj];
}];
[someList removeObjectsAtIndexes:indexesToBeRemoved];
Para mayor claridad, me gusta hacer un bucle inicial donde recolecto los elementos para eliminar. Luego los borro. Aquí hay una muestra usando la sintaxis de Objective-C 2.0:
NSMutableArray *discardedItems = [NSMutableArray array];
for (SomeObjectClass *item in originalArrayOfItems) {
if ([item shouldBeDiscarded])
[discardedItems addObject:item];
}
[originalArrayOfItems removeObjectsInArray:discardedItems];
Entonces no hay duda sobre si los índices se están actualizando correctamente u otros pequeños detalles de la contabilidad.
Editado para añadir:
Se ha observado en otras respuestas que la formulación inversa debería ser más rápida. es decir, si recorres la matriz y compones una nueva matriz de objetos para mantener, en lugar de objetos para descartar. Eso puede ser cierto (aunque ¿qué pasa con la memoria y el costo de procesamiento de asignar una nueva matriz y descartar la antigua?) Pero incluso si es más rápido, puede que no sea tan importante como lo sería para una implementación ingenua, porque NSArrays No se comporten como matrices "normales". Hablan la conversación pero caminan un paseo diferente. Vea un buen análisis aquí:
La formulación inversa puede ser más rápida, pero nunca tuve que preocuparme si lo es, porque la formulación anterior siempre ha sido lo suficientemente rápida para mis necesidades.
Para mí, el mensaje para llevar a casa es usar la formulación que sea más clara para usted. Optimizar solo si es necesario. Personalmente, encuentro la formulación anterior más clara, por lo que la uso. Pero si la formulación inversa es más clara para ti, hazlo.
Puede usar NSpredicate para eliminar elementos de su matriz mutable. Esto requiere no para bucles.
Por ejemplo, si tiene un NSMutableArray de nombres, puede crear un predicado como este:
NSPredicate *caseInsensitiveBNames =
[NSPredicate predicateWithFormat:@"SELF beginswith[c] ''b''"];
La siguiente línea lo dejará con una matriz que contiene solo nombres que comienzan con b.
[namesArray filterUsingPredicate:caseInsensitiveBNames];
Si tiene problemas para crear los predicados que necesita, use este enlace para desarrolladores de Apple .
Si todos los objetos en su matriz son únicos o si desea eliminar todas las apariciones de un objeto cuando lo encuentre, podría enumerar rápidamente en una copia de matriz y usar [NSMutableArray removeObject:] para eliminar el objeto del original.
NSMutableArray *myArray;
NSArray *myArrayCopy = [NSArray arrayWithArray:myArray];
for (NSObject *anObject in myArrayCopy) {
if (shouldRemove(anObject)) {
[myArray removeObject:anObject];
}
}
Una implementación más agradable podría ser utilizar el método de categoría a continuación en 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
El bloque de predicado se puede implementar para procesar en cada objeto de la matriz. Si el predicado devuelve verdadero, el objeto se elimina.
Un ejemplo de una matriz de fechas para eliminar todas las fechas que se encuentran en el pasado:
NSMutableArray *dates = ...;
[dates removeObjectsWithPredicate:^BOOL(id obj) {
NSDate *date = (NSDate *)obj;
return [date timeIntervalSinceNow] < 0;
}];
Una variación más. Así obtienes legibilidad y buen rendimiento:
NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet];
SomeObjectClass *item;
NSUInteger index = 0;
for (item in originalArrayOfItems) {
if ([item shouldBeDiscarded])
[discardedItems addIndex:index];
index++;
}
[originalArrayOfItems removeObjectsAtIndexes:discardedItems];
esto debería hacerlo:
NSMutableArray* myArray = ....;
int i;
for(i=0; i<[myArray count]; i++) {
id element = [myArray objectAtIndex:i];
if(element == ...) {
[myArray removeObjectAtIndex:i];
i--;
}
}
espero que esto ayude...