web-dev-qa-db-fra.com

Supprimer un élément d'un tableau dans Objective-C

Cette question peut sembler facile, mais je recherche le moyen le plus efficace et le plus convivial pour la mémoire.

Disons que j'ai un tableau d'objets Person. Chaque personne a une couleur de cheveux représentée par un NSString. Supposons ensuite que je souhaite supprimer tous les objets Person du tableau où leur couleur de cheveux est brune.

Comment puis-je faire cela?

N'oubliez pas que vous ne pouvez pas supprimer un objet d'un tableau en cours d'énumération.

21
Choppin Broccoli

Il existe deux approches générales. Nous pouvons tester chaque élément, puis supprimer immédiatement l'élément s'il répond aux critères de test, ou nous pouvons tester chaque élément et stocker les index des éléments répondant aux critères de test, puis supprimer tous ces éléments à la fois. L'utilisation de la mémoire étant une préoccupation réelle, les exigences de stockage pour cette dernière approche peuvent la rendre indésirable.

Avec l'approche "stocker tous les index à supprimer, puis les supprimer", nous devons prendre en compte les détails impliqués dans l'ancienne approche, et comment ils affecteront l'exactitude et la vitesse de l'approche. Il y a deux erreurs fatales en attente dans cette approche. La première consiste à supprimer l'objet évalué non pas en fonction de son index dans le tableau, mais plutôt avec le removeObject: méthode. removeObject: effectue une recherche linéaire du tableau pour trouver l'objet à supprimer. Avec un grand ensemble de données non triées, cela détruira nos performances à mesure que le temps augmente avec le carré de la taille d'entrée. Par ailleurs, en utilisant indexOfObject: puis removeObjectAtIndex: est tout aussi mauvais, nous devons donc également l'éviter. La deuxième erreur fatale serait de commencer notre itération à l'index 0. NSMutableArray réorganise les index après avoir ajouté ou supprimé un objet, donc si nous commençons par l'index 0, nous serons garantis une exception d'index hors limites si même un objet est supprimé lors de l'itération. Donc, nous devons commencer à l'arrière du tableau et ne supprimer que les objets qui ont des index inférieurs à tous les index que nous avons vérifiés jusqu'à présent.

Après avoir exposé cela, il y a vraiment deux choix évidents: une boucle for qui commence à la fin plutôt qu'au début du tableau, ou la méthode NSArrayenumerateObjectsWithOptions:usingBlock: méthode. Des exemples de chacun suivent:

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

Mes tests semblent montrer que la boucle for est légèrement plus rapide - peut-être environ un quart de seconde plus rapide pour 500 000 éléments, ce qui représente une différence entre 8,5 et 8,25 secondes, en gros. Je suggère donc d'utiliser l'approche par bloc, car elle est plus sûre et semble plus idiomatique.

28
Carl Veazey

En supposant que vous avez affaire à un tableau mutable et qu'il n'est pas trié/indexé (c'est-à-dire que vous devez parcourir le tableau), vous pouvez parcourir le tableau dans l'ordre inverse en utilisant enumerateObjectsWithOptions = avec l'option NSEnumerationReverse:

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

En allant dans l'ordre inverse, vous pouvez supprimer un objet du tableau en cours d'énumération.

14
Rob
NSMutableArray * tempArray = [self.peopleArray mutableCopy];

for (Person * person in peopleArray){

 if ([person.hair isEqualToString: @"Brown Hair"])
     [tempArray removeObject: person]

}

self.peopleArray = tempArray;

Ou NSPredicate fonctionne également: http://nshipster.com/nspredicate/

6
AdamG

La clé est d'utiliser des prédicats pour filtrer les tableaux. Voir le code ci-dessous;

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

Si vous faites une copie d'un tableau avec certains éléments filtrés, créez un nouveau tableau mutable, parcourez l'original et ajoutez-le à la volée, comme d'autres l'ont suggéré pour cette réponse. Mais votre question portait sur la suppression d'un tableau existant (vraisemblablement mutable).

Pendant l'itération, vous pouvez créer un tableau d'objets à supprimer, puis les supprimer ensuite:

NSMutableArray *thePeople = ...
NSString *hairColorToMatch = ...

NSMutableArray *matchingObjects = [NSMutableArray array];
for (People *person in thePeople) {
  if (person.hairColor isEqualToString:hairColorToMatch])
    [matchingObjects addObject:person];
[thePeople removeObjects:matchingObjects];

Mais cela crée un tableau temporaire que vous pourriez penser être un gaspillage et, plus important encore, il est difficile de voir removeObjects: étant très efficace. En outre, quelqu'un a mentionné quelque chose à propos des tableaux avec des éléments en double, cela devrait fonctionner dans ce cas, mais ce ne serait pas le meilleur, avec chaque doublon également dans le tableau temporaire et la correspondance redondante dans removeObjects:.

On peut itérer par index à la place et supprimer au fur et à mesure, mais cela rend la logique de boucle plutôt maladroite. Au lieu de cela, je collecterais les index dans un ensemble d'index et les supprimerais ensuite:

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

Je pense que les jeux d'index ont des frais généraux très bas, donc c'est presque aussi efficace que vous aurez et difficile à foutre. Une autre chose à propos de la suppression d'un lot à la fin comme ceci est qu'il est possible que Apple a optimisé removeObjectsAtIndexes: pour être meilleur qu'une séquence de removeObjectAtIndex:. Ainsi, même avec la surcharge de création de la structure de données de l'ensemble d'index, cela peut être plus efficace que la suppression à la volée lors de l'itération. Celui-ci fonctionne assez bien aussi si le tableau a des doublons.

Si au lieu de cela, vous faites vraiment une copie filtrée, alors je pensais qu'il y avait un opérateur de collection KVC que vous pouvez utiliser (je lisais à ce sujet récemment, vous pouvez faire des choses folles avec ceux-ci selon NSHipster & Guy anglais ). Apparemment non, mais à proximité, il faut utiliser KVC et NSPredicate dans cette ligne quelque peu verbeuse:

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

Allez-y et créez une catégorie sur NSArray pour rendre les choses plus concises pour votre code, filterWithFormat: ou quelque chose.

(tous non testés, saisis directement dans SO)

3
Pierre Houston

essayez comme ça,

        NSIndexSet *indices = [personsArray indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
            return [[obj objectForKey:@"hair"] isEqual:@"Brown Hair"];
        }];
         NSArray *filtered = [personsArray objectsAtIndexes:indices];

OR

        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.hair=%@ ",@"Brown Hair"];
        NSArray*   myArray = [personsArray filteredArrayUsingPredicate:predicate];
        NSLog(@"%@",myArray);
3
Balu