Comment inverser un NSArray dans Objective-C?

356

Je dois inverser mon NSArray .

Par exemple:

[1,2,3,4,5] doit devenir: [5,4,3,2,1]

Quelle est la meilleure façon d'y parvenir?

Andy Jacobs
la source
1
Cela vaut également la peine d'être consulté: http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Collections/Articles/sortingFilteringArrays.html qui vous indique comment trier un tableau dans l'ordre inverse (ce qui est généralement ce que vous faites, par exemple en utilisant un tableau dérivé de NSDictionary # allKeys, et vous voulez que l'ordre inverse date / alpha serve de groupement pour UITable sur iPhone, etc.).

Réponses:

305

Pour obtenir une copie inversée d'un tableau, regardez la solution de danielpunkass en utilisant reverseObjectEnumerator.

Pour inverser un tableau mutable, vous pouvez ajouter la catégorie suivante à votre code:

@implementation NSMutableArray (Reverse)

- (void)reverse {
    if ([self count] <= 1)
        return;
    NSUInteger i = 0;
    NSUInteger j = [self count] - 1;
    while (i < j) {
        [self exchangeObjectAtIndex:i
                  withObjectAtIndex:j];

        i++;
        j--;
    }
}

@end
Georg Schölly
la source
15
L'une des mauvaises choses à propos de l'énumération rapide est que les nouveaux gars comme moi n'apprennent rien de cool comme reverseObjectEnumerator. Une façon assez soignée de le faire.
Brent Royal-Gordon,
4
Parce que les itérateurs C ++ ont une syntaxe encore pire, ils sont moches.
Georg Schölly
4
Ne devriez-vous pas copier le tableau avant de le renvoyer?
CIFilter
12
@Georg: Je suis en désaccord avec vous sur celui-ci. Si je vois une méthode qui renvoie un objet immuable, je m'attends à ce qu'il retourne réellement un objet immuable. Le faire sembler renvoyer un objet immuable mais le retour réel d'un objet mutable est une pratique dangereuse.
Christine
3
La suggestion de reverseObjectEnumerator allObjects n'est pas utile car elle n'inverse pas le tableau mutable, même l'ajout de mutableCopy n'aiderait pas car le tableau d'origine n'est toujours pas muté. Apple documente que l'immuabilité ne doit pas être testée au moment de l'exécution, mais doit être supposée en fonction du type renvoyé, donc renvoyer un NSMutableArray dans ce cas est un code parfaitement correct.
Peter N Lewis
1286

Il existe une solution beaucoup plus simple si vous tirez parti de la reverseObjectEnumeratorméthode intégrée NSArrayet de la allObjectsméthode de NSEnumerator:

NSArray* reversedArray = [[startArray reverseObjectEnumerator] allObjects];

allObjectsest documenté comme renvoyant un tableau avec les objets qui n'ont pas encore été parcourus avec nextObject, dans l'ordre:

Ce tableau contient tous les objets restants de l'énumérateur dans l'ordre énuméré .

danielpunkass
la source
6
Il y a une réponse plus loin ici par Matt Williamson qui devrait être un commentaire: N'utilisez pas la solution de danielpunkass. Je l'ai utilisé en pensant que c'était un excellent raccourci, mais maintenant je viens de passer 3 heures à essayer de comprendre pourquoi mon algorithme A * a été cassé. C'est parce qu'il renvoie le mauvais ensemble!
Georg Schölly
1
Qu'entend-on par «mauvais ensemble»? Un tableau qui n'est pas dans l'ordre inverse?
Simo Salminen
31
Je ne peux plus reproduire ce bogue. Cela aurait pu être mon erreur. Il s'agit d'une solution très élégante.
Matt Williamson
3
L'ordre est désormais garanti dans la documentation.
jscs
je suis sûr que c'est la bonne réponse mais elle échouera pour les objets Mutable. Parce que NSEnumerator fournit le type d'objet en lecture seule @property (lecture seule, copie) NSArray <ObjectType> * allObjects;
Anurag Soni
49

Quelques repères

1. reverseObjectEnumerator allObjects

C'est la méthode la plus rapide:

NSArray *anArray = @[@"aa", @"ab", @"ac", @"ad", @"ae", @"af", @"ag",
        @"ah", @"ai", @"aj", @"ak", @"al", @"am", @"an", @"ao", @"ap", @"aq", @"ar", @"as", @"at",
        @"au", @"av", @"aw", @"ax", @"ay", @"az", @"ba", @"bb", @"bc", @"bd", @"bf", @"bg", @"bh",
        @"bi", @"bj", @"bk", @"bl", @"bm", @"bn", @"bo", @"bp", @"bq", @"br", @"bs", @"bt", @"bu",
        @"bv", @"bw", @"bx", @"by", @"bz", @"ca", @"cb", @"cc", @"cd", @"ce", @"cf", @"cg", @"ch",
        @"ci", @"cj", @"ck", @"cl", @"cm", @"cn", @"co", @"cp", @"cq", @"cr", @"cs", @"ct", @"cu",
        @"cv", @"cw", @"cx", @"cy", @"cz"];

NSDate *methodStart = [NSDate date];

NSArray *reversed = [[anArray reverseObjectEnumerator] allObjects];

NSDate *methodFinish = [NSDate date];
NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart];
NSLog(@"executionTime = %f", executionTime);

