Làm cách nào tôi có thể đảo ngược NSArray trong Objective-C?


356

Tôi cần phải đảo ngược của tôi NSArray.

Ví dụ:

[1,2,3,4,5] phải trở thành: [5,4,3,2,1]

cách tốt nhất để đạt được điều này là gì?


1
Cũng đáng để xem xét điều này: http://developer.apple.com/mac/l Library / documentation / Coloa / Contualual / Collections / Articles / subingFilteringArrays.html sẽ cho bạn biết cách sắp xếp một mảng theo thứ tự ngược lại (thường là những gì ví dụ, bạn đang sử dụng một mảng có nguồn gốc từ NSDipedia # allKeys và bạn muốn thứ tự ngày / alpha đảo ngược để đóng vai trò nhóm cho UITable trên iPhone, v.v.).

Câu trả lời:


305

Để có được một bản sao đảo ngược của một mảng, hãy xem giải pháp của danielpunkass bằng cách sử dụng reverseObjectEnumerator.

Để đảo ngược một mảng có thể thay đổi, bạn có thể thêm danh mục sau vào mã của mình:

@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
Một trong những điều tồi tệ về Tính toán nhanh là những người mới như tôi không tìm hiểu về những thứ hay ho như ReverseObjectEnumerator. Cách khá gọn gàng để làm điều đó.
Brent Royal-Gordon

4
Bởi vì C ++ - iterators có cú pháp thậm chí còn tệ hơn, chúng xấu.
Georg Schölly

4
Bạn không nên sao chép mảng trước khi trả lại?
CIFilter

12
@Georg: Tôi không đồng ý với bạn về điều này. Nếu tôi thấy một phương thức trả về một đối tượng bất biến, tôi hy vọng nó thực sự sẽ trả về một đối tượng bất biến. Có nó xuất hiện để trả về một đối tượng bất biến nhưng thực tế trả lại một đối tượng có thể thay đổi là một thực tế nguy hiểm để có được vào.
Christine

3
Gợi ý reverseObjectEnumerator allObjects là không hữu ích vì nó không đảo ngược các mảng có thể thay đổi, thậm chí thêm mutableCopy sẽ không giúp được gì vì vẫn còn những mảng ban đầu không bị biến đổi. Các tài liệu của Apple rằng tính không thay đổi không nên được kiểm tra trong thời gian chạy, nhưng được giả sử dựa trên loại được trả về, do đó, trả lại NSMutableArray trong trường hợp này là mã hoàn toàn chính xác.
Peter N Lewis

1286

Có một giải pháp dễ dàng hơn nhiều, nếu bạn tận dụng reverseObjectEnumeratorphương thức tích hợp sẵn NSArrayallObjectsphương pháp NSEnumerator:

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

allObjectsđược ghi lại là trả về một mảng với các đối tượng chưa được duyệt qua nextObject, theo thứ tự:

Mảng này chứa tất cả các đối tượng còn lại của điều tra theo thứ tự liệt kê .


6
Có một câu trả lời sâu hơn ở đây bởi Matt Williamson nên được nhận xét: Đừng sử dụng giải pháp của danielpunkass. Tôi đã sử dụng nó với suy nghĩ đó là một phím tắt tuyệt vời, nhưng bây giờ tôi chỉ mất 3 giờ để tìm hiểu tại sao thuật toán A * của tôi bị hỏng. Đó là vì nó trả về tập sai!
Georg Schölly

1
"Đặt sai" nghĩa là gì? Một mảng không theo thứ tự ngược lại?
Simo Salminen

31
Tôi không còn có thể tái tạo lỗi đó. Nó có thể là lỗi của tôi. Đây là một giải pháp rất thanh lịch.
Matt Williamson

3
Đơn hàng hiện được đảm bảo trong tài liệu.
jscs 3/03/2016

tôi chắc chắn đây là câu trả lời hay nhưng nó sẽ thất bại cho các đối tượng Mutable. Bởi vì NSEnumerator cung cấp loại đối tượng chỉ đọc @property (chỉ đọc, sao chép) NSArray <ObjectType> * allObjects;
Anurag Soni

