Jak mogę odwrócić tablicę NSAr w Objective-C?

356

Muszę odwrócić moje NSArray.

Jako przykład:

[1,2,3,4,5] musi stać się: [5,4,3,2,1]

Jaki jest najlepszy sposób na osiągnięcie tego?

Andy Jacobs
źródło
1
Warto również spojrzeć na to: http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Collections/Articles/sortingFilteringArrays.html, który mówi, jak sortować tablicę w odwrotnej kolejności (co zwykle jest robisz to, na przykład używając tablicy pochodnej z NSDictionary # allKeys, i chcesz, aby odwrotna kolejność daty / alfa służyła jako grupa dla UITable na iPhone'ie itp.).

Odpowiedzi:

305

Aby uzyskać odwróconą kopię tablicy, spójrz na rozwiązanie danielpunkass przy użyciu reverseObjectEnumerator.

Aby odwrócić zmienną tablicę, możesz dodać następującą kategorię do swojego kodu:

@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
źródło
15
Jedną ze złych rzeczy w Fast Enumeration jest to, że nowi faceci tacy jak ja nie dowiadują się o fajnych rzeczach, takich jak reverseObjectEnumerator. Bardzo fajny sposób na zrobienie tego.
Brent Royal-Gordon,
4
Ponieważ iteratory C ++ mają jeszcze gorszą składnię, są brzydkie.
Georg Schölly,
4
Czy nie powinieneś skopiować tablicy przed jej zwróceniem?
CIFilter
12
@Georg: Nie zgadzam się z tobą w tej sprawie. Jeśli widzę metodę, która zwraca niezmienny obiekt, spodziewam się, że faktycznie zwraca niezmienny obiekt. Wydaje się, że zwraca niezmienny obiekt, ale rzeczywisty zwrot zmutowanego obiektu jest niebezpieczną praktyką.
Christine,
3
Sugerując reverseObjectEnumerator allObjects nie jest pomocne, ponieważ nie odwraca się zmienny tablicę, Nawet dodanie mutableCopy nie pomoże, ponieważ wciąż oryginalna tablica nie jest zmutowana. Apple dokumentuje, że niezmienność nie powinna być testowana w czasie wykonywania, ale należy ją zakładać na podstawie zwróconego typu, więc zwracanie tablicy NSMutableArray w tym przypadku jest całkowicie poprawnym kodem.
Peter N Lewis
1287

Jest o wiele łatwiejsze rozwiązanie, jeśli skorzystasz z wbudowanej reverseObjectEnumeratormetody NSArrayi allObjectsmetody NSEnumerator:

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

allObjectsjest udokumentowane jako zwracanie tablicy z obiektami, z którymi jeszcze nie wykonano przejścia nextObject, w celu:

Ta tablica zawiera wszystkie pozostałe obiekty modułu wyliczającego w wyliczonej kolejności .

danielpunkass
źródło
6
Poniżej znajduje się odpowiedź Matta Williamsona, która powinna być komentarzem: nie używaj rozwiązania danielpunkass. Użyłem tego, myśląc, że to świetny skrót, ale teraz spędziłem 3 godziny, próbując dowiedzieć się, dlaczego mój algorytm A * został złamany. To dlatego, że zwraca zły zestaw!
Georg Schölly,
1
Co oznacza „zły zestaw”? Tablica, która nie jest w odwrotnej kolejności?
Simo Salminen,
31
Nie jestem już w stanie odtworzyć tego błędu. To mógł być mój błąd. To bardzo eleganckie rozwiązanie.
Matt Williamson
3
Zamówienie jest teraz gwarantowane w dokumentacji.
jscs
jestem pewien, że to dobra odpowiedź, ale nie powiedzie się w przypadku Zmiennych Przedmiotów. Ponieważ NSEnumerator zapewnia typ obiektu tylko do odczytu @ właściwość (tylko do odczytu, kopiuj) NSArray <ObjectType> * allObjects;
Anurag Soni
49

Niektóre testy porównawcze

1. reverseObjectEnumerator allObjects

To najszybsza metoda:

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

Wynik: executionTime = 0.000026

2. Iteracja po reverseObjectEnumerator

Jest to od 1,5x do 2,5x wolniej:

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

Wynik: executionTime = 0.000071

3. sortedArrayUsingComparator

Jest to od 30x do 40x wolniej (tutaj żadnych niespodzianek):

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

Wynik: executionTime = 0.001100

Tak [[anArray reverseObjectEnumerator] allObjects]jest wyraźny zwycięzca, jeśli chodzi o szybkość i łatwość.

Johannes Fahrenkrug
źródło
Mogę sobie wyobrazić, że dla większej liczby obiektów będzie on o ponad 30-40x wolniejszy. Nie wiem, jaka jest złożoność algorytmu sortowania (najlepszy przypadek O (n * logn) ?, ale wywołuje także indexOfObject, który prawdopodobnie jest O (n). Przy sortowaniu może to być O (n ^ 2 * logn) lub coś w tym rodzaju - nie dobrze!
Joseph Humfrey,
1
Co z użyciem testu porównawczego enumerateObjectsWithOptions:NSEnumerationReverse?
brandonscript
2
Właśnie zrobiłem test porównawczy enumerateObjectsWithOptions:NSEnumerationReverse- najwyższy ukończono w 0.000072kilka sekund, metodę blokową w 0.000009kilka sekund.
brandonscript
Ładna obserwacja, ma to dla mnie sens, ale myślę, że czasy wykonania są zbyt krótkie, aby stwierdzić, że algorytm X jest Y razy szybszy niż inne. Aby zmierzyć wydajność wykonywania algorytmu, musimy zadbać o kilka rzeczy, na przykład: Czy jest jakiś proces, który działa w tym samym czasie ?. Na przykład, być może po uruchomieniu pierwszego algorytmu masz więcej dostępnej pamięci podręcznej i tak dalej. Co więcej, jest tylko jeden zestaw danych, myślę, że powinniśmy uruchomić kilka zestawów danych (o różnych rozmiarach to kompozycje), aby zakończyć.
pcambre
21

DasBoot ma właściwe podejście, ale w jego kodzie jest kilka błędów. Oto całkowicie ogólny fragment kodu, który odwróci dowolne miejsce NSMutableArray:

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

Możesz zawinąć to w funkcję C lub dla punktów bonusowych użyć kategorii, aby dodać ją do NSMutableArray. (W takim przypadku „tablica” zmieni się w „ja”.) Możesz także zoptymalizować ją, przypisując [array count]zmienną przed pętlą i używając tej zmiennej, jeśli chcesz.

Jeśli masz tylko zwykły NSArray, nie ma możliwości odwrócenia go na miejscu, ponieważ NSArrays nie może być modyfikowany. Ale możesz zrobić odwróconą kopię:

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

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

Lub użyj tej małej sztuczki, aby zrobić to w jednym wierszu:

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

Jeśli chcesz po prostu zapętlić tablicę do tyłu, możesz użyć pętli for/ inz [array reverseObjectEnumerator], ale prawdopodobnie bardziej wydajne jest użycie -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.)
}];