Résultat: executionTime = 0.000026

2. Itération sur un reverseObjectEnumerator

C'est entre 1,5x et 2,5x plus lentement:

NSDate *methodStart = [NSDate date];
NSMutableArray *array = [NSMutableArray arrayWithCapacity:[anArray count]];
NSEnumerator *enumerator = [anArray reverseObjectEnumerator];
for (id element in enumerator) {
    [array addObject:element];
}
NSDate *methodFinish = [NSDate date];
NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart];
NSLog(@"executionTime = %f", executionTime);

Résultat: executionTime = 0.000071

3. sortedArrayUsingComparator

C'est entre 30x et 40x plus lent (pas de surprise ici):

NSDate *methodStart = [NSDate date];
NSArray *reversed = [anArray sortedArrayUsingComparator: ^(id obj1, id obj2) {
    return [anArray indexOfObject:obj1] < [anArray indexOfObject:obj2] ? NSOrderedDescending : NSOrderedAscending;
}];

NSDate *methodFinish = [NSDate date];
NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart];
NSLog(@"executionTime = %f", executionTime);

Résultat: executionTime = 0.001100

Il en [[anArray reverseObjectEnumerator] allObjects]va de même pour la vitesse et la facilité.

Johannes Fahrenkrug
la source
Et je peux imaginer que ce sera beaucoup plus lent de 30 à 40 fois pour un plus grand nombre d'objets. Je ne sais pas quelle est la complexité de l'algorithme de tri (dans le meilleur des cas O (n * logn)?, Mais il appelle également indexOfObject, qui est probablement O (n). Avec le tri, cela pourrait être O (n ^ 2 * logn) ou quelque chose de pas bon
Joseph Humfrey
1
Qu'en est-il d'une référence en utilisant enumerateObjectsWithOptions:NSEnumerationReverse?
brandonscript
2
Je viens de faire un benchmark en utilisant enumerateObjectsWithOptions:NSEnumerationReverse- celui du haut terminé en 0.000072quelques secondes, la méthode des blocs en 0.000009quelques secondes.
brandonscript
Belle observation, cela a du sens pour moi, mais je pense que les temps d'exécution sont trop courts pour conclure que l'algorithme X est Y fois plus rapide que les autres. Afin de mesurer les performances d'exécution de l'algorithme, nous devons prendre soin de plusieurs choses, comme: Y a-t-il un processus qui s'exécute en même temps?. Par exemple, peut-être que lorsque vous exécutez le premier algorithme, vous disposez de plus de mémoire cache, etc. De plus, il n'y a qu'un seul ensemble de données, je pense que nous devrions exécuter plusieurs ensembles de données (avec des tailles différentes sont des compositions) pour conclure.
pcambre
21

DasBoot a la bonne approche, mais il y a quelques erreurs dans son code. Voici un extrait de code complètement générique qui inversera tout NSMutableArray en place:

/* Algorithm: swap the object N elements from the top with the object N 
 * elements from the bottom. Integer division will wrap down, leaving 
 * the middle element untouched if count is odd.
 */
for(int i = 0; i < [array count] / 2; i++) {
    int j = [array count] - i - 1;

    [array exchangeObjectAtIndex:i withObjectAtIndex:j];
}

Vous pouvez envelopper dans une fonction C, ou pour des points bonus, utilisez des catégories pour l'ajouter à NSMutableArray. (Dans ce cas, 'array' deviendrait 'self'.) Vous pouvez également l'optimiser en l'assignant [array count]à une variable avant la boucle et en utilisant cette variable, si vous le souhaitez.

Si vous ne disposez que d'un NSArray normal, il n'y a aucun moyen de l'inverser en place, car les NSArrays ne peuvent pas être modifiés. Mais vous pouvez faire une copie inversée:

NSMutableArray * copy = [NSMutableArray arrayWithCapacity:[array count]];

for(int i = 0; i < [array count]; i++) {
    [copy addObject:[array objectAtIndex:[array count] - i - 1]];
}

Ou utilisez cette petite astuce pour le faire en une seule ligne:

NSArray * copy = [[array reverseObjectEnumerator] allObjects];

Si vous voulez simplement faire une boucle sur un tableau en arrière, vous pouvez utiliser une boucle for/ inavec [array reverseObjectEnumerator], mais c'est probablement un peu plus efficace à utiliser -enumerateObjectsWithOptions:usingBlock::

[array enumerateObjectsWithOptions:NSEnumerationReverse
                        usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // This is your loop body. Use the object in obj here. 
    // If you need the index, it's in idx.
    // (This is the best feature of this method, IMHO.)
    // Instead of using 'continue', use 'return'.
    // Instead of using 'break', set '*stop = YES' and then 'return'.
    // Making the surrounding method/block return is tricky and probably
    // requires a '__block' variable.
    // (This is the worst feature of this method, IMHO.)
}];

