[Résolu] Supprimer des objets en double dans un NSArray

septembre 2014 modifié dans API UIKit #1

Bonsoir à  tous,


 


Voilà , j'ai un tableau que j'alimente d'objets. Dans le tas, certains sont en double et je cherche un moyen de les supprimer. Je ne peux pas le faire au moment de l'ajout car ce sont des processus asynchrone.


 


Je pense donc faire ça ensuite, une fois le tableau peuplé.


 


Il faut donc que je test en comparant les objets et quand ils sont identiques, supprime le double.


 


Pour ce faire, je ne vois que prendre le premier objet et parcourir le tableau, puis le deuxième et parcourir le tableau et ainsi de suite.


 


 


Suis-je à  côté de la plaque ? Ou pas ? Et si oui, quel est le moyen le plus simple/rapide sans trop consommer de ressources.


 


Merci.


 


«1

Réponses


  • NSSet* monSet = [NSSet setWithArray:monTableau];

    Mais se serait sans doute plus simple d'ajouter tes objets dans un NSMutableSet plutôt que dans un NSMutableArray, les objets égaux à  un objet déjà  dans le Set ne sont pas ajoutés.


  • Am_MeAm_Me Membre
    septembre 2014 modifié #3

    Ce que jpimbert a oublié de te signaler enfin c'était implicite mais il faut que tu définisses la méthode : 



    -(BOOL)isEqual:(id)object
    {
    if(object==nil)
    return NO;

    //Attention faudrait vérifier si tu veux que ça soit propre que ces 2 objets sont de
    // la même instance/classe : Que tu ne compare pas une Banane avec une Pomme par exemple
    //=> sinon le cast (MonObjet*) va te crasher dessus
    MonObjet *second = (MonObjet*)object;
    if(![[self myID] isEqualToString:[second myID]])
    return NO;

    return YES;
    }

    C'est la méthode appelé par l'objet Set pour définir qu'est-ce qu'un objet égale.


    Si tu utilise juste la ligne de @jpimbert il va appeler la méthodes isEqual de NSObject et donc va ne vérifier qu'une chose : si les 2 objets ont la même adresse mémoire (correspondant à  mon premier if dans le code du dessus)


     


    Bref mais l'intention de jpimbert était bien là . Après à  toi de lire la doc sur NSSet


     


    je t'ai donné un exemple à  toi de le mettre à  ta sauce


  • AliGatorAliGator Membre, Modérateur
    septembre 2014 modifié #4
    En effet si tes objets sont des sous-classes perso de NSObject, il faut implémenter isEqual: pour lui expliquer comment faire la comparaison.


    Mais quand on implemente isEqual: il fait toujours implémenter aussi la méthode hash, c'est important pour quand t'es objets sont ensuite mis dans des conteneurs comme NSDictionary ou NSSet car la première phase de la comparaison de 2 objets passe par cette méthode pour accélérer les comparaisons.


    Attention à  respecter les propriétés d'un bon hash quand tu implémentes la méthode hash du coup :

    - deux objets identiques doivent avoir la même valeur de hash

    - deux objets qui ont des valeurs de bash différentes sont forcément des objets différents

    - par contre deux objets qui ont le même hash sont pas forcément égaux. Ils ont juste une bonne probabilité de l'être ; et dans ce cas isEqual est appelé pour faire le test final


    Le plus simple pour démonter une telle méthode est de combiner les hash des propriétés de ton objet, par exemple avec l'opérateur XOR. Par exemple si ton objet est défini par 2 propriétés "firstName" et "lastName" de type NSString, alors tu peux simplement implémenter la méthode hash pour qu'elle return self.firstName.hash ^ self.lastName.hash;. Et du coup implémenter -(BOOL)isEqual:(TonObjet*)other pour qu'il return [self.firstName isEqual:other.firstName] && [self.lastName isEqual:other.lastName];.


    Si ce sont des classes de Cocoa comme NSString ou NSDictionnary ou du genre ils ont déjà  leur propre implémentation de isEqual: et de hash donc c'est bon.


    Une fois que tu auras fait ça, mettre tes objets dans un NS(Mutable)Set va se charger de tout pour toi, en évitant d'ajouter les doublons.
  • Am_MeAm_Me Membre
    septembre 2014 modifié #5

    Pour le hash : si ça classe défini un objet avec un identifiant : par exemple identifiant récupéré sur un JSON, XML, Base de données : et qu'il veut mettre à  jour son objet (suite à  un update) :


     


    Exemple : Voiture id = 42 / nom = Renault Espace / Kilométrage = 100 000 / Vendu = NON


     


    Et que lors d'un refresh il souhaite mettre à  jour les données dans 2 jours par exemple à  Voiture id = 42 / nom = Renault Espace / Kilométrage = 100 010 / Vendu = OUI


     


    Bah 



     par contre deux objets qui ont le même hash sont pas forcément égaux. Ils ont juste une bonne probabilité de l'être ; et dans ce cas isEqual est appelé pour faire le test final



     


    Ce que tu dis n'est pas faux mais là  dans ce cas il a intérêt à  dire qu'il sont égaux par identifiant et faire une maj de son objet !


  • HerveHerve Membre
    septembre 2014 modifié #6

    Perso, j'aurais tendance, dans la méthode "isEqual" à  comparer un par un chacun des membres de ton objet. Pour les flottants, j'aurais même tendance à  utiliser une inégalité (du genre :



    /*valeur absolue de floatA - floatB inférieure à  une très petite valeur*/
    if (abs(floatA - floatB) < 0.00001) 

    pour le cas où elle serait le fruit d'un calcul. J'ai souvent eu des problèmes avec "isEqual".


     


    Sinon, je en vois pas bien comment éviter de comparer les membres un par un, mais si il y a beaucoup d'objets, cela risque d'être lent, il faudrait voir si tu peux optimiser : au moment de la création des tableaux, n'y a t-il pas moyen de savoir si un objet mis dans le premier tableau risque d'être le même qu'un objet mis parallèlement dans le second? Alors, un troisième tableau stockerait des infos sur l'objet (sa place dans le tableau par exemple) qui serait utilisé par une méthode ensuite. (???)


  • Bah justement ça dépend de la définition que tu donne de objet = un autre.


     


    Si on prend un exemple classique : 


     


    Que défini un étudiant = autre : on compare un indentifiant (donc un argument et pas nom prénom car ça ne défini pas l'unicité)


    Mais si tu veux définir ce qu'est un objet CoupleEntier : exemple (1,2) = (3,4) tu es obligé de comparer chaque membre 


    entier1.gauche = entier2.gauche


    entier1.droite = entier.droite 


     


    etc ...




  • Ce que jpimbert a oublié de te signaler enfin c'était implicite mais il faut que tu définisses la méthode : 



    -(BOOL)isEqual:(id)object
    {
    if(object==nil)
    return NO;

    //Attention faudrait vérifier si tu veux que ça soit propre que ces 2 objets sont de
    // la même instance/classe : Que tu ne compare pas une Banane avec une Pomme par exemple
    //=> sinon le cast (MonObjet*) va te crasher dessus
    MonObjet *second = (MonObjet*)object;
    if(![[self myID] isEqualToString:[second myID]])
    return NO;

    return YES;
    }

    C'est la méthode appelé par l'objet Set pour définir qu'est-ce qu'un objet égale.


    Si tu utilise juste la ligne de @jpimbert il va appeler la méthodes isEqual de NSObject et donc va ne vérifier qu'une chose : si les 2 objets ont la même adresse mémoire (correspondant à  mon premier if dans le code du dessus)


     


    Bref mais l'intention de jpimbert était bien là . Après à  toi de lire la doc sur NSSet


     


    je t'ai donné un exemple à  toi de le mettre à  ta sauce




     


    Merci pour vos réponses. Alors déjà  quelques détails supplémentaires :


     


    - Ma classe hérite en effet de NSObject mais tous mes attributs sont des NSString et un NSDictionary.


    - Dans ma classe, la seule chose dont j'ai a me préoccuper et le ID qui est un NSString.


     


    Par contre, je ne vois pas trop, dois-je tout ajouter a un NSArray que je "transvase" dans un NSSet ?

  • Bah l'idéal c'est que tu ai un id et avec -isEqual (ou -hash pas obligatoire mais d'après ce qu'a signalé Ali c'est important car avant de faire un isEqual il va demander au hash de lui retourner quelques chose) tu puisse comparer directement dessus.


     


     




     


     


    - Ma classe hérite en effet de NSObject mais tous mes attributs sont des NSString et un NSDictionary.


    - Dans ma classe, la seule chose dont j'ai a me préoccuper et le ID qui est un NSString.


     


    Par contre, je ne vois pas trop, dois-je tout ajouter a un NSArray que je "transvase" dans un NSSet ?




     


     


    Je ne vois pas de problème. L'idéal est que tu utilise NSSet directement dans ce cas et que tu oublis NSArray.


    Sauf si après dans ton application tu as besoin que ton NSSet soit une array tu appel la méthode pour convertir




  •  


    - Dans ma classe, la seule chose dont j'ai a me préoccuper et le ID qui est un NSString.




    Dans ce cas, la méthode de la classe NSString ("[myString isEqualToString:aString] ")  fonctionnera très bien. Combien d'entrées veux-tu traiter?


     


    Si il y en a peu, une double boucle "for" ferait l'affaire, mais au delà  d'un certain nombre, cela va poser problème... 



  • Dans ce cas, la méthode de la classe NSString ("[myString isEqualToString:aString] ")  fonctionnera très bien. Combien d'entrées veux-tu traiter?


     


    Si il y en a peu, une double boucle "for" ferait l'affaire, mais au delà  d'un certain nombre, cela va poser problème... 




     


    Dans un futur proche s'il accroit son nombre de données à  traiter il devra retoucher au code pour enlever la double for(). Du coup vaut mieux NSSet

  • Oui j'ai pas la main sur le nombre d'élément.


     


    La méthode Equal, on est d'accord que je dois l'overrider dans ma classe ?


  • Am_Me, concernant ton commentaire :


     



     


    //Attention faudrait vérifier si tu veux que ça soit propre que ces 2 objets sont de


        // la même instance/classe : Que tu ne compare pas une Banane avec une Pomme par exemple


        //=> sinon le cast (MonObjet*) va te crasher dessus



     


    Ce sont forcement les mêmes objets car ils sont extrais du JSON et j'init la même classe pour tout car l'archi du JSON est identique pour chaque objet.


  • ouep tu l'override (tu peux créer ton propre isEqualAmoi mais ca n'aurai pas trop de sens en objet):


    de toute façon du tape sur le clavier - (id)is .. et lui te complete après je t'ai donné un modèle là  haut


     


    Ensuite pour le 2nd commentaire oui je suis d'accord c'est pour ça que je ne le fais pas non plus :) mais juste pour te dire que si tu veux être pointilleux tu peux rajouter cette condition 

  • Par contre, du coup j'ai toujours un NSArray et je dois initialisé un NSSet quand le NSArray est rempli de tous ces objets ? C'est bien ça ?


  • Bah comme je t'ai dit ton NSArray là  tu peux directement le changer en un NSSet. 


    Mais si tu ne veux pas oui c'est ça dès que ton NSArray est rempli : tu le converti en NSSet en appelant une méthode dont je n'ai plus le nom ça doit être un truc du genre : setWithArray : et lui s'occupera d'insérer les élément un par un et si c'est un doublon il ne l'insérera pas 


  • septembre 2014 modifié #17

    Oui c'est bien cette méthode (voir message de jpimbert).


     


    Je vais te poser la question autrement, je me suis mal exprimé. J'ai une boucle que tu verras dans mon autre post d'ailleurs. Dans cette boucle, je récupère des données que j'envoie dans un NSArray. Je veux bien le remplacer par un NSSet mais à  ce moment, il fera lui-même le test isEqual ???


     


    (c'est un peu compliquer à  tester là  vu que mon UITableView est vide donc je pose la question)


  • Je vais faire peut-être mon rabajoie mais la doc est là  pour ça NSSet


     


  • Alors, comme ça ne fonctionne pas, voici mon code de l'override de isEqual :



    - (BOOL)isEqual:(id)object
    {
    BOOL thisReturn = YES;

    if (object == nil)
    {
    thisReturn = NO;
    }
    else
    {
    monObject *copyItem = (monObject *)object;

    if ([self.id isEqualToString:[copyItem id]])
    {
    thisReturn = YES;
    }
    }

    return thisReturn;
    }

    Comme ça ne fonctionne pas, je me suis dis que j'avais peut-être fais une bêtise en changeant 2-3 trucs du code que vous m'avez passé, donc j'ai remis ceci :



    -(BOOL)isEqual:(id)object
    {
    if(object==nil)
    return NO;

    //Attention faudrait vérifier si tu veux que ça soit propre que ces 2 objets sont de
    // la même instance/classe : Que tu ne compare pas une Banane avec une Pomme par exemple
    //=> sinon le cast (MonObjet*) va te crasher dessus
    monObject *second = (monObject*)object;
    if(![self.id isEqualToString:[second id]])
    return NO;

    return YES;
    }


    Pour un résultat identique, c'est à  dire que je me retrouve toujours avec des doublons et apparemment un nombre aléatoire entre chaque lancement de l'App.


     


    Le JSON est toujours le même, donc toujours les mêmes objets, les seuls éléments qui peuvent être différent sont le id (de type NSString) de chaque objet, c'est pour ça que je ne teste que ça.


     


    Sinon, dans le code de la UITableViewController, j'ai ceci :



    NSSet *test = [[NSSet alloc] initWithArray:myArray];
    self.myArray = (NSMutableArray *)test.allObjects;

    Voilà , en espérant n'avoir oublié aucun détails.


  • zoczoc Membre
    septembre 2014 modifié #20

    -(BOOL)isEqual:(id)object
    {
    ...
    if(![self.id isEqualToString:[second id]])
    return NO;
    ...
    }

    T'es sur que ton objet a une méthode "id"... Je crois que ce que tu voulais écrire c'est second.id, pas [second id]...
  • Am_MeAm_Me Membre
    septembre 2014 modifié #21


    T'es sur que ton objet a une méthode "id"... Je crois que ce que tu voulais écrire c'est second.id, pas [second id]...




     


    En fait c'est moi qui lui ai passé cette exemple oui en fait c'est plutôt [second getID] ou second.id !! 


    Erreur de frappe quand j'ai voulu donner l'exemple


     


    Non je n'ai rien dit j'avais écris ça précédemment : 



    if(![[self myID] isEqualToString:[second myID]])

    myID est une méthode qui retourne l'id mais tu peux passer directement par self.id isEqualToString second.myID


  • septembre 2014 modifié #22

    Alors je viens de faire un test, que ce soit ça :



    if ([self.monId isEqualToString:copyItem.monId])
    {
    thisReturn = YES;
    }

    ou :



    if ([self.monId isEqualToString:[copyItem monId]])
    {
    thisReturn = YES;
    }

    Les deux sont possibles mais le résultat est identique (toujours des doubles). A moins que j'ai rien compris à  vos messages ?


  • Am_MeAm_Me Membre
    septembre 2014 modifié #23

    Tu pourrais faire un printf avant de quitter la méthode equals et dire 



    NSLog(@J'ai trouve que self.id =%@ et que copyItem.id = %@ sont égaux = %@",self.monId,copyItem.monId,thirReturn);


    Et poster le résultat 


  • Bon alors, c'est un peu la fête au Moulin Rouge. Déjà  je suis reparti sur ton code, histoire d'être un peu plus sur, voici :



    -(BOOL)isEqual:(id)object
    {
    if(object==nil)
    return NO;

    //Attention faudrait vérifier si tu veux que ça soit propre que ces 2 objets sont de
    // la même instance/classe : Que tu ne compare pas une Banane avec une Pomme par exemple
    //=> sinon le cast (MonObjet*) va te crasher dessus
    monItem *second = (monItem*)object;
    if(![self.monId isEqualToString:[second monId]]) {
    NSLog(@J'ai trouve que self.id = %@ et que copyItem.id = %@ sont égaux.", self.monId, [second monId]);
    return NO;
    }


    return YES;
    }

    Voici un résultat :


     



     


     



     


    Oui, oui, c'est un résultat :)


     


    Un autre :


     



     


    J'ai trouve que self.id = 6834 et que copyItem.id = 6865 sont égaux.


    J'ai trouve que self.id = 6865 et que copyItem.id = 6877 sont égaux.


     


    Et un dernier pour la route :


     



     


    J'ai trouve que self.id = 6883 et que copyItem.id = 6900 sont égaux.


    J'ai trouve que self.id = 6911 et que copyItem.id = 6863 sont égaux.

    J'ai trouve que self.id = 6861 et que copyItem.id = 6867 sont égaux.

    J'ai trouve que self.id = 6875 et que copyItem.id = 6864 sont égaux.

    J'ai trouve que self.id = 6868 et que copyItem.id = 6880 sont égaux.

    J'ai trouve que self.id = 6911 et que copyItem.id = 6877 sont égaux.

    J'ai trouve que self.id = 6863 et que copyItem.id = 6877 sont égaux.

    J'ai trouve que self.id = 6873 et que copyItem.id = 6877 sont égaux.

    J'ai trouve que self.id = 6877 et que copyItem.id = 6906 sont égaux.

  • Am_MeAm_Me Membre
    septembre 2014 modifié #25

    Alors montre moi comment est déclaré ton id déjà 


    Ensuite utilise tu isEqual ou iEqualToString ?


     


    Fait attention NSString *id ne PEUT EXISTER !!!!


     


    id correspond à  un objet comme dans - (id) init 


     


    Alors déclare ton id sous la forme : NSString *identification;


    Ca t'évitera les problèmes. Car la je crois qu'il compare autre chose que les string mais plutôt leur classe ...


  • septembre 2014 modifié #26

    Non j'ai changé le nom c'est is_unTruc, pas juste id justement pour en pas poser ce genre de problème.


     


    Voici sa déclaration :



    @property (strong, nonatomic) NSString *idMonTruc;

    isEqualToString: pour répondre à  ta question.


     


    EDIT : dans mon fichier m j'ai ceci :



    @synthesize idMonTruc;

  • septembre 2014 modifié #27

    Je viens de faire ça :



    NSLog(@AVANT : %@, %@", self.idMonObjet, [second idMonObjet]);

    Avant le if qui compare les chaines de caractère et retour : 0, rien, nada.


     


    Sur un autre essai, sans rien changé, juste arrêt du simulateur et relance d'un autre test, il m'affiche des résultats où là , j'ai les deux :


     



     


    AVANT : 6859, 6863


    J'ai trouve que self.id = 6859 et que copyItem.id = 6863 sont égaux.

    AVANT : 6911, 6867

    J'ai trouve que self.id = 6911 et que copyItem.id = 6867 sont égaux.

    AVANT : 6834, 6880

    J'ai trouve que self.id = 6834 et que copyItem.id = 6880 sont égaux.

    AVANT : 6884, 6880

    J'ai trouve que self.id = 6884 et que copyItem.id = 6880 sont égaux.

    AVANT : 6877, 6880

    J'ai trouve que self.id = 6877 et que copyItem.id = 6880 sont égaux.

    AVANT : 6883, 6881

    J'ai trouve que self.id = 6883 et que copyItem.id = 6881 sont égaux.

    AVANT : 6859, 6881

    J'ai trouve que self.id = 6859 et que copyItem.id = 6881 sont égaux.

    AVANT : 6863, 6881

    J'ai trouve que self.id = 6863 et que copyItem.id = 6881 sont égaux.

    AVANT : 6850, 6877

    J'ai trouve que self.id = 6850 et que copyItem.id = 6877 sont égaux.

    AVANT : 6911, 6877

    J'ai trouve que self.id = 6911 et que copyItem.id = 6877 sont égaux.

    AVANT : 6867, 6877

    J'ai trouve que self.id = 6867 et que copyItem.id = 6877 sont égaux.

    AVANT : 6873, 6899

    J'ai trouve que self.id = 6873 et que copyItem.id = 6899 sont égaux.

    AVANT : 6835, 6898

    J'ai trouve que self.id = 6835 et que copyItem.id = 6898 sont égaux.

    AVANT : 6900, 6877

    J'ai trouve que self.id = 6900 et que copyItem.id = 6877 sont égaux.


  • septembre 2014 modifié #28

    Non mais je viens de comprendre... Je viens d'obtenir ça :


     



     


    AVANT : 6911, 6911



     


    Donc, il récupère les données de manières aléatoire because c'est du asynchrone. Le problème, et c'est ce que j'essayais de vous expliquer plus haut, je suis dans une boucle où j'ajoutes mes objets à  un tableau.


     


    Mais quand un nouvel objet arrive, il faut parcourir le tableau pour voir si y a pas déjà  un double. Avec la méthode actuelle, on ne le fais pas, donc c'est au bonheur la chance.


     


    Par contre, aller parcourir dans ce tableau maintenant je pense pas que ce soit une bonne idée.


     


    Qu'en pensez-vous ? (et est-ce que je suis assez clair ? :) )


  • Parcourir le tableau à  chaque fois est hyper couteux non ?


  • Tu peux faire : containsObject avant d'insérer (par contre comme dit plus haut le isEqual doit être implémenté)


     


     


    The containsObject method will invoke isEqual: on the underlying objects being compared. 


    Unless you implement isEqual: in the SUPDataValueList object, it will simply do a pointer comparison which is the default behavior of isEqual in NSObject.


     


    Ou -[NSArray indexOfObjectIdenticalTo:]


  • Beh oui, je pense aussi. Mais comment faire alors quand il est plein ?


Connectez-vous ou Inscrivez-vous pour répondre.