Wie kann ich ein NSArray in Objective-C umkehren?

356

Ich muss meine umkehren NSArray.

Als Beispiel:

[1,2,3,4,5] muss werden: [5,4,3,2,1]

Was ist der beste Weg, um dies zu erreichen?

Andy Jacobs
quelle
1
Beachten Sie auch Folgendes: http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Collections/Articles/sortingFilteringArrays.html Hier erfahren Sie, wie Sie ein Array in umgekehrter Reihenfolge sortieren (was normalerweise der Fall ist) Sie verwenden beispielsweise ein von NSDictionary # allKeys abgeleitetes Array und möchten, dass die umgekehrte Datums- / Alpha-Reihenfolge als Gruppierung für UITable auf dem iPhone usw. dient.

Antworten:

305

Um eine umgekehrte Kopie eines Arrays zu erhalten, schauen Sie sich die Lösung von danielpunkass mit an reverseObjectEnumerator.

Um ein veränderliches Array umzukehren, können Sie Ihrem Code die folgende Kategorie hinzufügen:

@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
quelle
15
Eines der schlechten Dinge an Fast Enumeration ist, dass neue Leute wie ich nichts über coole Dinge wie reverseObjectEnumerator lernen. Ziemlich ordentlicher Weg, es zu tun.
Brent Royal-Gordon
4
Da C ++ - Iteratoren eine noch schlechtere Syntax haben, sind sie hässlich.
Georg Schölly
4
Sollten Sie das Array nicht kopieren, bevor Sie es zurückgeben?
CIFilter
12
@Georg: Ich stimme dir in diesem Punkt nicht zu. Wenn ich eine Methode sehe, die ein unveränderliches Objekt zurückgibt, erwarte ich, dass sie tatsächlich ein unveränderliches Objekt zurückgibt. Es scheint eine gefährliche Praxis zu sein, ein unveränderliches Objekt zurückzugeben, aber tatsächlich ein veränderliches Objekt zurückzugeben.
Christine
3
Das Vorschlagen von reverseObjectEnumerator allObjects ist nicht hilfreich, da es das veränderbare Array nicht umkehrt. Selbst das Hinzufügen von mutableCopy würde nicht helfen, da das ursprüngliche Array immer noch nicht mutiert ist. Apple dokumentiert, dass Unveränderlichkeit nicht zur Laufzeit getestet werden sollte , sondern basierend auf dem zurückgegebenen Typ angenommen werden sollte. Daher ist die Rückgabe eines NSMutableArray in diesem Fall ein vollkommen korrekter Code.
Peter N Lewis
1287

Es gibt eine viel einfachere Lösung, wenn Sie die integrierte reverseObjectEnumeratorMethode nutzen NSArrayund die allObjectsMethode NSEnumerator:

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

allObjectsEs wird dokumentiert , dass ein Array mit den Objekten zurückgegeben wird, die noch nicht durchlaufen wurden nextObject, und zwar in der folgenden Reihenfolge:

Dieses Array enthält alle verbleibenden Objekte des Enumerators in aufgezählter Reihenfolge .

Danielpunkass
quelle
6
Weiter unten gibt es eine Antwort von Matt Williamson, die ein Kommentar sein sollte: Verwenden Sie nicht die Lösung von Danielpunkass. Ich habe es benutzt, weil ich dachte, es sei eine großartige Abkürzung, aber jetzt habe ich nur 3 Stunden damit verbracht herauszufinden, warum mein A * -Algorithmus kaputt war. Es ist, weil es den falschen Satz zurückgibt!
Georg Schölly
1
Was bedeutet "falsch eingestellt"? Ein Array, das nicht in umgekehrter Reihenfolge ist?
Simo Salminen
31
Ich kann diesen Fehler nicht mehr reproduzieren. Es könnte mein Fehler gewesen sein. Dies ist eine sehr elegante Lösung.
Matt Williamson
3
Die Bestellung ist jetzt in der Dokumentation garantiert.
Jscs
Ich bin sicher, dass dies die gute Antwort ist, aber es wird für die veränderlichen Objekte fehlschlagen. Da NSEnumerator schreibgeschützten Objekttyp @property (schreibgeschützt, kopieren) bereitstellt NSArray <ObjectType> * allObjects;
Anurag Soni
49

