Как мне отсортировать NSMutableArray с пользовательскими объектами в нем?

1056

То, что я хочу сделать, кажется довольно простым, но я не могу найти ответы в Интернете. У меня есть NSMutableArray объектов, и пусть они являются объектами Person. Я хочу отсортировать NSMutableArray по Person.birthDate, который является NSDate.

Я думаю, что это имеет какое-то отношение к этому методу:

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

В Java я хотел бы, чтобы мой объект реализовал Comparable или использовал Collections.sort с встроенным пользовательским компаратором... как вы это делаете в Objective-C?

Теги:
sorting
nsmutablearray
cocoa-touch

22 ответа

2156
Лучший ответ

Метод сравнения

Либо вы реализуете метод сравнения для своего объекта:

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

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

NSSortDescriptor (лучше)

или обычно даже лучше:

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

Вы можете легко сортировать по нескольким клавишам, добавляя в массив больше одного. Также возможно использование пользовательских методов-компараторов. Посмотрите документацию.

Блоки (блестящие!)

Также существует возможность сортировки с блоком с Mac OS X 10.6 и 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];
}];

Производительность

Методы -compare: и на основе блоков будут в целом более быстрыми, чем использование NSSortDescriptor, поскольку последний полагается на KVC. Основное преимущество метода NSSortDescriptor заключается в том, что он обеспечивает способ определения вашего порядка сортировки с использованием данных, а не кода, что упрощает его, например, чтобы пользователи могли сортировать NSTableView, щелкнув строку заголовка.

  • 64
    В первом примере есть ошибка: вы сравниваете переменную экземпляра birthDate в одном объекте с самим другим объектом, а не с его переменной birthDate.
  • 88
    @ Мартин: Спасибо! Забавно, что никто не заметил, прежде чем я получил 75 голосов за это.
Показать ещё 27 комментариев
101

См. метод NSMutableArray sortUsingFunction:context:

Вам нужно будет настроить функцию сравнения, которая принимает два объекта (типа Person, поскольку вы сравниваете два объекта Person) и параметр контекста.

Два объекта - это просто экземпляры Person. Третий объект представляет собой строку, например. @ "РОЖДЕНИЕ".

Эта функция возвращает NSComparisonResult: она возвращает NSOrderedAscending, если PersonA.birthDate < PersonB.birthDate. Он вернет NSOrderedDescending, если PersonA.birthDate > PersonB.birthDate. Наконец, он вернет NSOrderedSame, если PersonA.birthDate == PersonB.birthDate.

Это грубый псевдокод; вам нужно будет определить, что означает для одной даты "меньше", "больше" или "равно" другой дате (например, сравнение секунд-с-эпох и т.д.):

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

Если вы хотите что-то более компактное, вы можете использовать тройные операторы:

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

Вложение может немного ускорить это, если вы сделаете это много.

  • 9
    Использование sortUsingFunction: context: является, вероятно, наиболее точным способом и определенно самым нечитаемым.
  • 10
    Что не так с подходом "c-ish"? Работает нормально.
Показать ещё 8 комментариев
55

Я сделал это в iOS 4, используя блок. Пришлось отличать элементы моего массива от id до моего типа класса. В этом случае это класс под названием "Оценка" с свойством, называемым точками.

Также вам нужно решить, что делать, если элементы вашего массива не соответствуют типу, для этого примера я только что вернул NSOrderedSame, однако в моем коде я, хотя и исключение.

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: Это сортировка в порядке убывания.

  • 6
    Вам на самом деле здесь не нужны приведения "(Score *)", вы можете просто выполнить "Score * s1 = obj1;" потому что id с радостью приведёт к чему угодно без предупреждения от компилятора :-)
  • 0
    правый апельсин, downcasting рейтинг, не требует приведения перед слабой переменной.
Показать ещё 4 комментария
25

Я пробовал все, но это сработало для меня. В классе у меня есть другой класс с именем "crimeScene" и хочу сортировать по свойству "crimeScene".

Это работает как прелесть:

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

Начиная с iOS 4 вы также можете использовать блоки для сортировки.

