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

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

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

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

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

Ответ 1

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

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

- (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, щелкнув строку заголовка.

Ответ 2

См. метод 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;
}

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

Ответ 3

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

Ответ 4

Начиная с 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];

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

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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

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

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

Ответ 9

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

Ответ 10

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

Ответ 11

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

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

Ответ 12

Если вы просто сортируете массив 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]];

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

Ответ 13

Я использовал 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];

Ответ 14

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

NSArray* sortedByBirthDate = [input sort:^id(id person) {
    return [person birthDate];
}]

Ответ 15

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

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

    [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

Ответ 16

-(NSMutableArray*) sortArray:(NSMutableArray *)toBeSorted 
{
  NSArray *sortedArray;
  sortedArray = [toBeSorted sortedArrayUsingComparator:^NSComparisonResult(id a, id b) 
  {
    return [a compare:b];
 }];
 return [sortedArray mutableCopy];
}

Ответ 17

Вы используете NSSortDescriptor для сортировки NSMutableArray с пользовательскими объектами

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

Ответ 18

Сортировка 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];

Ответ 19

Сортировка с использованием 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/

Ответ 20

Быстрые протоколы и функциональное программирование очень упрощают вам просто сделать свой класс совместимым с протоколом 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)

Ответ 21

NSSortDescriptor  *sort = [[NSSortDescriptor alloc] initWithKey:@"_strPrice"
                                                 ascending:sortFlag selector:@selector(localizedStandardCompare:)] ;

Ответ 22

В моем случае я использую 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

Ответ 23

Сортировать массив в Swift


Для Swifty человек ниже - это очень чистый метод для достижения поставленной цели в глобальном масштабе. Давайте рассмотрим пример пользовательского класса User, который имеет некоторые атрибуты.

class User: NSObject {
    var id: String?
    var name: String?
    var email: String?
    var createdDate: Date?
}

Теперь у нас есть массив, который нам нужно отсортировать на основе createdDate по возрастанию и/или по убыванию. Итак, давайте добавим функцию для сравнения дат.

class User: NSObject {
    var id: String?
    var name: String?
    var email: String?
    var createdDate: Date?
    func checkForOrder(_ otherUser: User, _ order: ComparisonResult) -> Bool {
        if let myCreatedDate = self.createdDate, let othersCreatedDate = otherUser.createdDate {
            //This line will compare both date with the order that has been passed.
            return myCreatedDate.compare(othersCreatedDate) == order
        }
        return false
    }
}

Теперь давайте получим extension из Array для User. Проще говоря, давайте добавим некоторые методы только для тех массивов, в которых есть только объекты User.

extension Array where Element: User {
    //This method only takes an order type. i.e ComparisonResult.orderedAscending
    func sortUserByDate(_ order: ComparisonResult) -> [User] {
        let sortedArray = self.sorted { (user1, user2) -> Bool in
            return user1.checkForOrder(user2, order)
        }
        return sortedArray
    }
}

Использование в порядке возрастания

let sortedArray = someArray.sortUserByDate(.orderedAscending)

Использование по убыванию заказа

let sortedArray = someArray.sortUserByDate(.orderedAscending)

Использование для того же заказа

let sortedArray = someArray.sortUserByDate(.orderedSame)

Выше метод в extension будет доступен только в том случае, если Array имеет тип [User] || Array<User>

Ответ 24

Вы должны создать sortDescriptor, а затем вы можете отсортировать массив nsmutablearray, используя sortDescriptor, как показано ниже.

 let sortDescriptor = NSSortDescriptor(key: "birthDate", ascending: true, selector: #selector(NSString.compare(_:)))
 let array = NSMutableArray(array: self.aryExist.sortedArray(using: [sortDescriptor]))
 print(array)

Ответ 25

Используйте это для вложенных объектов,

NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastRoute.to.lastname" ascending:YES selector:@selector(caseInsensitiveCompare:)];
NSMutableArray *sortedPackages = [[NSMutableArray alloc]initWithArray:[packages sortedArrayUsingDescriptors:@[sortDescriptor]]];

lastRoute - это один объект, и этот объект содержит объект to, а объекту - строковые значения lastname.

Ответ 26

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(@"===============================");