( Remarque : substantiellement mis à jour en 2014 avec cinq années supplémentaires d'expérience de la Fondation, une ou deux nouvelles fonctionnalités Objective-C et quelques conseils tirés des commentaires.)

Brent Royal-Gordon
la source
Est-ce que ça marche? Je ne pense pas que NSMutableArray ait une méthode setObject: atIndex :. Merci pour le correctif suggéré pour la boucle et pour l'utilisation de l'identifiant générique au lieu de NSNumber.
Himadri Choudhury
Vous avez raison, j'ai compris cela en lisant certains des autres exemples. Fixé maintenant.
Brent Royal-Gordon,
2
[nombre de tableaux] est appelé à chaque boucle. C'est très coûteux. Il y a même une fonction qui change les positions de deux objets.
Georg Schölly
8

Après avoir examiné les réponses des autres ci-dessus et trouvé la discussion de Matt Gallagher ici

Je propose ceci:

NSMutableArray * reverseArray = [NSMutableArray arrayWithCapacity:[myArray count]]; 

for (id element in [myArray reverseObjectEnumerator]) {
    [reverseArray addObject:element];
}

Comme Matt l'observe:

Dans le cas ci-dessus, vous pouvez vous demander si - [NSArray reverseObjectEnumerator] serait exécuté à chaque itération de la boucle - ralentissant potentiellement le code. <...>

Peu de temps après, il répond ainsi:

<...> L'expression "collection" n'est évaluée qu'une seule fois, lorsque la boucle for commence. C'est le meilleur cas, car vous pouvez sans risque mettre une fonction coûteuse dans l'expression "collection" sans impact sur les performances par itération de la boucle.

Aeronin
la source
8

Les catégories de Georg Schölly sont très agréables. Cependant, pour NSMutableArray, l'utilisation de NSUIntegers pour les index entraîne un blocage lorsque le tableau est vide. Le bon code est:

@implementation NSMutableArray (Reverse)

- (void)reverse {
    NSInteger i = 0;
    NSInteger j = [self count] - 1;
    while (i < j) {
        [self exchangeObjectAtIndex:i
                  withObjectAtIndex:j];

        i++;
        j--;
    }
}

@end
Werner Jainek
la source
8

La façon la plus efficace d'énumérer un tableau à l'envers:

Utilisez enumerateObjectsWithOptions:NSEnumerationReverse usingBlock. En utilisant le benchmark de @ JohannesFahrenkrug ci-dessus, cela a été 8 fois plus rapide que [[array reverseObjectEnumerator] allObjects];:

NSDate *methodStart = [NSDate date];

[anArray enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    //
}];

NSDate *methodFinish = [NSDate date];
NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart];
NSLog(@"executionTime = %f", executionTime);
brandonscript
la source
7
NSMutableArray *objMyObject = [NSMutableArray arrayWithArray:[self reverseArray:objArrayToBeReversed]];

// Function reverseArray 
-(NSArray *) reverseArray : (NSArray *) myArray {   
    return [[myArray reverseObjectEnumerator] allObjects];
}
Jayprakash Dubey
la source
3

Inverser le tableau et le parcourir:

[[[startArray reverseObjectEnumerator] allObjects] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    ...
}];
Aqib Mumtaz
la source
2

Pour mettre à jour cela, dans Swift cela peut être fait facilement avec:

array.reverse()
Julio
la source
1

