Câu hỏi Làm thế nào để sắp xếp một NSMutableArray với các đối tượng tùy chỉnh trong nó?


Những gì tôi muốn làm có vẻ khá đơn giản, nhưng tôi không thể tìm thấy bất kỳ câu trả lời nào trên web. tôi có một NSMutableArray các đối tượng và giả sử chúng là đối tượng 'Người'. Tôi muốn sắp xếp NSMutableArray bởi Person.birthDate là một NSDate.

Tôi nghĩ rằng nó có một cái gì đó để làm với phương pháp này:

NSArray *sortedArray = [drinkDetails sortedArrayUsingSelector:@selector(???)];

Trong Java tôi sẽ làm cho đối tượng của tôi thực hiện Comparable, hoặc sử dụng Collections.sort với một so sánh tùy chỉnh nội tuyến ... làm thế nào trên trái đất để bạn làm điều này trong Objective-C?


1175
2018-04-30 06:10


gốc




Các câu trả lời:


Phương pháp so sánh

Hoặc bạn thực hiện phương pháp so sánh cho đối tượng của mình:

- (NSComparisonResult)compare:(Person *)otherObject {
    return [self.birthDate compare:otherObject.birthDate];
}

NSArray *sortedArray = [drinkDetails sortedArrayUsingSelector:@selector(compare:)];

NSSortDescriptor (tốt hơn)

hoặc thường tốt hơn nữa:

NSSortDescriptor *sortDescriptor;
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"birthDate"
                                           ascending:YES];
NSArray *sortedArray = [drinkDetails sortedArrayUsingDescriptors:@[sortDescriptor]];

Bạn có thể dễ dàng sắp xếp theo nhiều khóa bằng cách thêm nhiều hơn một cho mảng. Sử dụng phương pháp so sánh tùy chỉnh cũng có thể. Hãy xem tài liệu.

Khối (sáng bóng!)

Ngoài ra còn có khả năng sắp xếp với một khối từ Mac OS X 10.6 và iOS 4:

NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
    NSDate *first = [(Person*)a birthDate];
    NSDate *second = [(Person*)b birthDate];
    return [first compare:second];
}];

Hiệu suất

Các -compare: và các phương pháp dựa trên khối sẽ nhanh hơn một chút, nói chung, hơn là sử dụng NSSortDescriptor như sau này phụ thuộc vào KVC. Ưu điểm chính của NSSortDescriptor phương pháp là nó cung cấp một cách để xác định thứ tự sắp xếp của bạn bằng cách sử dụng dữ liệu, chứ không phải là mã, giúp dễ dàng, ví dụ: thiết lập mọi thứ để người dùng có thể sắp xếp NSTableView bằng cách nhấp vào hàng tiêu đề.


2216
2018-04-30 06:24



Ví dụ đầu tiên có một lỗi: Bạn so sánh biến cá thể birthDate trong một đối tượng với chính đối tượng khác, thay vì biến birthDate của nó. - Martin Gjaldbaek
@Martin: Cảm ơn! Hài hước mà không ai khác nhận thấy trước khi tôi có 75 upvotes cho nó. - Georg Schölly
Bởi vì đây là câu trả lời được chấp nhận, và do đó có thể được coi là dứt khoát bởi hầu hết người dùng, có thể hữu ích khi thêm một ví dụ dựa trên khối thứ 3 để người dùng cũng biết rằng nó tồn tại. - jpswain
@ cam80: Tôi đã thử điều đó. Tôi không sở hữu máy Mac nữa, vì vậy sẽ rất tuyệt nếu bạn có thể xem mã. - Georg Schölly
Nếu bạn có một NSMutableArray tôi thích sử dụng các phương pháp sortUsingDescriptors , sortUsingFunction hoặc là sortUsingSelector. Theo như mảng là mutable tôi thường không cần một bản sao được sắp xếp. - Stephan


Xem NSMutableArray phương pháp sortUsingFunction:context: 

Bạn sẽ cần thiết lập so sánh hàm nhận hai đối tượng (thuộc loại Person, vì bạn đang so sánh hai Person các đối tượng) và bối cảnh tham số.

