web-dev-qa-db-fra.com

Comment trier un NSMutableArray avec des objets personnalisés?

Ce que je veux faire semble assez simple, mais je ne trouve aucune réponse sur le Web. J'ai un NSMutableArray d'objets, et disons que ce sont des objets 'Personne'. Je veux trier le NSMutableArray par Person.birthDate qui est un NSDate.

Je pense que cela a quelque chose à voir avec cette méthode:

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

Dans Java, je voudrais que mon objet implémente Comparable ou j'utilise Collections.sort avec un comparateur personnalisé en ligne ... comment pouvez-vous procéder de la sorte dans Objective-C?

1240
rustyshelf

Méthode de comparaison

Soit vous implémentez une méthode de comparaison pour votre objet:

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

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

NSSortDescriptor (meilleur)

ou généralement encore mieux:

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

Vous pouvez facilement trier plusieurs clés en ajoutant plusieurs clés à la matrice. L'utilisation de méthodes de comparaison personnalisées est également possible. Regardez la documentation .

Blocs (brillants!)

Il existe également la possibilité de trier avec un bloc depuis Mac OS X 10.6 et 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];
}];

Performance

Les méthodes -compare: et basées sur les blocs seront généralement un peu plus rapides que d'utiliser NSSortDescriptor car cette dernière repose sur KVC. Le principal avantage de la méthode NSSortDescriptor est qu’elle vous permet de définir votre ordre de tri à l’aide de données, plutôt que de code, ce qui facilite l’exécution par exemple. configurez les éléments pour que les utilisateurs puissent trier un NSTableView en cliquant sur la ligne d'en-tête.

2278
Georg Schölly

Voir la méthode NSMutableArraysortUsingFunction:context:

Vous devrez configurer une fonction compare qui prend deux objets (de type Person, puisque vous comparez deux Person objets) et un paramètre context .

Les deux objets ne sont que des occurrences de Person. Le troisième objet est une chaîne, par exemple. @"date de naissance".

Cette fonction retourne un NSComparisonResult: Elle retourne NSOrderedAscending si _PersonA.birthDate_ <_PersonB.birthDate_. Il retournera NSOrderedDescending si _PersonA.birthDate_> _PersonB.birthDate_. Enfin, il retournera NSOrderedSame si _PersonA.birthDate_ == _PersonB.birthDate_.

C'est un pseudocode approximatif; vous devrez préciser ce que cela signifie pour une date "moins", "plus" ou "égale" par rapport à une autre date (comme comparer secondes-depuis-Epoch, etc.):

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

Si vous voulez quelque chose de plus compact, vous pouvez utiliser des opérateurs ternaires:

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

L'inline pourrait peut-être accélérer un peu si vous le faites beaucoup.

107
Alex Reynolds

Je l'ai fait dans iOS 4 en utilisant un bloc. J'ai dû convertir les éléments de mon tableau d'id à mon type de classe. Dans ce cas, il s’agissait d’une classe appelée Score avec une propriété appelée points.

De plus, vous devez décider quoi faire si les éléments de votre tableau ne sont pas du bon type. Pour cet exemple, je viens de renvoyer NSOrderedSame, mais dans mon code, je pense qu'il y a une exception.

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: C'est le tri par ordre décroissant.

61
Chris

À partir de iOS 4, vous pouvez également utiliser des blocs pour le tri.

Pour cet exemple particulier, je suppose que les objets de votre tableau ont une méthode 'position', qui retourne un 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];

Remarque: le tableau "trié" sera automatiquement libéré.

28
leviathan

J'ai tout essayé, mais cela a fonctionné pour moi. Dans une classe, j'ai une autre classe nommée "crimeScene" et je souhaite trier une propriété de "crimeScene".

Ça fonctionne super bien:

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

Il manque une étape dans deuxième réponse de Georg Schölly , mais cela fonctionne bien alors.

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

Merci, ça fonctionne bien ...

18
Hardik Darji

Vos objets Person doivent implémenter une méthode, par exemple compare:, qui prend un autre objet Person, et renvoie NSComparisonResult en fonction de la relation entre les 2 objets.

Ensuite, vous appeleriez sortedArrayUsingSelector: avec @selector(compare:) et vous devriez le faire.

Il y a d'autres moyens mais, autant que je sache, il n'y a pas d'équivalent Cocoa de l'interface Comparable. Utiliser sortedArrayUsingSelector: est probablement le moyen le plus simple de le faire.

16
freespace

iOS 4 blocs vous sauvera :)

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 un peu de description

8

Pour NSMutableArray, utilisez la méthode sortUsingSelector. Il trie it-place, sans créer une nouvelle instance.

7
DenTheMan

Vous pouvez utiliser la méthode générique suivante à cette fin. Cela devrait résoudre votre problème.

//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];
6
MD Aslam Ansari

Si vous ne faites que trier un tableau de NSNumbers, vous pouvez les trier en 1 appel:

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

