Bagaimana saya bisa membalik NSArray di Objective-C?

356

Saya perlu membalikkan NSArray.

Sebagai contoh:

[1,2,3,4,5] harus menjadi: [5,4,3,2,1]

Apa cara terbaik untuk mencapai ini?

Andy Jacobs
sumber
1
Ada baiknya juga melihat ini: http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Collections/Articles/sortingFilteringArrays.html yang memberi tahu Anda cara mengurutkan array dalam urutan terbalik (yang umumnya seperti apa Anda sedang melakukan, misalnya dalam menggunakan array yang berasal dari NSDictionary # allKeys, dan Anda ingin membalikkan tanggal / urutan alpha untuk berfungsi sebagai pengelompokan untuk UITable pada iPhone, dll).

Jawaban:

305

Untuk mendapatkan salinan array yang terbalik, lihat solusi danielpunkass menggunakan reverseObjectEnumerator.

Untuk membalikkan array yang bisa berubah, Anda dapat menambahkan kategori berikut ke kode Anda:

@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
sumber
15
Salah satu hal buruk tentang Penghitungan Cepat adalah orang-orang baru seperti saya tidak belajar tentang hal-hal keren seperti reverseObjectEnumerator. Cara yang cukup rapi untuk melakukannya.
Brent Royal-Gordon
4
Karena C ++ - iterator memiliki sintaks yang lebih buruk, mereka jelek.
Georg Schölly
4
Bukankah Anda harus menyalin array sebelum mengembalikannya?
CIFilter
12
@ Georg: Saya tidak setuju dengan Anda tentang hal ini. Jika saya melihat metode yang mengembalikan objek yang tidak dapat diubah, saya mengharapkannya untuk benar-benar mengembalikan objek yang tidak dapat diubah. Setelah itu muncul untuk mengembalikan objek yang tidak bisa diubah tetapi sebenarnya mengembalikan objek yang bisa berubah adalah praktik berbahaya untuk dimasuki.
Christine
3
Menunjukkan reverseObjectEnumerator allObjects tidak membantu karena tidak membalikkan dengan array bisa berubah, bahkan menambahkan mutableCopy tidak akan membantu karena masih array asli tidak bermutasi. Apple mendokumentasikan bahwa ketidakmampuan tidak harus diuji pada waktu berjalan, tetapi diasumsikan berdasarkan pada tipe yang dikembalikan, jadi mengembalikan NSMutableArray dalam hal ini adalah kode yang benar-benar benar.
Peter N Lewis
1286

Ada solusi yang jauh lebih mudah, jika Anda memanfaatkan reverseObjectEnumeratormetode bawaan pada NSArray, dan allObjectsmetode NSEnumerator:

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

allObjectsdidokumentasikan sebagai mengembalikan array dengan objek yang belum dilalui nextObject, dalam urutan:

Array ini berisi semua objek enumerator yang tersisa dalam urutan yang disebutkan .

danielpunkass
sumber
6
Ada jawaban lebih lanjut di sini oleh Matt Williamson yang seharusnya menjadi komentar: Jangan gunakan solusi danielpunkass. Saya menggunakannya berpikir itu adalah jalan pintas yang hebat, tapi sekarang saya baru saja menghabiskan 3 jam mencoba mencari tahu mengapa algoritma A * saya rusak. Itu karena mengembalikan set yang salah!
Georg Schölly
1
Apa yang dimaksud dengan 'set yang salah'? Array yang tidak dalam urutan terbalik?
Simo Salminen
31
Saya tidak lagi dapat mereproduksi bug itu. Itu bisa jadi kesalahan saya. Ini adalah solusi yang sangat elegan.
Matt Williamson
3
Pesanan sekarang dijamin dalam dokumentasi.
jscs
saya yakin ini adalah jawaban yang baik tetapi akan gagal untuk Objek yang Dapat Diubah. Karena NSEnumerator menyediakan tipe objek readonly @ properti (readonly, copy) NSArray <ObjectType> * allObjects;
Anurag Soni
49

Beberapa tolok ukur

1. reverseObjectEnumerator allObjects

Ini adalah metode tercepat:

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

Hasil: executionTime = 0.000026

2. Iterating melalui reverseObjectEnumerator

Ini antara 1,5x dan 2,5x lebih lambat:

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

Hasil: executionTime = 0.000071

3. DiurutkanArrayUsingComparator

Ini antara 30x dan 40x lebih lambat (tidak ada kejutan di sini):

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

Hasil: executionTime = 0.001100

Jadi [[anArray reverseObjectEnumerator] allObjects]adalah pemenang yang jelas dalam hal kecepatan dan kemudahan.

Johannes Fahrenkrug
sumber
Dan saya dapat membayangkan bahwa itu akan jauh lebih lambat dari 30-40x untuk jumlah objek yang lebih besar. Saya tidak tahu apa kompleksitas dari algoritma pengurutan (kasus terbaik O (n * logn) ?, tetapi juga memanggil indexOfObject, yang mungkin O (n) .Dengan pengurutan, itu bisa menjadi O (n ^ 2 * logn) atau yang lainnya Tidak bagus!
Joseph Humfrey
1
Bagaimana dengan benchmark yang digunakan enumerateObjectsWithOptions:NSEnumerationReverse?
naskah merek
2
Saya baru saja melakukan benchmark menggunakan enumerateObjectsWithOptions:NSEnumerationReverse- yang teratas selesai dalam 0.000072hitungan detik, metode blok dalam 0.000009hitungan detik.
naskah merek
Pengamatan yang bagus, masuk akal bagi saya, tapi saya pikir waktu eksekusi terlalu pendek untuk menyimpulkan bahwa algoritma X adalah Y kali lebih cepat daripada yang lain. Untuk memastikan kinerja eksekusi algoritma kita harus berhati-hati beberapa hal, seperti, Apakah ada beberapa proses yang berjalan pada saat yang sama? Sebagai contoh, mungkin ketika Anda menjalankan algoritma pertama Anda memiliki lebih banyak memori cache, dan sebagainya. Selain itu, hanya ada satu set data, saya pikir kita harus menjalankan dengan beberapa set data (dengan berbagai ukuran komposisi) untuk menyimpulkan.
pcambre
21

DasBoot memiliki pendekatan yang tepat, tetapi ada beberapa kesalahan dalam kodenya. Berikut cuplikan kode yang sepenuhnya generik yang akan membalikkan NSMutableArray apa pun di tempat:

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

Anda dapat membungkusnya dalam fungsi C, atau untuk poin bonus, gunakan kategori untuk menambahkannya ke NSMutableArray. (Dalam hal ini, 'array' akan menjadi 'mandiri'.) Anda juga dapat mengoptimalkannya dengan menetapkan [array count]ke variabel sebelum loop dan menggunakan variabel itu, jika diinginkan.

Jika Anda hanya memiliki NSArray biasa, tidak ada cara untuk membalikkannya, karena NSArrays tidak dapat dimodifikasi. Tetapi Anda dapat membuat salinan terbalik:

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

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

Atau gunakan trik kecil ini untuk melakukannya dalam satu baris:

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

Jika Anda hanya ingin mengulang array, Anda dapat menggunakan for/ inloop dengan [array reverseObjectEnumerator], tetapi sepertinya lebih efisien untuk digunakan -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.)
}];