Hai đối tượng chỉ là trường hợp của Person. Đối tượng thứ ba là một chuỗi, ví dụ: @"ngày sinh".

Hàm này trả về một NSComparisonResult: Nó trở lại NSOrderedAscending nếu PersonA.birthDate < PersonB.birthDate. Nó sẽ trở lại NSOrderedDescending nếu PersonA.birthDate > PersonB.birthDate. Cuối cùng, nó sẽ trở lại NSOrderedSame nếu PersonA.birthDate == PersonB.birthDate.

Đây là mã giả thô; bạn sẽ cần phải xác định ý nghĩa của một ngày để trở thành "ít", "nhiều hơn" hoặc "bình đẳng" sang một ngày khác (chẳng hạn như so sánh giây-từ-epoch, vv):

NSComparisonResult compare(Person *firstPerson, Person *secondPerson, void *context) {
  if ([firstPerson birthDate] < [secondPerson birthDate])
    return NSOrderedAscending;
  else if ([firstPerson birthDate] > [secondPerson birthDate])
    return NSOrderedDescending;
  else 
    return NSOrderedSame;
}

Nếu bạn muốn một cái gì đó nhỏ gọn hơn, bạn có thể sử dụng các toán tử ternary:

NSComparisonResult compare(Person *firstPerson, Person *secondPerson, void *context) {
  return ([firstPerson birthDate] < [secondPerson birthDate]) ? NSOrderedAscending : ([firstPerson birthDate] > [secondPerson birthDate]) ? NSOrderedDescending : NSOrderedSame;
}

Nội tuyến có lẽ có thể tăng tốc độ này một chút, nếu bạn làm điều này rất nhiều.


103
2018-03-24 22:15



Sử dụng sortUsingFunction: context: có lẽ là cách c-ish nhất và chắc chắn là không thể đọc được nhất. - Georg Schölly
Có gì sai với cách tiếp cận "c-ish"? Nó hoạt động tốt. - Alex Reynolds
Không có gì thực sự sai với nó, nhưng tôi nghĩ rằng bây giờ có nhiều lựa chọn thay thế tốt hơn. - Georg Schölly
Có lẽ, nhưng tôi không nghĩ nó sẽ dễ đọc hơn đối với một người nào đó từ một nền Java có thể đang tìm kiếm thứ gì đó tương tự như lớp Comparator trừu tượng của Java, nó thực hiện so sánh (Type obj1, Type obj2). - Alex Reynolds
Tôi có cảm giác rằng một vài người trong số các bạn đang tìm kiếm bất kỳ lý do gì để chỉ trích câu trả lời hoàn toàn tốt đẹp này, ngay cả khi những lời chỉ trích đó có rất ít công đức kỹ thuật. Kỳ dị. - Alex Reynolds


Tôi đã làm điều này trong iOS 4 bằng cách sử dụng một khối. Đã phải đưa các phần tử của mảng của tôi từ id vào loại lớp của tôi. Trong trường hợp này, đó là một lớp được gọi là Điểm với một thuộc tính được gọi là điểm.

Ngoài ra, bạn cần phải quyết định phải làm gì nếu các phần tử của mảng của bạn không phải là loại phù hợp, ví dụ này tôi vừa trả về NSOrderedSame, tuy nhiên trong mã của tôi, tôi là một ngoại lệ.

NSArray *sorted = [_scores sortedArrayUsingComparator:^(id obj1, id obj2){
    if ([obj1 isKindOfClass:[Score class]] && [obj2 isKindOfClass:[Score class]]) {
        Score *s1 = obj1;
        Score *s2 = obj2;

        if (s1.points > s2.points) {
            return (NSComparisonResult)NSOrderedAscending;
        } else if (s1.points < s2.points) {
            return (NSComparisonResult)NSOrderedDescending;
        }
    }

    // TODO: default is the same?
    return (NSComparisonResult)NSOrderedSame;
}];

return sorted;

PS: Sắp xếp theo thứ tự giảm dần.