Einige Benchmarks

1. reverseObjectEnumerator allObjects

Dies ist die schnellste Methode:

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

Ergebnis: executionTime = 0.000026

2. Iterieren über einen reverseObjectEnumerator

Dies ist zwischen 1,5x und 2,5x langsamer:

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

Ergebnis: executionTime = 0.000071

3. sortierterArrayUsingComparator

Dies ist zwischen 30x und 40x langsamer (keine Überraschungen hier):

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

Ergebnis: executionTime = 0.001100

So [[anArray reverseObjectEnumerator] allObjects]ist der klare Gewinner, wenn es um Geschwindigkeit und Leichtigkeit geht.

Johannes Fahrenkrug
quelle
Und ich kann mir vorstellen, dass es für eine größere Anzahl von Objekten viel mehr als 30-40x langsamer sein wird. Ich weiß nicht, wie komplex der Sortieralgorithmus ist (bester Fall O (n * logn)?), Aber er ruft auch indexOfObject auf, was wahrscheinlich O (n) ist. Bei der Sortierung könnte das O (n ^ 2 sein * logn) oder so. Nicht gut!
Joseph Humfrey
1
Was ist mit einem Benchmark enumerateObjectsWithOptions:NSEnumerationReverse?
Brandonscript
2
Ich habe gerade einen Benchmark durchgeführt enumerateObjectsWithOptions:NSEnumerationReverse- den obersten in 0.000072Sekunden, die Blockmethode in 0.000009Sekunden.
Brandonscript
Gute Beobachtung, es macht für mich Sinn, aber ich denke, dass die Ausführungszeiten zu kurz sind, um zu dem Schluss zu kommen, dass der X-Algorithmus Y-mal schneller ist als andere. Um die Leistung der Algorithmusausführung zu messen, müssen wir verschiedene Dinge beachten, z. B. Gibt es einen Prozess, der gleichzeitig ausgeführt wird? Wenn Sie beispielsweise den ersten Algorithmus ausführen, steht möglicherweise mehr Cache-Speicher zur Verfügung, und so weiter. Darüber hinaus gibt es nur einen Datensatz. Ich denke, wir sollten mit mehreren Datensätzen (mit unterschiedlichen Größen sind Zusammensetzungen) arbeiten, um zu schließen.
pcambre
21

DasBoot hat den richtigen Ansatz, aber sein Code enthält einige Fehler. Hier ist ein vollständig generisches Code-Snippet, das alle vorhandenen NSMutableArray umkehrt:

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

Sie können dies in eine C-Funktion einschließen oder für Bonuspunkte Kategorien verwenden, um es zu NSMutableArray hinzuzufügen. (In diesem Fall wird 'array' zu 'self'.) Sie können es auch optimieren, indem Sie [array count]einer Variablen vor der Schleife zuweisen und diese Variable verwenden, wenn Sie dies wünschen.

Wenn Sie nur ein reguläres NSArray haben, können Sie es nicht umkehren, da NSArrays nicht geändert werden können. Sie können jedoch eine umgekehrte Kopie erstellen:

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

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

Oder verwenden Sie diesen kleinen Trick, um es in einer Zeile zu tun:

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

Wenn Sie ein Array nur rückwärts durchlaufen möchten, können Sie eine for/ in-Schleife mit verwenden [array reverseObjectEnumerator], die Verwendung ist jedoch wahrscheinlich etwas effizienter -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.)
}];

( Hinweis : Im Jahr 2014 mit fünf weiteren Jahren Foundation-Erfahrung, ein oder zwei neuen Objective-C-Funktionen und einigen Tipps aus den Kommentaren erheblich aktualisiert.)

Brent Royal-Gordon
quelle
Funktioniert es? Ich glaube nicht, dass NSMutableArray eine setObject: atIndex: -Methode hat. Vielen Dank für die vorgeschlagene Korrektur für die Schleife und die Verwendung der generischen ID anstelle von NSNumber.
Himadri Choudhury
Du hast recht, das habe ich verstanden, als ich einige der anderen Beispiele gelesen habe. Jetzt behoben.
Brent Royal-Gordon
2
[Array Count] wird bei jeder Schleife aufgerufen. Das ist sehr teuer. Es gibt sogar eine Funktion, die die Position von zwei Objekten ändert.
Georg Schölly
8