( Catatan : Secara substansial diperbarui pada 2014 dengan pengalaman Foundation selama lima tahun lagi, satu atau dua fitur Objective-C baru, dan beberapa tips dari komentar.)

Brent Royal-Gordon
sumber
Apakah itu bekerja? Saya tidak berpikir NSMutableArray memiliki setObject: atIndex: metode. Terima kasih atas perbaikan yang disarankan untuk loop, dan menggunakan id umum bukan NSNumber.
Himadri Choudhury
Anda benar, saya menangkapnya ketika saya membaca beberapa contoh lainnya. Diperbaiki sekarang
Brent Royal-Gordon
2
[jumlah array] dipanggil setiap kali Anda mengulang. Ini sangat mahal. Bahkan ada fungsi yang mengubah posisi dua objek.
Georg Schölly
8

Setelah meninjau jawaban yang lain di atas dan menemukan diskusi Matt Gallagher di sini

Saya mengusulkan ini:

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

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

Seperti yang diamati Matt:

Dalam kasus di atas, Anda mungkin bertanya-tanya apakah - [NSArray reverseObjectEnumerator] akan dijalankan pada setiap iterasi dari loop - berpotensi memperlambat kode. <...>

Segera setelah itu, ia menjawab sebagai berikut:

<...> Ekspresi "koleksi" hanya dievaluasi satu kali, ketika perulangan for dimulai. Ini adalah kasus terbaik, karena Anda dapat dengan aman memasukkan fungsi mahal dalam ekspresi "koleksi" tanpa memengaruhi kinerja perulangan dari loop.

