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?


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

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 .


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ść.


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)


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.


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

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

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

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

3

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

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

2

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

array.reverse()

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

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

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.


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.


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.



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

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.