49

Một số điểm chuẩn

1. ReverseObjectEnumerator allObjects

Đây là phương pháp nhanh nhất:

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

Kết quả: executionTime = 0.000026

2. Lặp lại một ReverseObjectEnumerator

Tốc độ này chậm hơn từ 1,5 lần đến 2,5 lần:

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

Kết quả: executionTime = 0.000071

3. sortArrayUsingComparator

Tốc độ này chậm hơn từ 30 lần đến 40 lần (không có gì ngạc nhiên ở đây):

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

Kết quả: executionTime = 0.001100

Vì vậy, [[anArray reverseObjectEnumerator] allObjects]là người chiến thắng rõ ràng khi nói đến tốc độ và dễ dàng.


Và tôi có thể tưởng tượng rằng nó sẽ chậm hơn rất nhiều so với 30-40x đối với số lượng đối tượng lớn hơn. Tôi không biết độ phức tạp của thuật toán sắp xếp là gì (trường hợp tốt nhất O (n * logn)?, Nhưng nó cũng gọi indexOfObject, có lẽ là O (n). Với cách sắp xếp, đó có thể là O (n ^ 2 * logn) hoặc một cái gì đó. Không tốt!
Joseph Humfrey

1
Điều gì về một điểm chuẩn sử dụng enumerateObjectsWithOptions:NSEnumerationReverse?
brandonscript

2
Tôi vừa thực hiện một điểm chuẩn bằng cách sử dụng enumerateObjectsWithOptions:NSEnumerationReverse- phương pháp hàng đầu hoàn thành trong 0.000072vài giây, phương pháp khối tính bằng 0.000009giây.
brandonscript

Quan sát tốt, nó có ý nghĩa với tôi, nhưng tôi nghĩ rằng thời gian thực hiện quá ngắn để kết luận rằng thuật toán X nhanh hơn Y lần khác. Để đảm bảo hiệu suất thực thi thuật toán, chúng tôi phải chăm sóc một số thứ, như, Có quá trình nào đang chạy cùng một lúc không?. Chẳng hạn, có lẽ khi bạn chạy thuật toán đầu tiên, bạn có sẵn nhiều bộ nhớ cache hơn, v.v. Hơn nữa, chỉ có một bộ dữ liệu, tôi nghĩ chúng ta nên chạy với một vài bộ dữ liệu (với các kích cỡ khác nhau là các thành phần) để kết luận.
pcambre

21

DasBoot có cách tiếp cận đúng, nhưng có một vài lỗi trong mã của anh ấy. Đây là một đoạn mã hoàn toàn chung chung sẽ đảo ngược mọi NSMutableArray tại chỗ:

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

Bạn có thể gói nó trong hàm C hoặc cho điểm thưởng, sử dụng các danh mục để thêm nó vào NSMutableArray. (Trong trường hợp đó, 'mảng' sẽ trở thành 'tự'.) Bạn cũng có thể tối ưu hóa nó bằng cách gán [array count]cho một biến trước vòng lặp và sử dụng biến đó, nếu bạn muốn.

Nếu bạn chỉ có NSArray thông thường, không có cách nào để đảo ngược nó tại chỗ, bởi vì NSArrays không thể được sửa đổi. Nhưng bạn có thể tạo một bản sao đảo ngược:

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

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

Hoặc sử dụng mẹo nhỏ này để thực hiện trong một dòng:

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

Nếu bạn chỉ muốn lặp lại một mảng ngược, bạn có thể sử dụng vòng lặp for/ , nhưng có thể sử dụng hiệu quả hơn một chút :in[array reverseObjectEnumerator]-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.)
}];

( Lưu ý : Được cập nhật đáng kể vào năm 2014 với năm năm kinh nghiệm Foundation, tính năng Objective-C mới hoặc hai và một vài lời khuyên từ các nhận xét.)


Nó có hoạt động không? Tôi không nghĩ NSMutableArray có phương thức setObject: atIndex :. Cảm ơn đã sửa lỗi được đề xuất cho vòng lặp và sử dụng id chung thay vì NSNumber.
Himadri Choudhury

