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


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

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

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

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


1169


источник


Ответы:


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

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

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


2209



См. 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;
}

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


103



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


58



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

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


28



I tried all, but this worked for me. In a class I have another class named "crimeScene", and want to sort by a property of "crimeScene".

This works like a charm:

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

24



There is a missing step in Georg Schölly's second answer, but it works fine then.

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

19



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

Thanks, it's working fine...


18



Your Person objects need to implement a method, say compare: which takes another Person object, and return NSComparisonResult according to the relationship between the 2 objects.

Then you would call sortedArrayUsingSelector: with @selector(compare:) and it should be done.

There are other ways, but as far as I know there is no Cocoa-equiv of the Comparable interface. Using sortedArrayUsingSelector: is probably the most painless way to do it.


16



iOS 4 blocks will save you :)

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 a bit of description


8



For NSMutableArray, use the sortUsingSelector method. It sorts it-place, without creating a new instance.


7



If you're just sorting an array of NSNumbers, you can sort them with 1 call:

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

That works because the objects in the array (NSNumber objects) implement the compare method. You could do the same thing for NSString objects, or even for an array of custom data objects that implement a compare method.

Here's some example code using comparator blocks. It sorts an array of dictionaries where each dictionary includes a number in a key "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;
 }];

The code above goes through the work of getting an integer value for each sort key and comparing them, as an illustration of how to do it. Since NSNumber objects implement a compare method, it could be rewritten much more simply:

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

or the body of the comparator could even be distilled down to 1 line:

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

I tend to prefer simple statements and lots of temporary variables because the code is easier to read, and easier to debug. The compiler optimizes away the temporary variables anyway, so there is no advantage to the all-in-one-line version.


5



You can use the following generic method for your purpose. It should solve your issue.

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

5