Aeronin
sumber
8

Kategori Georg Schölly sangat bagus. Namun, untuk NSMutableArray, menggunakan NSUIntegers untuk indeks akan menghasilkan crash ketika array kosong. Kode yang benar adalah:

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

Cara paling efisien untuk menghitung array secara terbalik:

Gunakan enumerateObjectsWithOptions:NSEnumerationReverse usingBlock. Menggunakan patokan @ JohannesFahrenkrug di atas, ini menyelesaikan 8x lebih cepat dari [[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);
naskah merek
sumber
7
NSMutableArray *objMyObject = [NSMutableArray arrayWithArray:[self reverseArray:objArrayToBeReversed]];

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

Membalikkan array dan mengulanginya:

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

Untuk memperbarui ini, di Swift dapat dilakukan dengan mudah dengan:

array.reverse()
Julio
sumber
1

Sedangkan untuk saya, sudahkah Anda mempertimbangkan bagaimana array dihuni di tempat pertama? Saya sedang dalam proses menambahkan objek BANYAK ke array, dan memutuskan untuk memasukkan masing-masing di awal, mendorong benda yang ada dengan satu. Membutuhkan array yang bisa berubah, dalam hal ini.

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

Atau cara 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
sumber
2
Rekursi adalah ide yang mengerikan pada struktur data besar, berdasarkan ingatan.
Eran Goldin
Jika dikompilasi dengan rekursi ekor itu seharusnya tidak menjadi masalah
simple_code
1

Ada cara mudah untuk melakukannya.

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

Saya harap ini membantu.

Vldan
sumber
0

Saya tidak tahu adanya metode bawaan. Tapi, coding dengan tangan tidak terlalu sulit. Dengan asumsi elemen array yang Anda hadapi adalah objek NSNumber bertipe integer, dan 'arr' adalah NSMutableArray yang ingin Anda balikkan.

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

Karena Anda mulai dengan NSArray maka Anda harus membuat array yang bisa berubah terlebih dahulu dengan konten NSArray asli ('origArray').

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

Sunting: Memperbaiki n -> n / 2 dalam jumlah loop dan mengubah NSNumber ke id yang lebih umum karena saran dalam jawaban Brent.

Himadri Choudhury
sumber
1
Apakah ini tidak ada rilis pada c?
Clay Bridges
0

Jika semua yang ingin Anda lakukan adalah beralih secara terbalik, coba ini:

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

Anda dapat melakukan [myArrayCount] sekali dan menyimpannya ke variabel lokal (saya pikir itu mahal), tetapi saya juga menduga bahwa kompiler akan melakukan hal yang sama dengan kode seperti yang ditulis di atas.

DougPA
sumber
0

Sintaks Swift 3:

let reversedArray = array.reversed()
fethica
sumber
0

Coba ini:

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

Berikut ini adalah makro bagus yang akan berfungsi untuk NSMutableArray ATAU 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]];\
    }\
}

Untuk menggunakan panggilan saja: reverseArray(myArray);

Albert Renshaw
sumber