Bạn nói đúng, tôi đã bắt gặp điều đó khi tôi đọc một số ví dụ khác. Đã sửa bây giờ.
Brent Royal-Gordon

2
[đếm mảng] được gọi mỗi lần bạn lặp. Điều này rất tốn kém. Thậm chí còn có một chức năng thay đổi vị trí của hai đối tượng.
Georg Schölly

8

Sau khi xem xét các câu trả lời của người khác ở trên và tìm thấy cuộc thảo luận của Matt Gallagher tại đây

Tôi đề xuất này:

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

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

Như Matt quan sát:

Trong trường hợp trên, bạn có thể tự hỏi liệu - [NSArray ReverseObjectEnumerator] sẽ được chạy trên mỗi lần lặp của vòng lặp - có khả năng làm chậm mã. <...>

Ngay sau đó, anh trả lời:

<...> Biểu thức "bộ sưu tập" chỉ được đánh giá một lần, khi vòng lặp for bắt đầu. Đây là trường hợp tốt nhất, vì bạn có thể đặt một hàm đắt tiền trong biểu thức "bộ sưu tập" một cách an toàn mà không ảnh hưởng đến hiệu suất mỗi lần lặp của vòng lặp.


8

Thể loại của Georg Schölly rất đẹp. Tuy nhiên, đối với NSMutableArray, việc sử dụng NSUIntegers cho các chỉ số dẫn đến sự cố khi mảng trống. Mã chính xác là:

@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

Cách hiệu quả nhất để liệt kê một mảng ngược lại:

Sử dụng enumerateObjectsWithOptions:NSEnumerationReverse usingBlock. Sử dụng điểm chuẩn của @ JohannesFahrenkrug ở trên, việc này hoàn thành nhanh hơn 8 lần so với [[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

Đảo ngược mảng và lặp qua nó:

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

2

Để cập nhật điều này, trong Swift có thể thực hiện dễ dàng với:

array.reverse()

1

Đối với tôi, bạn đã xem xét làm thế nào các mảng được phổ biến ở nơi đầu tiên? Tôi đang trong quá trình thêm NHIỀU đối tượng vào một mảng và quyết định chèn từng đối tượng vào lúc đầu, đẩy bất kỳ đối tượng hiện có nào lên một. Yêu cầu một mảng có thể thay đổi, trong trường hợp này.

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

1

Hoặc cách 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
Đệ quy là một ý tưởng khủng khiếp trên các cấu trúc dữ liệu lớn, bộ nhớ khôn ngoan.
Eran Goldin

Nếu nó biên dịch với đệ quy đuôi thì đó không phải là vấn đề
Simple_code

1

Có một cách dễ dàng để làm điều đó.

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

Tôi hi vọng cái này giúp được.


0

Tôi không biết bất kỳ phương pháp được xây dựng trong. Nhưng, mã hóa bằng tay không quá khó. Giả sử các phần tử của mảng bạn đang xử lý là các đối tượng NSNumber có kiểu nguyên và 'mảng' là NSMutableArray mà bạn muốn đảo ngược.

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

Vì bạn bắt đầu với NSArray nên trước tiên bạn phải tạo mảng có thể thay đổi với nội dung của NSArray gốc ('origArray').

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

Chỉnh sửa: Đã sửa n -> n / 2 trong số vòng lặp và thay đổi NSNumber thành id chung hơn do các đề xuất trong câu trả lời của Brent.


1
Đây không phải là thiếu một bản phát hành trên c?
Cầu đất sét

0

Nếu tất cả những gì bạn muốn làm là lặp lại, hãy thử điều này:

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

Bạn có thể thực hiện [myArrayCount] một lần và lưu nó vào một biến cục bộ (tôi nghĩ nó đắt tiền), nhưng tôi cũng đoán rằng trình biên dịch sẽ thực hiện tương tự với mã như được viết ở trên.



0

Thử cái này:

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

Đây là một macro đẹp sẽ hoạt động cho cả NSMutableArray HOẶC 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]];\
    }\
}

Để sử dụng chỉ cần gọi: reverseArray(myArray);

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.