58
2018-03-16 18:46



Bạn không thực sự cần các "(Điểm *)" phôi trong đó, bạn chỉ có thể làm "Điểm * s1 = obj1;" bởi vì id sẽ vui vẻ truyền đến bất cứ điều gì mà không cần cảnh báo từ trình biên dịch :-) - jpswain
đúng màu cam80 downcasting không yêu cầu một diễn viên trước biến yếu. - thesummersign
Cảm ơn các bạn, tôi đã xóa diễn viên khỏi câu trả lời - Chris
Bạn nên sắp xếp nil so với không nil ở trên cùng hoặc dưới cùng một cách nhất quán, vì vậy kết quả trả về mặc định có thể là return ((!obj1 && !obj2) ? NSOrderedSame : (obj1 ? NSOrderedAscending : NSOrderedDescending)) - Scott Corscadden
heh Chris, tôi đã thử mã này, tôi làm hv một làm mới trong chương trình của tôi .. lần đầu tiên tôi làm công việc chính xác, có một đầu ra thứ tự giảm dần .. nhưng khi tôi làm mới (thực hiện cùng một mã với cùng một dữ liệu) nó thay đổi thứ tự, nó không giảm dần .. Nói i hv 4 đối tượng trong mảng của tôi, 3 hv cùng một dữ liệu, 1 là khác nhau. - Nikesh K


Bắt đầu từ iOS 4, bạn cũng có thể sử dụng các khối để sắp xếp.

Đối với ví dụ cụ thể này, tôi giả định rằng các đối tượng trong mảng của bạn có phương thức 'position', nó trả về một NSInteger.

NSArray *arrayToSort = where ever you get the array from... ;
NSComparisonResult (^sortBlock)(id, id) = ^(id obj1, id obj2) 
{
    if ([obj1 position] > [obj2 position]) 
    { 
        return (NSComparisonResult)NSOrderedDescending;
    }
    if ([obj1 position] < [obj2 position]) 
    {
        return (NSComparisonResult)NSOrderedAscending;
    }
    return (NSComparisonResult)NSOrderedSame;
};
NSArray *sorted = [arrayToSort sortedArrayUsingComparator:sortBlock];

Lưu ý: mảng "được sắp xếp" sẽ được tự động phát hành.


28
2017-11-22 10:51





Tôi đã thử tất cả, nhưng điều này làm việc cho tôi. Trong một lớp, tôi có một lớp khác có tên là "crimeScene"và muốn sắp xếp theo thuộc tính"crimeScene".

Công việc này như một cái duyên vậy:

NSSortDescriptor *sorter = [[NSSortDescriptor alloc] initWithKey:@"crimeScene.distance" ascending:YES];
[self.arrAnnotations sortUsingDescriptors:[NSArray arrayWithObject:sorter]];

24
2018-05-06 16:25





Có một bước mất tích trong Câu trả lời thứ hai của Georg Schölly, nhưng nó hoạt động tốt sau đó.

NSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"birthDate"
                                              ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingDescriptor:sortDescriptors];

19
2018-03-30 05:54



Phương thức gọi thực sự là "formattedArrayUsingDescriptors:", với 's' ở cuối. - LucasTizma
Cảm ơn, đã không nhìn thấy 's'. - Georg Schölly


NSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"birthDate" ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingDescriptors:sortDescriptors];

Cảm ơn, nó hoạt động tốt ...


18
2018-04-30 06:20





Của bạn Person các đối tượng cần thực hiện một phương thức, nói compare: cái khác Person đối tượng và trả lại NSComparisonResult theo mối quan hệ giữa 2 vật thể.

Sau đó, bạn sẽ gọi sortedArrayUsingSelector: với @selector(compare:) và nó nên được thực hiện.

Có nhiều cách khác, nhưng theo như tôi biết không có Cocoa-equiv của Comparable giao diện. Sử dụng sortedArrayUsingSelector: có lẽ là cách không đau đớn nhất để làm điều đó.


16
2017-11-09 13:47





Các khối iOS 4 sẽ giúp bạn tiết kiệm :)