Cela fonctionne car les objets du tableau (objets NSNumber) implémentent la méthode de comparaison. Vous pouvez faire la même chose pour les objets NSString, ou même pour un tableau d'objets de données personnalisés qui implémentent une méthode de comparaison.

Voici un exemple de code utilisant des blocs de comparaison. Il trie un tableau de dictionnaires où chaque dictionnaire comprend un numéro dans une clé "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;
 }];

Le code ci-dessus consiste à obtenir une valeur entière pour chaque clé de tri et à la comparer, afin d'illustrer comment procéder. Puisque les objets NSNumber implémentent une méthode de comparaison, celle-ci pourrait être réécrite beaucoup plus simplement:

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

ou le corps du comparateur pourrait même être distillé jusqu'à 1 ligne:

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

J'ai tendance à préférer les instructions simples et de nombreuses variables temporaires car le code est plus facile à lire et à déboguer. Le compilateur optimise néanmoins les variables temporaires, de sorte que la version tout en un ne présente aucun avantage.

5
Dinesh_

J'ai utilisé sortUsingFunction :: dans certains de mes projets:

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];
4
roocell

J'ai créé une petite bibliothèque de méthodes de catégories, appelée Linq to ObjectiveC , qui facilite ce genre de choses. En utilisant la méthode sort avec un sélecteur à clé, vous pouvez trier par birthDate comme suit:

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

Je viens de faire un tri sur plusieurs niveaux en fonction d'exigences personnalisées.

// trie les valeurs

    [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
Boobalan
-(NSMutableArray*) sortArray:(NSMutableArray *)toBeSorted 
{
  NSArray *sortedArray;
  sortedArray = [toBeSorted sortedArrayUsingComparator:^NSComparisonResult(id a, id b) 
  {
    return [a compare:b];
 }];
 return [sortedArray mutableCopy];
}
3
Emile Khattar

Vous utilisez NSSortDescriptor pour trier un NSMutableArray avec des objets personnalisés

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

Trier NSMutableArray est très simple:

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];
2
Mohit

Les protocoles et la programmation fonctionnelle de Swift vous facilitent la tâche. Il vous suffit de rendre votre classe conforme au protocole Comparable, d'implémenter les méthodes requises par le protocole, puis d'utiliser la fonction triée (par:) d'ordre élevé pour créer un tableau trié sans avoir besoin de l'utiliser. tableaux mutables en passant.

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)
1
James Rochabrun

Tri à l'aide de NSComparator

Si nous voulons trier les objets personnalisés, nous devons fournir NSComparator, qui sert à comparer les objets personnalisés. Le bloc retourne une valeur NSComparisonResult pour indiquer l'ordre des deux objets. Donc, pour trier tout le tableau, NSComparator est utilisé de la manière suivante.

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

Trie à l'aide de NSSortDescriptor
Supposons, à titre d’exemple, que nous avons un tableau contenant les instances d’une classe personnalisée, Employé ayant les attributs prénom, nom et âge. L'exemple suivant montre comment créer un NSSortDescriptor pouvant être utilisé pour trier le contenu du tableau dans l'ordre croissant en fonction de la clé d'âge.

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

Tri à l'aide de comparaisons personnalisées
Les noms sont des chaînes et lorsque vous triez des chaînes à présenter à l'utilisateur, vous devez toujours utiliser une comparaison localisée. Souvent, vous souhaitez également effectuer une comparaison insensible à la casse. Voici un exemple avec (localizedStandardCompare :) pour classer le tableau par nom et prénom.

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

Pour référence et une discussion détaillée, veuillez vous reporter à: 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/

1
Aamir

Dans mon cas, j'utilise "" sortArrayUsingComparator "pour trier un tableau. Regardez le code ci-dessous.

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

Aussi mon objet est,

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

Utilisez comme ceci pour les objets imbriqués,

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

lastRoute est un objet et cet objet contient l'objet to, celui-ci contenant les valeurs de chaîne lastname.

Trier le tableau dans Swift


Pour Swifty Person ci-dessous est une technique très propre à atteindre l'objectif ci-dessus pour globalement. Donnons un exemple de classe personnalisée de User qui possède certains attributs.

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

Nous avons maintenant un tableau que nous devons trier sur la base de createdDate croissant ou décroissant. Ajoutons donc une fonction pour la comparaison des dates.

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

Maintenant, prenons un extension sur Array pour User. En termes simples, ajoutons quelques méthodes uniquement pour les tableaux contenant uniquement des objets 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
    }
}

tilisation pour ordre croissant

let sortedArray = someArray.sortUserByDate(.orderedAscending)

tilisation pour l'ordre décroissant

let sortedArray = someArray.sortUserByDate(.orderedAscending)

tilisation pour le même ordre

let sortedArray = someArray.sortUserByDate(.orderedSame)

La méthode ci-dessus dans extension ne sera accessible que si la Array est de type [User] || Array<User>

0
Syed Qamar Abbas

Vous devez créer sortDescriptor et ensuite vous pouvez trier le nsmutablearray en utilisant sortDescriptor comme ci-dessous.

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