Come posso invertire un NSArray in Objective-C?


356

Devo invertire il mio NSArray.

Come esempio:

[1,2,3,4,5] deve diventare: [5,4,3,2,1]

Qual è il modo migliore per raggiungere questo obiettivo?


1
Vale anche la pena guardare questo: http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Collections/Articles/sortingFilteringArrays.html che ti dice come ordinare un array in ordine inverso (che è comunemente cosa stai facendo, ad esempio usando un array derivato da NSDictionary # allKeys, e vuoi che la data inversa / l'ordine alfa serva da raggruppamento per UITable su iPhone, ecc.).

Risposte:


305

Per ottenere una copia invertita di un array, guarda la soluzione di danielpunkass usando reverseObjectEnumerator.

Per invertire un array mutabile, è possibile aggiungere la seguente categoria al codice:

@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

15
Una delle cose cattive di Fast Enumeration è che nuovi ragazzi come me non imparano cose interessanti come reverseObjectEnumerator. Modo abbastanza pulito per farlo.
Brent Royal-Gordon,

4
Poiché C ++ - gli iteratori hanno una sintassi ancora peggiore, sono brutti.
Georg Schölly,

4
Non dovresti copiare l'array prima di restituirlo?
CIFilter

12
@Georg: non sono d'accordo con te su questo. Se vedo un metodo che restituisce un oggetto immutabile, mi aspetto che restituisca effettivamente un oggetto immutabile. Farlo sembrare restituire un oggetto immutabile, ma restituire effettivamente un oggetto mutabile è una pratica pericolosa in cui entrare.
Christine,

3
Suggerire reverseObjectEnumerator allObjects non è utile poiché non inverte l' array mutabile, anche l'aggiunta di mutableCopy non sarebbe di aiuto poiché l'array originale non è mutato. Apple documenta che l'immutabilità non deve essere testata in fase di esecuzione, ma deve essere assunta in base al tipo restituito, quindi la restituzione di un NSMutableArray in questo caso è un codice perfettamente corretto.
Peter N Lewis,

1286

Esiste una soluzione molto più semplice, se si sfrutta il reverseObjectEnumeratormetodo integrato attivo NSArraye il allObjectsmetodo di NSEnumerator:

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

allObjectsè documentato come restituire un array con gli oggetti che non sono stati ancora attraversati nextObject, nell'ordine:

Questa matrice contiene tutti gli oggetti rimanenti dell'enumeratore in ordine enumerato .


6
C'è una risposta più in basso qui di Matt Williamson che dovrebbe essere un commento: non usare la soluzione di danielpunkass. L'ho usato pensando che fosse un ottimo collegamento, ma ora ho appena trascorso 3 ore a cercare di capire perché il mio algoritmo A * era rotto. È perché restituisce il set sbagliato!
Georg Schölly,

1
Cosa significa "set errato"? Un array che non è in ordine inverso?
Simo Salminen,

31
Non sono più in grado di riprodurre quel bug. Potrebbe essere stato un mio errore. Questa è una soluzione molto elegante.
Matt Williamson,

3
L'ordine è ora garantito nella documentazione.
jscs

sono sicuro che questa è la buona risposta, ma fallirà per gli oggetti mutabili. Poiché NSEnumerator fornisce il tipo di oggetto di sola lettura @property (sola lettura, copia) NSArray <ObjectType> * allObjects;
Anurag Soni,

49

Alcuni parametri di riferimento

1. reverseObjectEnumerator allObjects

Questo è il metodo più veloce:

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

Risultato: executionTime = 0.000026

2. Iterazione su un reverseObjectEnumerator

Questo è tra 1,5x e 2,5x più lento:

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

Risultato: executionTime = 0.000071

3. sortArrayUsingComparator

Questo è tra 30x e 40x più lento (nessuna sorpresa qui):

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

Risultato: executionTime = 0.001100

Quindi [[anArray reverseObjectEnumerator] allObjects]è il chiaro vincitore quando si tratta di velocità e facilità.


E posso immaginare che sarà molto più lento di 30-40 volte per un numero maggiore di oggetti. Non so quale sia la complessità dell'algoritmo di ordinamento (nel migliore dei casi O (n * logn) ?, ma sta anche chiamando indexOfObject, che è probabilmente O (n). Con l'ordinamento, potrebbe essere O (n ^ 2 * logn) o qualcosa del genere. Non va bene!
Joseph Humfrey,

1
Che dire di un benchmark che utilizza enumerateObjectsWithOptions:NSEnumerationReverse?
brandonscript,

2
Ho appena fatto un benchmark usando enumerateObjectsWithOptions:NSEnumerationReverse: il primo completato in 0.000072pochi secondi, il metodo del blocco in 0.000009pochi secondi.
brandonscript,

Bella osservazione, ha senso per me, ma penso che i tempi di esecuzione siano troppo brevi per concludere che l'algoritmo X è Y volte più veloce di altri. Al fine di misurare le prestazioni di esecuzione dell'algoritmo, dobbiamo occuparci di diverse cose, ad esempio: Esiste un processo in esecuzione contemporaneamente ?. Ad esempio, forse quando esegui il primo algoritmo hai più memoria cache disponibile e così via. Inoltre, c'è solo un set di dati, penso che dovremmo correre con diversi set di dati (con dimensioni diverse sono composizioni) per concludere.
pcambre,

21

DasBoot ha l'approccio giusto, ma ci sono alcuni errori nel suo codice. Ecco uno snippet di codice completamente generico che annullerà qualsiasi NSMutableArray in atto:

/* 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];
}

Puoi racchiuderlo in una funzione C o, per i punti bonus, utilizzare le categorie per aggiungerlo a NSMutableArray. (In tal caso, 'array' diventerebbe 'self'.) Puoi anche ottimizzarlo assegnando [array count]una variabile prima del ciclo e usando quella variabile, se lo desideri.

Se si dispone solo di un normale NSArray, non è possibile invertirlo in posizione, poiché NSArrays non può essere modificato. Ma puoi fare una copia rovesciata:

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

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

O usa questo piccolo trucco per farlo in una riga:

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

Se vuoi semplicemente eseguire il loop su un array all'indietro, puoi usare un for/ inloop con [array reverseObjectEnumerator], ma è probabilmente un po 'più efficiente da usare -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.)
}];

( Nota : sostanzialmente aggiornato nel 2014 con altri cinque anni di esperienza della Fondazione, una o due nuove funzionalità di Objective-C e un paio di suggerimenti dai commenti.)


Funziona? Non credo che NSMutableArray abbia un metodo setObject: atIndex:. Grazie per la correzione suggerita per il ciclo e per aver usato l'id generico invece di NSNumber.
Himadri Choudhury,

Hai ragione, l'ho capito quando ho letto alcuni degli altri esempi. Riparato ora.
Brent Royal-Gordon,

2
[conteggio array] viene chiamato ogni volta che si esegue il loop. Questo è molto costoso. C'è anche una funzione che cambia le posizioni di due oggetti.
Georg Schölly,

8

Dopo aver esaminato le risposte dell'altro sopra e trovato qui la discussione di Matt Gallagher

Propongo questo:

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

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

Come osserva Matt:

Nel caso precedente, potresti chiederti se - [NSArray reverseObjectEnumerator] verrebbe eseguito su ogni iterazione del ciclo - potenzialmente rallentando il codice. <...>

Poco dopo, risponde così:

<...> L'espressione "collection" viene valutata una sola volta, quando inizia il ciclo for. Questo è il caso migliore, poiché puoi tranquillamente inserire una funzione costosa nell'espressione "collection" senza influire sulle prestazioni di pererazione del loop.


8

Le categorie di Georg Schölly sono molto belle. Tuttavia, per NSMutableArray, l'utilizzo di NSUIntegers per gli indici provoca un arresto anomalo quando l'array è vuoto. Il codice corretto è:

@implementation NSMutableArray (Reverse)

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

        i++;
        j--;
    }
}

@end

8

Il modo più efficiente per enumerare un array al contrario:

Usa enumerateObjectsWithOptions:NSEnumerationReverse usingBlock. Utilizzando il punto di riferimento @ JohannesFahrenkrug sopra, questo ha completato 8 volte più velocemente di [[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);

7
NSMutableArray *objMyObject = [NSMutableArray arrayWithArray:[self reverseArray:objArrayToBeReversed]];

// Function reverseArray 
-(NSArray *) reverseArray : (NSArray *) myArray {   
    return [[myArray reverseObjectEnumerator] allObjects];
}

3

Matrice inversa e loop attraverso di essa:

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

2

Per aggiornare questo, in Swift può essere fatto facilmente con:

array.reverse()

1

Quanto a me, hai considerato come l'array era popolato in primo luogo? Stavo aggiungendo MOLTI oggetti a un array e ho deciso di inserirli all'inizio, spingendo tutti gli oggetti esistenti di uno. Richiede un array mutabile, in questo caso.

NSMutableArray *myMutableArray = [[NSMutableArray alloc] initWithCapacity:1];
[myMutableArray insertObject:aNewObject atIndex:0];

1

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

2
La ricorsione è un'idea terribile su grandi strutture di dati, per quanto riguarda la memoria.
Eran Goldin,

Se si compila con la ricorsione della coda non dovrebbe essere un problema
simple_code

1

C'è un modo semplice per farlo.

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

Spero che questo possa essere d'aiuto.


0

Non conosco alcun metodo integrato. Ma codificare a mano non è troppo difficile. Supponendo che gli elementi dell'array con cui si ha a che fare siano oggetti NSNumber di tipo intero e 'arr' è l'array NSMutable che si desidera invertire.

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

Poiché inizi con un NSArray, devi prima creare l'array mutabile con i contenuti dell'originale NSArray ('origArray').

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

Modifica: risolto n -> n / 2 nel conteggio dei loop e modificato NSNumber nell'ID più generico a causa dei suggerimenti nella risposta di Brent.


1
Non manca questo rilascio su c?
Clay Bridges,

0

Se tutto ciò che vuoi fare è iterare al contrario, prova questo:

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

Puoi fare una volta [myArrayCount] e salvarlo in una variabile locale (penso che sia costoso), ma sto anche immaginando che il compilatore farà praticamente la stessa cosa con il codice come scritto sopra.


0

Sintassi Swift 3:

let reversedArray = array.reversed()

0

Prova questo:

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

0

Ecco una macro bello che funziona per entrambi i NSMutableArray O 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]];\
    }\
}

Per usare basta chiamare: reverseArray(myArray);

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.