Nachdem Sie die Antworten des anderen oben überprüft und Matt Gallaghers Diskussion hier gefunden haben

Ich schlage vor:

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

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

Wie Matt bemerkt:

Im obigen Fall fragen Sie sich möglicherweise, ob - [NSArray reverseObjectEnumerator] bei jeder Iteration der Schleife ausgeführt wird - was möglicherweise den Code verlangsamt. <...>

Kurz darauf antwortet er folgendermaßen:

<...> Der Ausdruck "Sammlung" wird nur einmal ausgewertet, wenn die for-Schleife beginnt. Dies ist der beste Fall, da Sie eine teure Funktion sicher in den Ausdruck "Sammlung" einfügen können, ohne die Leistung der Schleife pro Iteration zu beeinträchtigen.

Aeronin
quelle
8

Georg Schöllys Kategorien sind sehr schön. Bei NSMutableArray führt die Verwendung von NSUIntegern für die Indizes jedoch zu einem Absturz, wenn das Array leer ist. Der richtige Code lautet:

@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
quelle
8

Der effizienteste Weg, ein Array in umgekehrter Reihenfolge aufzulisten:

Verwenden Sie enumerateObjectsWithOptions:NSEnumerationReverse usingBlock. Mit dem obigen Benchmark von @ JohannesFahrenkrug wurde dies 8x schneller abgeschlossen als [[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
quelle
7
NSMutableArray *objMyObject = [NSMutableArray arrayWithArray:[self reverseArray:objArrayToBeReversed]];

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

Array umkehren und durchlaufen:

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

Um dies zu aktualisieren, kann dies in Swift einfach durchgeführt werden mit:

array.reverse()
Julio
quelle
1

Haben Sie sich überlegt, wie das Array überhaupt gefüllt wurde? Ich war gerade dabei, einem Array VIELE Objekte hinzuzufügen, und entschied mich, jedes am Anfang einzufügen und alle vorhandenen Objekte um eins nach oben zu verschieben. Erfordert in diesem Fall ein veränderbares Array.

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

Oder der Scala-Weg:

-(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
quelle
2
Rekursion ist eine schreckliche Idee für große Datenstrukturen, was den Speicher betrifft.
Eran Goldin
Wenn es mit Schwanzrekursion kompiliert wird, sollte es kein Problem sein
simple_code
1

Es gibt einen einfachen Weg, dies zu tun.

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

Ich hoffe das hilft.

vroldan
quelle
0

Ich kenne keine eingebaute Methode. Das Codieren von Hand ist jedoch nicht allzu schwierig. Angenommen, die Elemente des Arrays, mit denen Sie sich befassen, sind NSNumber-Objekte vom Typ Integer, und 'arr' ist das NSMutableArray, das Sie umkehren möchten.

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

Da Sie mit einem NSArray beginnen, müssen Sie zuerst das veränderbare Array mit dem Inhalt des ursprünglichen NSArray ('origArray') erstellen.

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

Bearbeiten: n -> n / 2 in der Schleifenzahl behoben und NSNumber aufgrund der Vorschläge in Brents Antwort in die allgemeinere ID geändert.

Himadri Choudhury
quelle
1
Fehlt hier nicht eine Veröffentlichung auf c?
Clay Bridges
0

Wenn Sie nur in umgekehrter Reihenfolge iterieren möchten, versuchen Sie Folgendes:

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

Sie können [myArrayCount] einmal ausführen und in einer lokalen Variablen speichern (ich denke, es ist teuer), aber ich vermute auch, dass der Compiler mit dem oben beschriebenen Code so ziemlich dasselbe tun wird.

DougPA
quelle
0

Swift 3-Syntax:

let reversedArray = array.reversed()
Fethica
quelle
0

Versuche dies:

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
quelle
0

Hier ist ein schönes Makro , das für beide NSMutableArray arbeiten ODER 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]];\
    }\
}

Um zu verwenden, rufen Sie einfach an: reverseArray(myArray);

Albert Renshaw
quelle