( Uwaga : Znacząco zaktualizowany w 2014 r. Z pięcioletnim doświadczeniem Fundacji, nową funkcją Celu C lub dwoma oraz kilkoma wskazówkami z komentarzy)

Brent Royal-Gordon
źródło
Czy to działa? Nie sądzę, aby NSMutableArray ma metodę setObject: atIndex:. Dzięki za sugerowaną poprawkę dla pętli i użycie ogólnego identyfikatora zamiast NSNumber.
Himadri Choudhury,
Masz rację, złapałem to, kiedy przeczytałem kilka innych przykładów. Naprawiono teraz.
Brent Royal-Gordon,
2
[liczba tablic] jest wywoływana przy każdej pętli. To jest bardzo kosztowne. Istnieje nawet funkcja, która zmienia pozycje dwóch obiektów.
Georg Schölly,
8

Po zapoznaniu się z odpowiedziami drugiego powyżej i znalezieniu dyskusji Matta Gallaghera tutaj

Proponuję to:

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

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

Jak zauważa Matt:

W powyższym przypadku możesz się zastanawiać, czy - [NSArray reverseObjectEnumerator] będzie uruchamiany przy każdej iteracji pętli - potencjalnie spowolni kod. <...>

Wkrótce potem odpowiada w ten sposób:

<...> Wyrażenie „kolekcja” jest oceniane tylko raz, gdy rozpoczyna się pętla for. Jest to najlepszy przypadek, ponieważ można bezpiecznie umieścić kosztowną funkcję w wyrażeniu „kolekcja” bez wpływu na wydajność pętli w odniesieniu do iteracji.

Aeronina
źródło
8

Kategorie Georga Schölly są bardzo ładne. Jednak w przypadku NSMutableArray użycie NSUIntegers dla indeksów powoduje awarię, gdy tablica jest pusta. Prawidłowy kod to:

@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
źródło
8

Najbardziej efektywny sposób wyliczenia tablicy w odwrotnej kolejności:

Zastosowanie enumerateObjectsWithOptions:NSEnumerationReverse usingBlock. Korzystając z powyższego testu porównawczego @ JohannesFahrenkrug, ukończono to 8 razy szybciej niż [[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
źródło
7
NSMutableArray *objMyObject = [NSMutableArray arrayWithArray:[self reverseArray:objArrayToBeReversed]];

// Function reverseArray 
-(NSArray *) reverseArray : (NSArray *) myArray {   
    return [[myArray reverseObjectEnumerator] allObjects];
}
Jayprakash Dubey
źródło
3

Odwróć tablicę i przechodź przez nią:

[[[startArray reverseObjectEnumerator] allObjects] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    ...
}];
Aqib Mumtaz
źródło
2

Aby to zaktualizować, w Swift można to łatwo zrobić za pomocą:

array.reverse()
Julio
źródło
1

Co do mnie, czy zastanawiałeś się, w jaki sposób tablica była zapełniona? Byłem w trakcie dodawania WIELU obiektów do tablicy i postanowiłem wstawić każdy z nich na początku, przesuwając wszystkie istniejące obiekty o jeden. Wymaga w tym przypadku zmiennej tablicy.

NSMutableArray *myMutableArray = [[NSMutableArray alloc] initWithCapacity:1];
[myMutableArray insertObject:aNewObject atIndex:0];
James Perih
źródło
1

Lub Scala-Way:

-(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
źródło
2
Rekurencja to straszny pomysł na duże struktury danych, jeśli chodzi o pamięć.
Eran Goldin
Jeśli kompiluje się z rekurencją ogona, nie powinno to stanowić problemu
simple_code
1

Jest na to łatwy sposób.

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

Mam nadzieję, że to pomoże.

Vroldan
źródło
0

Nie znam żadnej wbudowanej metody. Jednak ręczne kodowanie nie jest zbyt trudne. Zakładając, że elementami tablicy, z którymi mamy do czynienia, są obiekty typu NSNumber typu liczba całkowita, a „arr” to tablica NSMutableArray, którą chcemy odwrócić.

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

Ponieważ zaczynasz od NSArray, musisz najpierw utworzyć zmienną tablicę z zawartością oryginalnego NSArray („origArray”).

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

Edycja: Naprawiono n -> n / 2 w liczbie pętli i zmieniono NSNumber na bardziej ogólny id ze względu na sugestie w odpowiedzi Brenta.

Himadri Choudhury
źródło
1
Czy nie brakuje wersji na c?
Clay Bridges
0

Jeśli wszystko, co chcesz zrobić, to iterować w odwrotnej kolejności, spróbuj tego:

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

Możesz zrobić [myArrayCount] raz i zapisać go w zmiennej lokalnej (myślę, że jest droga), ale zgaduję również, że kompilator zrobi to samo z kodem, jak napisano powyżej.

DougPA
źródło
0

Składnia Swift 3:

let reversedArray = array.reversed()
fethica
źródło
0

Spróbuj tego:

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
źródło
0

Oto ładne makro, które będzie działać dla NSMutableArray LUB 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]];\
    }\
}

Aby użyć wystarczy zadzwonić: reverseArray(myArray);

Albert Renshaw
źródło