ios - Comment trier un NSMutableArray avec des objets personnalisés?




objective-c sorting cocoa-touch (21)

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 qu'il s'agit d'objets 'Person'. Je veux trier 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(???)];

En Java, je rendrais mon objet comparable à Compare, ou utiliser Collections.sort avec un comparateur personnalisé en ligne ... comment diable faites-vous cela en Objective-C?


Answers

Trier à l'aide de NSComparator

Si nous voulons trier des objets personnalisés, nous devons fournir NSComparator , qui est utilisé pour comparer des objets personnalisés. Le bloc renvoie une valeur NSComparisonResult pour indiquer l'ordre des deux objets. Donc, afin de trier le tableau entier, 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 ayons un tableau contenant des instances d'une classe personnalisée, Employee a les attributs firstname, lastname et age. L'exemple suivant illustre comment créer un NSSortDescriptor qui peut être utilisé pour trier le contenu du tableau dans l'ordre croissant par la clé d'âge.

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

Trier à l'aide de comparaisons personnalisées
Les noms sont des chaînes et lorsque vous triez les 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 ordonner le tableau par le prénom et le 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 discussion détaillée s'il vous plaît se référer: 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/


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

Les protocoles et la programmation fonctionnelle de Swift rendent très facile le fait de rendre votre classe conforme au protocole Comparable, implémenter les méthodes requises par le protocole, puis utiliser la fonction triée (by:) pour créer un tableau trié sans avoir besoin d'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)

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

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

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

Je viens de faire un tri à plusieurs niveaux en fonction des besoins personnalisés.

// trier 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


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)

Pour NSMutableArray , utilisez la méthode sortUsingSelector . Il trie le lieu, sans créer une nouvelle instance.


Il y a une étape manquante dans la deuxième réponse de Georg Schölly , mais cela fonctionne bien.

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

Le tri de 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];

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

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

Cela fonctionne parce que les objets dans le 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 comparateurs. Il trie un tableau de dictionnaires où chaque dictionnaire inclut un nombre 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 passe par le travail d'obtenir une valeur entière pour chaque clé de tri et de les comparer, comme une illustration de la façon de le faire. Comme les objets NSNumber implémentent une méthode de comparaison, il est possible de la réécrire 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 beaucoup de variables temporaires car le code est plus facile à lire et plus facile à déboguer. Le compilateur optimise de toute façon les variables temporaires, donc il n'y a aucun avantage à la version tout-en-un-ligne.


Vos objets Person ont besoin d'implémenter une méthode, disons compare: qui prend un autre objet Person , et renvoient NSComparisonResult fonction de la relation entre les 2 objets.

Ensuite, vous appelez sortedArrayUsingSelector: avec @selector(compare:) et cela devrait être fait.

Il y a d'autres façons, mais pour 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.


Vous pouvez trier un tableau d'objets personnalisés à l'aide du tri rapide. Veuillez trouver ci-dessous un exemple utilisant Swift 4

func sortArrayOfPersonInAscendingOrder() {
        if self.arrSortingPersons != nil, self.arrSortingPersons.count > 0 {
            self.arrSortingPersons.sort(by: { (person1, person2) -> Bool in
                return person1.birthDate < person2.birthDate
            })
        }

        print("Sorted Array In Ascending Order:\n\(self.arrSortingPersons)")
    }

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

Ça fonctionne super bien:

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

Vous pouvez utiliser la méthode générique suivante pour votre objectif. 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];

Voir la méthode NSMutableArray sortUsingFunction:context:

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

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

Cette fonction renvoie un NSComparisonResult : Il renvoie NSOrderedAscending si PersonA.birthDate < PersonB.birthDate . Il renverra NSOrderedDescending si PersonA.birthDate > PersonB.birthDate . Enfin, il retournera NSOrderedSame si PersonA.birthDate == PersonB.birthDate .

C'est un pseudocode rugueux; vous aurez besoin de préciser ce que cela signifie pour une date d'être "moins", "plus" ou "égal" à une autre date (comme la comparaison des secondes depuis l'époque, 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;
}

Inlining pourrait peut-être accélérer un peu, si vous le faites beaucoup.


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


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

Je l'ai fait dans iOS 4 en utilisant un bloc. J'ai dû lancer les éléments de ma matrice de l'id à mon type de classe. Dans ce cas, c'était une classe appelée Score avec une propriété appelée points.

Aussi, 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 retourner NSOrderedSame , mais dans mon code, je pense à 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.


Comparer la méthode

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 (mieux)

ou habituellement encore mieux:

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

Vous pouvez facilement trier par plusieurs clés en ajoutant plus d'un au tableau. L'utilisation de méthodes de comparaison personnalisées est également possible. Jetez un oeil à la documentation .

Blocs (brillants!)

Il y a aussi 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 -compare: et block-based seront en général un peu plus rapides que l'utilisation de NSSortDescriptor car ce dernier repose sur KVC. Le principal avantage de la méthode NSSortDescriptor est qu'elle permet de définir votre ordre de tri à l'aide de données, plutôt que de code, ce qui facilite la configuration des éléments afin que les utilisateurs puissent trier un NSTableView en cliquant sur la ligne d'en-tête.


Créez un comparateur personnalisé et utilisez-le lors de la création d'un nouvel objet TreeMap.

class MyComparator implements Comparator<Object> {

    Map<String, Integer> map;

    public MyComparator(Map<String, Integer> map) {
        this.map = map;
    }

    public int compare(Object o1, Object o2) {

        if (map.get(o2) == map.get(o1))
            return 1;
        else
            return ((Integer) map.get(o2)).compareTo((Integer)     
                                                            map.get(o1));

    }
}

Utilisez le code ci-dessous dans votre fonction principale

    Map<String, Integer> lMap = new HashMap<String, Integer>();
    lMap.put("A", 35);
    lMap.put("B", 75);
    lMap.put("C", 50);
    lMap.put("D", 50);

    MyComparator comparator = new MyComparator(lMap);

    Map<String, Integer> newMap = new TreeMap<String, Integer>(comparator);
    newMap.putAll(lMap);
    System.out.println(newMap);

Sortie:

{B=75, D=50, C=50, A=35}




ios objective-c sorting cocoa-touch nsmutablearray