В этом конкретном примере я предполагаю, что объекты в вашем массиве имеют метод "position", который возвращает 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];

Примечание: "отсортированный" массив будет автореализован.

17

В отсутствует второй ответ Георга Шёлли, но он отлично работает.

NSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"birthDate"
                                              ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingDescriptor:sortDescriptors];
  • 0
    Вызов метода на самом деле "sortedArrayUsingDescriptors:", с 's' в конце.
  • 0
    Спасибо, еще не видел этого.
16
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"birthDate" ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingDescriptors:sortDescriptors];

Спасибо, он отлично работает...

16

Для объектов Person необходимо реализовать метод, например compare:, который принимает еще один объект Person и возвращает NSComparisonResult в соответствии с отношением между двумя объектами.

Затем вы вызываете sortedArrayUsingSelector: с помощью @selector(compare:), и это должно быть сделано.

Существуют и другие способы, но насколько я знаю, нет Cocoa -equiv интерфейса Comparable. Использование sortedArrayUsingSelector: - это, вероятно, самый безболезненный способ сделать это.

8

Блоки iOS 4 сохранят вас:)

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 немного описания

7

Для NSMutableArray используйте метод sortUsingSelector. Он сортирует это место, не создавая новый экземпляр.

  • 0
    Просто обновление: я тоже искал что-то, что сортировало изменяемый массив на месте, теперь есть «sortUsing» эквивалентные методы для всех методов «sortedArrayUsing», начиная с iOS 7. Например, sortUsingComparator:
5

Если вы просто сортируете массив NSNumbers, вы можете отсортировать их с помощью 1 вызова:

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

Это работает, потому что объекты в массиве (NSNumber objects) реализуют метод сравнения. Вы можете сделать то же самое для объектов NSString или даже для массива пользовательских объектов данных, реализующих метод сравнения.

Вот пример кода с использованием блоков компаратора. Он сортирует массив словарей, где каждый словарь содержит число в ключе "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;
 }];

В приведенном выше коде рассматривается работа по получению целочисленного значения для каждого ключа сортировки и их сравнение в качестве иллюстрации того, как это сделать. Поскольку объекты NSNumber реализуют метод сравнения, его можно было бы переписать гораздо проще:

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

или тело компаратора может быть даже перегоняется до 1 строки:

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

Я предпочитаю простые операторы и множество временных переменных, потому что код легче читать и проще отлаживать. Компилятор в любом случае оптимизирует временные переменные, поэтому нет преимущества для версии "все в одной строке".

4

Вы можете использовать следующий общий метод для своей цели. Он должен решить вашу проблему.

//Called method
-(NSMutableArray*)sortArrayList:(NSMutableArray*)arrDeviceList filterKeyName:(NSString*)sortKeyName ascending:(BOOL)isAscending{
    NSSortDescriptor *sorter = [[NSSortDescriptor alloc] initWithKey:sortKeyName ascending:isAscending];
    [arrDeviceList sortUsingDescriptors:[NSArray arrayWithObject:sorter]];
    return arrDeviceList;
}

//Calling method
[self sortArrayList:arrSomeList filterKeyName:@"anything like date,name etc" ascending:YES];
4

Я просто выполнил многоуровневую сортировку на основе пользовательских требований.

//сортировать значения

    [arrItem sortUsingComparator:^NSComparisonResult (id a, id b){

    ItemDetail * itemA = (ItemDetail*)a;
    ItemDetail* itemB =(ItemDetail*)b;

    //item price are same
    if (itemA.m_price.m_selling== itemB.m_price.m_selling) {

        NSComparisonResult result=  [itemA.m_itemName compare:itemB.m_itemName];

        //if item names are same, then monogramminginfo has to come before the non monograme item
        if (result==NSOrderedSame) {

            if (itemA.m_monogrammingInfo) {
                return NSOrderedAscending;
            }else{
                return NSOrderedDescending;
            }
        }
        return result;
    }

    //asscending order
    return itemA.m_price.m_selling > itemB.m_price.m_selling;
}];

https://sites.google.com/site/greateindiaclub/mobil-apps/ios/multilevelsortinginiosobjectivec

