ios - Eliminar un elemento de una matriz en Objective-C
nsstring nsmutablearray (6)
Hay dos enfoques generales. Podemos probar cada elemento y luego eliminar el elemento inmediatamente si cumple con los criterios de la prueba, o podemos probar cada elemento y almacenar los índices de los elementos que cumplen con los criterios de la prueba, y luego eliminar todos los elementos a la vez. Con el uso de la memoria como una preocupación real, los requisitos de almacenamiento para este último enfoque pueden hacer que sea indeseable.
Con el enfoque "almacenar todos los índices para eliminar, luego eliminarlos" fuera de la tabla, debemos considerar los detalles involucrados en el enfoque anterior y cómo afectarán la corrección y la velocidad del enfoque. Hay dos errores fatales esperando en este enfoque. El primero es eliminar el objeto evaluado no basado en su índice en la matriz, sino con el método removeObject:
. removeObject:
realiza una búsqueda lineal de la matriz para encontrar el objeto a eliminar. Con un conjunto de datos grande, sin clasificar, esto destruirá nuestro rendimiento a medida que el tiempo aumenta con el cuadrado del tamaño de entrada. Por cierto, el uso de indexOfObject:
y luego removeObjectAtIndex:
es igual de malo, por lo que también debemos evitarlo. El segundo error grave sería comenzar nuestra iteración en el índice 0. NSMutableArray
reorganiza los índices después de agregar o eliminar un objeto, por lo que si comenzamos con el índice 0, se nos garantizará una excepción de índice fuera de límites si se elimina un solo objeto durante la iteración Por lo tanto, debemos comenzar desde la parte posterior de la matriz y solo eliminar los objetos que tienen índices más bajos que todos los índices que hemos verificado hasta ahora.
Habiendo establecido eso, realmente hay dos opciones obvias: un bucle for
que comienza al final en lugar del principio de la matriz, o el método NSArray
enumerateObjectsWithOptions:usingBlock:
method. Ejemplos de cada uno siguen:
[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];
}
}
Mis pruebas parecen mostrar el bucle for
ligeramente más rápido, tal vez aproximadamente un cuarto de segundo más rápido para 500,000 elementos, que es una diferencia entre 8,5 y 8,25 segundos, básicamente. Por lo tanto, sugiero utilizar el enfoque de bloque, ya que es más seguro y se siente más idiomático.
Esta pregunta puede parecer fácil, pero estoy buscando la forma más eficiente y fácil de recordar.
Digamos que tengo una serie de objetos Person. Cada persona tiene un color de pelo representado por un NSString
. Entonces digamos que quiero eliminar todos los objetos Person de la matriz donde el color de su cabello es marrón.
¿Cómo hago esto?
Tenga en cuenta que no puede eliminar un objeto de una matriz que se está enumerando.
La clave es usar predicados para filtrar la matriz. Vea el código de abajo;
- (NSArray*)filterArray:(NSArray*)list
{
return [list filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings){
People *currentObj = (People*)evaluatedObject;
return (![currentObj.hairColour isEqualToString:@"brown"]);
}]];
}
Si está haciendo una copia de una matriz con ciertos elementos filtrados, cree una nueva matriz mutable, repita la iteración sobre el original y agregue la copia sobre la marcha, como otros han sugerido para esta respuesta. Pero su pregunta dice que la eliminación de una matriz existente (probablemente mutable).
Mientras realiza la iteración, puede crear una matriz de objetos para eliminar y luego eliminarlos después:
NSMutableArray *thePeople = ...
NSString *hairColorToMatch = ...
NSMutableArray *matchingObjects = [NSMutableArray array];
for (People *person in thePeople) {
if (person.hairColor isEqualToString:hairColorToMatch])
[matchingObjects addObject:person];
[thePeople removeObjects:matchingObjects];
Pero esto crea una matriz temporal que podría pensar que es un desperdicio y, lo que es más importante, es difícil ver removeObjects:
es muy eficiente. Además, alguien mencionó algo sobre los arreglos con elementos duplicados, esto debería funcionar en ese caso pero no sería el mejor, con cada duplicado también en el arreglo temporal y la coincidencia redundante dentro de removeObjects:
Uno puede iterar por índice en su lugar y eliminarlo a medida que avanza, pero eso hace que la lógica del bucle sea algo incómoda. En su lugar, recopilaría los índices en un conjunto de índices y, de nuevo, eliminaría después:
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];
Creo que los conjuntos de índices tienen una sobrecarga muy baja, por lo que es casi tan eficiente como la que obtendrás y difícil de arruinar. Otra cosa acerca de la eliminación en un lote como este es que es posible que Apple haya optimizado removeObjectsAtIndexes:
para ser mejor que una secuencia de removeObjectAtIndex:
Así que incluso con la sobrecarga de crear la estructura de datos del conjunto de índices, esto puede superar la eliminación sobre la marcha mientras se itera. Este también funciona bastante bien si la matriz tiene duplicados.
Si, en cambio, realmente estás haciendo una copia filtrada, entonces pensé que había algún operador de colección de KVC
que pudieras usar (estuve leyendo sobre eso recientemente, puedes hacer algunas locuras con los que están de acuerdo con NSHipster y Guy English ). Aparentemente no, pero cerca, uno necesita usar KVC y NSPredicate en esta línea un tanto prolija:
NSArray *subsetOfPeople = [allPeople filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:@"SELF.hairColor != %@", hairColorToMatch]];
filterWithFormat:
y cree una categoría en NSArray
para hacer las cosas más concisas para su código, filterWithFormat:
o algo así.
(todo no probado, escrito directamente en SO)
Suponiendo que está tratando con una matriz mutable y no está ordenada / indexada (es decir, tiene que escanear la matriz), puede recorrer la matriz en orden inverso utilizando enumerateObjectsWithOptions
con la opción NSEnumerationReverse
:
[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// now you can remove the object without affecting the enumeration
}];
Al ir en orden inverso, puede eliminar un objeto de la matriz que se está enumerando.
prueba de esta manera,
NSIndexSet *indices = [personsArray indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
return [[obj objectForKey:@"hair"] isEqual:@"Brown Hair"];
}];
NSArray *filtered = [personsArray objectsAtIndexes:indices];
O
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.hair=%@ ",@"Brown Hair"];
NSArray* myArray = [personsArray filteredArrayUsingPredicate:predicate];
NSLog(@"%@",myArray);
NSMutableArray * tempArray = [self.peopleArray mutableCopy];
for (Person * person in peopleArray){
if ([person.hair isEqualToString: @"Brown Hair"])
[tempArray removeObject: person]
}
self.peopleArray = tempArray;
O NSPredicate también funciona: http://nshipster.com/nspredicate/