199 votes

Meilleure façon de supprimer d'un NSMutableArray tout en itérant ?

En Cocoa, si je veux parcourir un NSMutableArray et supprimer plusieurs objets qui répondent à certains critères, quelle est la meilleure façon de le faire sans redémarrer la boucle à chaque fois que je supprime un objet?

Merci,

Éditer : Juste pour clarifier - je cherchais la meilleure façon, par exemple quelque chose de plus élégant que de mettre à jour manuellement l'index où je me trouve. Par exemple en C++, je peux faire ;

iterator it = someList.begin();

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

9 votes

Bouclez de l'arrière vers l'avant.

0 votes

Personne ne répond à la question "POURQUOI"

1 votes

@HotLicks Un de mes préférés de tous les temps et la solution la plus sous-estimée en programmation en général :D

392voto

Christopher Ashworth Points 2450

Pour plus de clarté, j'aime faire une boucle initiale où je rassemble les éléments à supprimer. Ensuite, je les supprime. Voici un exemple utilisant la syntaxe Objective-C 2.0 :

NSMutableArray *discardedItems = [NSMutableArray array];

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

[originalArrayOfItems removeObjectsInArray:discardedItems];

Ensuite, il n'y a aucune question sur le bon fonctionnement de la mise à jour des indices, ou sur d'autres petits détails de gestion.

Édité pour ajouter :

Il a été noté dans d'autres réponses que la formulation inverse pourrait être plus rapide. Par exemple, si vous parcourez le tableau et composez un nouveau tableau d'objets à garder, au lieu d'objets à supprimer. Cela peut être vrai (mais qu'en est-il du coût en mémoire et en traitement de l'allocation d'un nouveau tableau, et de la suppression de l'ancien ?), mais même si c'est plus rapide, ce n'est peut-être pas aussi important que pour une implémentation naïve, car les NSArrays ne se comportent pas comme des tableaux "normaux". Ils parlent le langage mais marchent différemment. Voir une bonne analyse ici :

La formulation inverse peut être plus rapide, mais je n'ai jamais eu besoin de me soucier de savoir si c'était le cas, car la formulation ci-dessus a toujours été suffisamment rapide pour mes besoins.

Pour moi, le message à retenir est d'utiliser la formulation la plus claire pour vous. Optimisez seulement si nécessaire. Personnellement, je trouve la formulation ci-dessus la plus claire, c'est pourquoi je l'utilise. Mais si la formulation inverse est plus claire pour vous, allez-y.

48 votes

Soyez prudent car cela pourrait créer des bugs si les objets apparaissent plus d'une fois dans un tableau. En tant qu'alternative, vous pourriez utiliser un NSMutableIndexSet et -(void)removeObjectsAtIndexes.

1 votes

Il est en réalité plus rapide de faire l'inverse. La différence de performance est assez importante, mais si votre tableau n'est pas si grand, alors les temps seront négligeables de toute façon.

0 votes

J'ai utilisé revers... a fait du tableau un nul... puis j'ai seulement ajouté ce que je voulais et rechargé les données... fait... créé dummyArray qui est le miroir du tableau principal pour avoir les données principales réelles

83voto

Corey Floyd Points 16747

Une autre variation. Ainsi, vous obtenez lisibilité et bonnes performances:

NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet];
SomeObjectClass *item;
NSUInteger index = 0;

for (item in originalArrayOfItems) {
    if ([item shouldBeDiscarded])
        [discardedItems addIndex:index];
    index++;
}

[originalArrayOfItems removeObjectsAtIndexes:discardedItems];

0 votes

C'est génial. J'essayais de supprimer plusieurs éléments du tableau et ça a parfaitement fonctionné. D'autres méthodes causaient des problèmes :) Merci mec

0 votes

Corey, cette réponse (ci-dessous) a dit que, removeObjectsAtIndexes est la pire méthode pour supprimer les objets, êtes-vous d'accord avec cela ? Je demande cela car votre réponse est désormais ancienne. Est-il toujours bon de choisir le meilleur ?

0 votes

J'aime beaucoup cette solution en termes de lisibilité. Comment se compare-t-elle en termes de performances à la réponse de @HotLicks?

49voto

Hot Licks Points 25075

C'est un problème très simple. Il suffit d'itérer en arrière :

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

C'est un motif très courant.

0 votes

Aurait manqué la réponse de Jens, car il n'a pas écrit le code :P.. merci l'ami

3 votes

Il s'agit de la meilleure méthode. Il est important de se rappeler que la variable d'itération doit être signée, bien que les indices de tableau en Objective-C soient déclarés comme non signés (je peux imaginer à quel point Apple le regrette maintenant).

40voto

benzado Points 36367

Certaines des autres réponses pourraient avoir de mauvaises performances sur des tableaux très grands, car des méthodes comme removeObject: et removeObjectsInArray: impliquent une recherche linéaire du récepteur, ce qui est une perte de temps car vous savez déjà où se trouve l'objet. De plus, tout appel à removeObjectAtIndex: devra copier les valeurs de l'index à la fin du tableau un emplacement à la fois.

Plus efficace serait ce qui suit :

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

Comme nous avons défini la capacité de itemsToKeep, nous ne perdons pas de temps à copier des valeurs lors d'un redimensionnement. Nous ne modifions pas le tableau en place, donc nous sommes libres d'utiliser la numération rapide. Utiliser setArray: pour remplacer le contenu de array par itemsToKeep sera efficace. Selon votre code, vous pourriez même remplacer la dernière ligne par :

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

Il n'est donc même pas nécessaire de copier les valeurs, il suffit de remplacer un pointeur.

0 votes

Cet algo se révèle être bon en termes de complexité temporelle. J'essaie de le comprendre en termes de complexité spatiale. J'ai la même implémentation en cours où je définis la capacité pour 'itemsToKeep' avec le 'Array count' réel. Mais disons que je ne veux pas de quelques objets du tableau donc je ne les ajoute pas à itemsToKeep. Donc la capacité de mes itemsToKeep est de 10, mais je stocke en réalité seulement 6 objets dedans. Cela signifie-t-il que je gaspille de l'espace pour 4 objets ? P.S. Je ne trouve pas de défauts, j'essaie juste de comprendre la complexité de l'algo. :)

0 votes

Oui, vous avez de l'espace réservé pour quatre pointeurs que vous n'utilisez pas. Gardez à l'esprit que le tableau contient uniquement des pointeurs, pas les objets eux-mêmes, donc pour quatre objets cela signifie 16 octets (architecture 32 bits) ou 32 octets (64 bits).

0 votes

Notez que toutes les solutions nécessiteront au mieux aucun espace supplémentaire (vous supprimez des éléments sur place) ou au pire doubler la taille initiale du tableau (car vous copiez le tableau). Encore une fois, comme nous travaillons avec des pointeurs et non des copies complètes d'objets, cela est assez peu coûteux dans la plupart des cas.

28voto

Vous pouvez utiliser NSPredicate pour supprimer des éléments de votre tableau mutable. Cela ne nécessite pas de boucles for.

Par exemple, si vous avez un NSMutableArray de noms, vous pouvez créer un prédicat comme celui-ci:

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

La ligne suivante ne laissera dans votre tableau que les noms commençant par b.

[namesArray filterUsingPredicate:caseInsensitiveBNames];

Si vous avez du mal à créer les prédicats dont vous avez besoin, utilisez ce lien du développeur Apple.

3 votes

Nécessite aucun visible pour les boucles. Il y a une boucle dans l'opération de filtrage, vous ne la voyez tout simplement pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X