Quant à moi, avez-vous réfléchi à la façon dont le tableau a été rempli en premier lieu? J'étais en train d'ajouter de NOMBREUX objets à un tableau, et j'ai décidé d'insérer chacun au début, en poussant tous les objets existants par un. Nécessite un tableau mutable, dans ce cas.

NSMutableArray *myMutableArray = [[NSMutableArray alloc] initWithCapacity:1];
[myMutableArray insertObject:aNewObject atIndex:0];
James Perih
la source
1

Ou la voie Scala:

-(NSArray *)reverse
{
    if ( self.count < 2 )
        return self;
    else
        return [[self.tail reverse] concat:[NSArray arrayWithObject:self.head]];
}

-(id)head
{
    return self.firstObject;
}

-(NSArray *)tail
{
    if ( self.count > 1 )
        return [self subarrayWithRange:NSMakeRange(1, self.count - 1)];
    else
        return @[];
}
Hugo Stieglitz
la source
2
La récursivité est une idée terrible sur les grandes structures de données, en termes de mémoire.
Eran Goldin
S'il compile avec la récursivité de la queue, cela ne devrait pas être un problème
simple_code
1

Il existe un moyen simple de le faire.

    NSArray *myArray = @[@"5",@"4",@"3",@"2",@"1"];
    NSMutableArray *myNewArray = [[NSMutableArray alloc] init]; //this object is going to be your new array with inverse order.
    for(int i=0; i<[myNewArray count]; i++){
        [myNewArray insertObject:[myNewArray objectAtIndex:i] atIndex:0];
    }
    //other way to do it
    for(NSString *eachValue in myArray){
        [myNewArray insertObject:eachValue atIndex:0];
    }

    //in both cases your new array will look like this
    NSLog(@"myNewArray: %@", myNewArray);
    //[@"1",@"2",@"3",@"4",@"5"]

J'espère que ça aide.

vroldan
la source
0

Je ne connais aucune méthode intégrée. Mais, le codage à la main n'est pas trop difficile. En supposant que les éléments du tableau que vous traitez sont des objets NSNumber de type entier, et «arr» est le NSMutableArray que vous souhaitez inverser.

int n = [arr count];
for (int i=0; i<n/2; ++i) {
  id c  = [[arr objectAtIndex:i] retain];
  [arr replaceObjectAtIndex:i withObject:[arr objectAtIndex:n-i-1]];
  [arr replaceObjectAtIndex:n-i-1 withObject:c];
}

Puisque vous commencez avec un NSArray, vous devez d'abord créer le tableau mutable avec le contenu du NSArray d'origine ('origArray').

NSMutableArray * arr = [[NSMutableArray alloc] init];
[arr setArray:origArray];

Edit: Correction de n -> n / 2 dans le nombre de boucles et modification de NSNumber en identifiant plus générique en raison des suggestions dans la réponse de Brent.

Himadri Choudhury
la source
1
Ne manque-t-il pas une version sur c?
Clay Bridges
0

Si tout ce que vous voulez faire est d'itérer en sens inverse, essayez ceci:

// iterate backwards
nextIndex = (currentIndex == 0) ? [myArray count] - 1 : (currentIndex - 1) % [myArray count];

Vous pouvez faire le [myArrayCount] une fois et l'enregistrer dans une variable locale (je pense que c'est cher), mais je suppose également que le compilateur fera à peu près la même chose avec le code écrit ci-dessus.

DougPA
la source
0

Syntaxe Swift 3:

let reversedArray = array.reversed()
fethica
la source
0

Essaye ça:

for (int i = 0; i < [arr count]; i++)
{
    NSString *str1 = [arr objectAtIndex:[arr count]-1];
    [arr insertObject:str1 atIndex:i];
    [arr removeObjectAtIndex:[arr count]-1];
}
Shashank shree
la source
0

Voici une belle macro qui fonctionnera pour NSMutableArray OU NSArray:

#define reverseArray(__theArray) {\
    if ([__theArray isKindOfClass:[NSMutableArray class]]) {\
        if ([(NSMutableArray *)__theArray count] > 1) {\
            NSUInteger i = 0;\
            NSUInteger j = [(NSMutableArray *)__theArray count]-1;\
            while (i < j) {\
                [(NSMutableArray *)__theArray exchangeObjectAtIndex:i\
                                                withObjectAtIndex:j];\
                i++;\
                j--;\
            }\
        }\
    } else if ([__theArray isKindOfClass:[NSArray class]]) {\
        __theArray = [[NSArray alloc] initWithArray:[[(NSArray *)__theArray reverseObjectEnumerator] allObjects]];\
    }\
}

Pour utiliser simplement appeler: reverseArray(myArray);

Albert Renshaw
la source