featuresArray = [[unsortedFeaturesArray sortedArrayUsingComparator: ^(id a, id b)  
{
    DMSeatFeature *first = ( DMSeatFeature* ) a;
    DMSeatFeature *second = ( DMSeatFeature* ) b;

    if ( first.quality == second.quality )
        return NSOrderedSame;
    else
    {
        if ( eSeatQualityGreen  == m_seatQuality || eSeatQualityYellowGreen == m_seatQuality || eSeatQualityDefault  == m_seatQuality )
        {
            if ( first.quality < second.quality )
                return NSOrderedAscending;
            else
                return NSOrderedDescending;
        }
        else // eSeatQualityRed || eSeatQualityYellow
        {
            if ( first.quality > second.quality )
                return NSOrderedAscending;
            else
                return NSOrderedDescending;
        } 
    }
}] retain];

http://sokol8.blogspot.com/2011/04/sorting-nsarray-with-blocks.html một chút mô tả


8
2017-12-06 07:51





Dành cho NSMutableArray, sử dụng sortUsingSelector phương pháp. Nó sắp xếp nó, không tạo ra một thể hiện mới.


7
2018-04-16 09:17



Chỉ cần một bản cập nhật: Tôi cũng đang tìm kiếm thứ gì đó đã sắp xếp mảng có thể thay đổi tại chỗ, hiện có phương pháp tương đương "sortUsing" cho tất cả các phương thức "formattedArrayUsing" như của iOS 7. Chẳng hạn như sortUsingComparator:. - jmathew


Nếu bạn chỉ sắp xếp một mảng NSNumbers, bạn có thể sắp xếp chúng với 1 cuộc gọi:

[arrayToSort sortUsingSelector: @selector(compare:)];

Điều đó hoạt động vì các đối tượng trong mảng (NSNumber các đối tượng) thực hiện phương pháp so sánh. Bạn có thể làm điều tương tự cho NSString các đối tượng, hoặc thậm chí cho một mảng các đối tượng dữ liệu tùy chỉnh thực hiện một phương thức so sánh.

Dưới đây là một số mã ví dụ sử dụng các khối so sánh. Nó sắp xếp một loạt các từ điển mà mỗi từ điển bao gồm một số trong một khóa "sort_key".

#define SORT_KEY @\"sort_key\"

[anArray sortUsingComparator: 
 ^(id obj1, id obj2) 
  {
  NSInteger value1 = [[obj1 objectForKey: SORT_KEY] intValue];
  NSInteger value2 = [[obj2 objectForKey: SORT_KEY] intValue];
  if (value1 > value2) 
{
  return (NSComparisonResult)NSOrderedDescending;
  }

  if (value1 < value2) 
{
  return (NSComparisonResult)NSOrderedAscending;
  }
    return (NSComparisonResult)NSOrderedSame;
 }];

Đoạn mã trên đi qua công việc nhận giá trị số nguyên cho mỗi khóa sắp xếp và so sánh chúng, như là một minh họa về cách thực hiện nó. Vì NSNumber các đối tượng thực hiện một phương thức so sánh, nó có thể được viết lại đơn giản hơn nhiều:

 #define SORT_KEY @\"sort_key\"

[anArray sortUsingComparator: 
^(id obj1, id obj2) 
 {
  NSNumber* key1 = [obj1 objectForKey: SORT_KEY];
  NSNumber* key2 = [obj2 objectForKey: SORT_KEY];
  return [key1 compare: key2];
 }];

hoặc cơ thể của bộ so sánh thậm chí có thể được chưng cất xuống 1 dòng:

  return [[obj1 objectForKey: SORT_KEY] compare: [obj2 objectForKey: SORT_KEY]];

Tôi có xu hướng thích các câu lệnh đơn giản và nhiều biến tạm thời vì mã dễ đọc hơn và dễ debug hơn. Trình biên dịch tối ưu hóa các biến tạm thời, vì vậy không có lợi thế cho phiên bản tất cả trong một dòng.


5
2018-03-06 08:29