4

Я создал небольшую библиотеку методов категорий, называемую Linq to ObjectiveC, что делает эту вещь более легкой. Используя метод sort с помощью селектора клавиш, вы можете сортировать по birthDate следующим образом:

NSArray* sortedByBirthDate = [input sort:^id(id person) {
    return [person birthDate];
}]
  • 0
    Вы должны назвать это « LINQ to Objective-C ».
4

Я использовал sortUsingFunction:: в некоторых моих проектах:

int SortPlays(id a, id b, void* context)
{
    Play* p1 = a;
    Play* p2 = b;
    if (p1.score<p2.score) 
        return NSOrderedDescending;
    else if (p1.score>p2.score) 
        return NSOrderedAscending;
    return NSOrderedSame;
}

...
[validPlays sortUsingFunction:SortPlays context:nil];
3
-(NSMutableArray*) sortArray:(NSMutableArray *)toBeSorted 
{
  NSArray *sortedArray;
  sortedArray = [toBeSorted sortedArrayUsingComparator:^NSComparisonResult(id a, id b) 
  {
    return [a compare:b];
 }];
 return [sortedArray mutableCopy];
}
  • 0
    зачем передавать в изменяемый массив, когда возвращается новый массив. зачем вообще создавать обертку?
2

Сортировка NSMutableArray очень проста:

NSMutableArray *arrayToFilter =
     [[NSMutableArray arrayWithObjects:@"Photoshop",
                                       @"Flex",
                                       @"AIR",
                                       @"Flash",
                                       @"Acrobat", nil] autorelease];

NSMutableArray *productsToRemove = [[NSMutableArray array] autorelease];

for (NSString *products in arrayToFilter) {
    if (fliterText &&
        [products rangeOfString:fliterText
                        options:NSLiteralSearch|NSCaseInsensitiveSearch].length == 0)

        [productsToRemove addObject:products];
}
[arrayToFilter removeObjectsInArray:productsToRemove];
0

Быстрые протоколы и функциональное программирование очень упрощают вам просто сделать свой класс совместимым с протоколом Comparable, реализовать методы, требуемые протоколом, а затем использовать отсортированную (по:) функцию высокого порядка для создания отсортированного массива без кстати, нужно использовать изменяемые массивы.

class Person: Comparable {
    var birthDate: NSDate?
    let name: String

    init(name: String) {
        self.name = name
    }

    static func ==(lhs: Person, rhs: Person) -> Bool {
        return lhs.birthDate === rhs.birthDate || lhs.birthDate?.compare(rhs.birthDate as! Date) == .orderedSame
    }

    static func <(lhs: Person, rhs: Person) -> Bool {
        return lhs.birthDate?.compare(rhs.birthDate as! Date) == .orderedAscending
    }

    static func >(lhs: Person, rhs: Person) -> Bool {
        return lhs.birthDate?.compare(rhs.birthDate as! Date) == .orderedDescending
    }

}

let p1 = Person(name: "Sasha")
p1.birthDate = NSDate() 

let p2 = Person(name: "James")
p2.birthDate = NSDate()//he is older by miliseconds

if p1 == p2 {
    print("they are the same") //they are not
}

let persons = [p1, p2]

//sort the array based on who is older
let sortedPersons = persons.sorted(by: {$0 > $1})

//print sasha which is p1
print(persons.first?.name)
//print James which is the "older"
print(sortedPersons.first?.name)
0

В моем случае я использую sortedArrayUsingComparator для сортировки массива. Посмотрите на приведенный ниже код.

contactArray = [[NSArray arrayWithArray:[contactSet allObjects]] sortedArrayUsingComparator:^NSComparisonResult(ContactListData *obj1, ContactListData *obj2) {
    NSString *obj1Str = [NSString stringWithFormat:@"%@ %@",obj1.contactName,obj1.contactSurname];
    NSString *obj2Str = [NSString stringWithFormat:@"%@ %@",obj2.contactName,obj2.contactSurname];
    return [obj1Str compare:obj2Str];
}];

Также мой объект,

