Как мне отсортировать NSMutableArray с пользовательскими объектами в нем?
То, что я хочу сделать, кажется довольно простым, но я не могу найти ответы в Интернете. у меня есть NSMutableArray
объектов, и, скажем, они являются объектами "Персона". Я хочу отсортировать NSMutableArray
по Person.birthDate который является NSDate
,
Я думаю, что это как-то связано с этим методом:
NSArray *sortedArray = [drinkDetails sortedArrayUsingSelector:@selector(???)];
В Java я хотел бы, чтобы мой объект реализовывал Comparable, или использовал Collections.sort со встроенным пользовательским компаратором... как же это сделать в Objective-C?
25 ответов
Сравнить метод
Либо вы реализуете метод сравнения для вашего объекта:
- (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
нажав на строку заголовка.
Увидеть NSMutableArray
метод sortUsingFunction:context:
Вам нужно настроить функцию сравнения, которая принимает два объекта (типа Person
, так как вы сравниваете два Person
объекты) и параметр контекста.
Два объекта являются лишь примерами Person
, Третий объект - это строка, например @ "birthDate".
Эта функция возвращает 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;
}
Встраивание может, возможно, немного ускорить это, если вы делаете это много.
Я сделал это в iOS 4, используя блок. Пришлось привести элементы моего массива из id к типу моего класса. В данном случае это был класс Score с свойством points.
Также вам нужно решить, что делать, если элементы вашего массива имеют неправильный тип, для этого примера я только что вернулся 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: это сортировка по убыванию.
Я перепробовал все, но это сработало для меня. В классе у меня есть другой класс с именем "crimeScene
"и хотите отсортировать по свойству"crimeScene
".
Это работает как шарм:
NSSortDescriptor *sorter = [[NSSortDescriptor alloc] initWithKey:@"crimeScene.distance" ascending:YES];
[self.arrAnnotations sortUsingDescriptors:[NSArray arrayWithObject:sorter]];
Начиная с 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];
Примечание: отсортированный массив будет автоматически освобожден.
Во втором ответе Георга Шолли пропущен шаг, но тогда он работает нормально.
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"birthDate"
ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingDescriptor:sortDescriptors];
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"birthDate" ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [drinkDetails sortedArrayUsingDescriptors:sortDescriptors];
Спасибо работает нормально...
Ваш Person
объекты должны реализовать метод, скажем, compare:
который занимает другое Person
возражать и возвращать NSComparisonResult
в соответствии с отношениями между 2 объектами.
Тогда вы бы позвонили sortedArrayUsingSelector:
с @selector(compare:)
и это должно быть сделано.
Есть и другие способы, но, насколько я знаю, нет какао-эквивалента Comparable
интерфейс. С помощью sortedArrayUsingSelector:
это, наверное, самый безболезненный способ сделать это.
Блоки 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 немного описания
За NSMutableArray
, использовать sortUsingSelector
метод. Это сортирует это место, не создавая новый экземпляр.
Вы можете использовать следующий общий метод для ваших целей. Это должно решить вашу проблему.
//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];
Если вы просто сортируете массив NSNumbers
Вы можете отсортировать их одним вызовом:
[arrayToSort sortUsingSelector: @selector(compare:)];
Это работает, потому что объекты в массиве (NSNumber
объекты) реализовать метод сравнения. Вы могли бы сделать то же самое для 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]];
Я предпочитаю простые операторы и множество временных переменных, потому что код легче читать и легче отлаживать. В любом случае компилятор оптимизирует временные переменные, поэтому нет преимущества перед версией "все в одной строке".
Вы используете NSSortDescriptor для сортировки NSMutableArray с пользовательскими объектами
NSSortDescriptor *sortingDescriptor;
sortingDescriptor = [[NSSortDescriptor alloc] initWithKey:@"birthDate"
ascending:YES];
NSArray *sortArray = [drinkDetails sortedArrayUsingDescriptors:@[sortDescriptor]];
Я использовал 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];
Я только что сделал многоуровневую сортировку на основе пользовательских требований.
// сортируем значения
[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
Я создал небольшую библиотеку методов категорий, названную Linq to ObjectiveC, которая делает подобные вещи более простыми. Используя метод сортировки с ключевым селектором, вы можете сортировать по birthDate
следующее:
NSArray* sortedByBirthDate = [input sort:^id(id person) {
return [person birthDate];
}]
-(NSMutableArray*) sortArray:(NSMutableArray *)toBeSorted
{
NSArray *sortedArray;
sortedArray = [toBeSorted sortedArrayUsingComparator:^NSComparisonResult(id a, id b)
{
return [a compare:b];
}];
return [sortedArray mutableCopy];
}
Сортировка 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];
Сортировка с использованием NSComparator
Если мы хотим отсортировать пользовательские объекты, мы должны предоставить NSComparator
, который используется для сравнения пользовательских объектов. Блок возвращает NSComparisonResult
значение для обозначения порядка двух объектов. Таким образом, чтобы отсортировать весь массив NSComparator
используется следующим образом.
NSArray *sortedArray = [employeesArray sortedArrayUsingComparator:^NSComparisonResult(Employee *e1, Employee *e2){
return [e1.firstname compare:e2.firstname];
}];
Сортировка с использованием NSSortDescriptor
Предположим, в качестве примера, что у нас есть массив, содержащий экземпляры пользовательского класса, у Employee есть атрибуты имя, фамилия и возраст. В следующем примере показано, как создать 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/
Протоколы и функциональное программирование Swift делают это очень простым: нужно просто привести свой класс в соответствие с протоколом Comparable, реализовать методы, требуемые протоколом, а затем использовать функцию высокого порядка сортировки (by:) для создания отсортированного массива без необходимости использования изменяемые массивы кстати.
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)
В моем случае я использую "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
Вы должны создать 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)
Быстрая версия: 5.1
Если у вас есть настраиваемая структура или класс и вы хотите отсортировать их произвольно, вам следует вызвать sort(), используя завершающее замыкание, которое сортирует по указанному вами полю. Вот пример использования массива настраиваемых структур, которые сортируются по определенному свойству:
struct User {
var firstName: String
}
var users = [
User(firstName: "Jemima"),
User(firstName: "Peter"),
User(firstName: "David"),
User(firstName: "Kelly"),
User(firstName: "Isabella")
]
users.sort {
$0.firstName < $1.firstName
}
Если вы хотите вернуть отсортированный массив, а не отсортировать его на месте, используйте sorted() следующим образом:
let sortedUsers = users.sorted {
$0.firstName < $1.firstName
}
Сортировать массив в 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>
let sortedUsers = users.sorted {
$0.firstName < $1.firstName
}
Используйте как это для вложенных объектов,
NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastRoute.to.lastname" ascending:YES selector:@selector(caseInsensitiveCompare:)];
NSMutableArray *sortedPackages = [[NSMutableArray alloc]initWithArray:[packages sortedArrayUsingDescriptors:@[sortDescriptor]]];
lastRoute - это один объект, и этот объект содержит объект t o, этот объект содержит строковые значения lastname.
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"_strPrice"
ascending:sortFlag selector:@selector(localizedStandardCompare:)] ;
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(@"===============================");