@interface ContactListData : JsonData
@property(nonatomic,strong) NSString * contactName;
@property(nonatomic,strong) NSString * contactSurname;
@property(nonatomic,strong) NSString * contactPhoneNumber;
@property(nonatomic) BOOL isSelected;
@end
0

Сортировка с использованием NSComparator

Если мы хотим сортировать пользовательские объекты, нам нужно предоставить NSComparator, который используется для сравнения пользовательских объектов. Блок возвращает значение NSComparisonResult для обозначения порядка двух объектов. Поэтому для сортировки целого массива NSComparator используется следующим образом.

NSArray *sortedArray = [employeesArray sortedArrayUsingComparator:^NSComparisonResult(Employee *e1, Employee *e2){
    return [e1.firstname compare:e2.firstname];    
}];

Сортировка с использованием NSSortDescriptor
Предположим в качестве примера, что у нас есть массив, содержащий экземпляры пользовательского класса, Employee имеет атрибуты firstname, lastname и age. В следующем примере показано, как создать NSSortDescriptor, который может использоваться для сортировки содержимого массива в возрастающем порядке по возрастному ключу.

NSSortDescriptor *ageDescriptor = [[NSSortDescriptor alloc] initWithKey:@"age" ascending:YES];
NSArray *sortDescriptors = @[ageDescriptor];
NSArray *sortedArray = [employeesArray sortedArrayUsingDescriptors:sortDescriptors];

Сортировка с использованием пользовательских сопоставлений
Имена являются строками, и когда вы сортируете строки для представления пользователю, вы всегда должны использовать локализованное сравнение. Часто вы также хотите провести сравнение без учета регистра. Вот пример с (localizedStandardCompare:) для упорядочивания массива по имени и имени.

NSSortDescriptor *lastNameDescriptor = [[NSSortDescriptor alloc]
              initWithKey:@"lastName" ascending:YES selector:@selector(localizedStandardCompare:)];
NSSortDescriptor * firstNameDescriptor = [[NSSortDescriptor alloc]
              initWithKey:@"firstName" ascending:YES selector:@selector(localizedStandardCompare:)];
NSArray *sortDescriptors = @[lastNameDescriptor, firstNameDescriptor];
NSArray *sortedArray = [employeesArray sortedArrayUsingDescriptors:sortDescriptors];

Для справки и подробного обсуждения см. https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/SortDescriptors/Articles/Creating.html
http://www.ios-blog.co.uk/tutorials/objective-c/how-to-sort-nsarray-with-custom-objects/

0
NSSortDescriptor  *sort = [[NSSortDescriptor alloc] initWithKey:@"_strPrice"
                                                 ascending:sortFlag selector:@selector(localizedStandardCompare:)] ;
-3
NSMutableArray *stockHoldingCompanies = [NSMutableArray arrayWithObjects:fortune1stock,fortune2stock,fortune3stock,fortune4stock,fortune5stock,fortune6stock , nil];

NSSortDescriptor *sortOrder = [NSSortDescriptor sortDescriptorWithKey:@"companyName" ascending:NO];

[stockHoldingCompanies sortUsingDescriptors:[NSArray arrayWithObject:sortOrder]];

NSEnumerator *enumerator = [stockHoldingCompanies objectEnumerator];

ForeignStockHolding *stockHoldingCompany;

NSLog(@"Fortune 6 companies sorted by Company Name");

    while (stockHoldingCompany = [enumerator nextObject]) {
        NSLog(@"===============================");
        NSLog(@"CompanyName:%@",stockHoldingCompany.companyName);
        NSLog(@"Purchase Share Price:%.2f",stockHoldingCompany.purchaseSharePrice);
        NSLog(@"Current Share Price: %.2f",stockHoldingCompany.currentSharePrice);
        NSLog(@"Number of Shares: %i",stockHoldingCompany.numberOfShares);
        NSLog(@"Cost in Dollars: %.2f",[stockHoldingCompany costInDollars]);
        NSLog(@"Value in Dollars : %.2f",[stockHoldingCompany valueInDollars]);
    }
    NSLog(@"===============================");

Ещё вопросы

Сообщество Overcoder
Наверх
Меню