Utiliser un prédicat avec un tableau d'argument

MayerickMayerick Membre
juillet 2014 modifié dans API UIKit #1

Bonjour, 

 

je suis navré si cette question peut paraitre bête mais je ne trouves pas de réponse dans la doc ni sur le net. 

 

Je cherche à  savoir si un Set (mais ça peut être un array ça m'est égal) contient un string dont la première lettre commence par celle d'autres objets présent dans un autre tableau. J'arrive à  comparer si je fourni l'objet en dur mais pas en passant par un tableau.

 

Voici le code que j'ai :



NSMutableSet *aSet = [[NSMutableSet alloc]initWithObjects:@J, @T, nil];
NSString *nomDeFleurRecherchee = @Rose_De_Sable;

NSPredicate *sPredicate = [NSPredicate predicateWithFormat:@nomDeLaFleurRepresentee beginswith[c] %@ || nomDeLaFleurRepresentee beginswith[c] %@", [[nomDeFleurRecherchee componentsSeparatedByString:@_]objectAtIndex:0], aSet]; // [aSet allKeys] ne fonctionne pas non plus.
verifSet = (NSMutableSet*)[nSet filteredSetUsingPredicate:sPredicate]; // ca plante à  cet endroit si on doit passer après le || en me disant que ce ne sont pas des strings comme attendu mais bien une classe collection.

En fait j'aimerai que pour chaque objet qui est comparé dans nSet, il le compare à  tous les objets présent dans aSet, voir même pouvoir effectuer une action sur tous les objets présent dans aSet, comme par exemple récupérer une partie du nom avec componentsSeparatedByString dans le cas où aSet contiendrait des noms entiers comme @Tulipe_Rouge. Est ce que tout cela est possible ? 


 


j'ai trouvé la méthode objectPassingTest, mais j'avoue ne pas bien la comprendre et n'arrive pas à  savoir si elle peut m'être utile, car j'ai l'impression qu'elle s'applique à  l'objet qui passe le test dans nSet et non dans aSet.


 


Quelqu'un pourrait-il me dire comment arriver à  réaliser quelque chose qui ressemblerai à  ça en pseudo code : 



NSPredicate *sPredicate = [NSPredicate predicateWithFormat:@nomDeLaFleurRepresentee beginswith[c] %@ /*la première lettre ou le premier mot de chaque objet présents */ IN %@", ???, aSet];

 

Ca m'enlèverai vraiment une épine du pied.


Merci beaucoup.


Réponses

  • AliGatorAliGator Membre, Modérateur
    Je ne suis pas sûr d'avoir tout compris, si tu pouvais donner des exemples ça serait plus parlant je pense.

    Mais d'après ce que j'ai compris de ton besoin, c'est peut-être mieux de passer par un bloc qu'un NSPredicate du coup, d'autant que si tu arrives à  écrire un NSPredicate qui fait ça (alors que tu veux à  ce que je comprend limite pouvoir faire des actions comme couper le nom en morceaux pour la comparaison, etc, ce que les NSpredicate ne peuvent pas vraiment faire de toute façon), il risque de toute façon d'être assez costaud et illisible.

    Il est préférable d'avoir un code lisible qu'un code avec un predicate alambiqué et impossible à  relire.

    Donc moi je ferais un truc du style :

    NSSet* found = [nSet objectsPassingTest:^(id obj, BOOL* stop) {
    // ici tu écris ton code qui va dire si oui ou non chaque objet obj de ton nSet
    // doit être sélectionné (return YES) ou éliminé (return NO)

    // Si ton NSSet ne contiens que des NSString, alors chaque obj est donc une NSString
    // et tu peux utiliser substringToIndex:1 pour récupérer la première lettre
    NSString* firstchar = [obj substringToIndex:1];
    // Et vérifier si cette première lettre fait partie des lettres recherchées présentes dans aSet
    return [aSet containsObject: firstChar]; // si présente, on garde l'objet, sinon on élimine
    }];
    Avec ce code, dans ton NSSet 'found' tu as tous les objet de nSet qui commencent par une des lettres présentes dans aSet. Je suis pas sûr que c'est vraiment ça que tu voulais faire car j'ai pas tout compris de tes explications mais bon si c'est pas ça je suis sûr que tu sauras adapter.
  • MayerickMayerick Membre
    juillet 2014 modifié #3

    Merci beaucoup pour cette réponse. J'avais peur en effet de ne pas être assez précis mais ta réponse à  été assez claire et en effet mieux vaut ne pas utiliser de prédicats s'ils rendent le code illisible. 


     


    En fait j'ai une première collection qui contient des customs objets, mon seuls moyens pour retrouver ces objets et les identifier est de passer par leur attribut qui est un nom souvent composé,( par exemple roseRouge possède l'attribut @Rose_Rouge comme nom).


     


    Dans une deuxième collection je n'ai que des strings qui contiennent la première partie du nom, (par exemple @Rose_Jaune se transforme en @Rose).


     


    J'aimerais donc trouver dans ma première collection tous les objets dont l'attribut nom correspond aux noms présent dans ma deuxième collection. Ainsi si j'ai roseJaune dans l'un et roseRouge dans l'autre, il doit détecter que la rose est commune au deux collection même si ce n'est pas le même objet et quelque soit la couleur ou le type.


     


    Grace à  ton code et tes explications j'ai pu comprendre la méthode objectsPassingTest et trouver comment régler mon problème, j'ai donc fais ça : 



    NSSet *found = [nSet objectsPassingTest:^BOOL(Fleur *obj, BOOL *stop) {

    // comme obj est de type custom il faut en extraire l'attribut nom :
    NSString *nomObj = [[obj.nomDeLaFleurRepresentee componentsSeparatedByString:@_]objectAtIndex:0];

    // regarde si la premiere partie du nom de l'objet analysé correspond à  la première partie d'un string précis :
    if ([nomObj isEqualToString:[[nomDeFleurRecherchee componentsSeparatedByString:@_]objectAtIndex:0]]) {

    return YES;
    }

    // regarde si nomObj est présent dans la collection contenant les noms à  vérifier :
    return [verifSet containsObject:nomObj];

    }];


    Un grand merci pour ces explications ! ! ! 


  • AliGatorAliGator Membre, Modérateur
    Super !

    Mais ne faudrait-il pas mieux que tu repenses ton modèle pour avoir deux attributs 'type' et 'couleur' sur ton entité 'Fleur' ?

    Quitte à  ce que tu fournisses un constructeur qui, à  partir du nom composé (que tu as peut-être parce que tu récupères tes données d'un WebService ou d'un fichier CSV ou autre qui ne te liste les fleurs que sous la forme "Rose_Rouge" et "Rose_Jaune"), te sépare ces 2 sous-chaà®nes (componentsSeparatedByString:@_) et te crée l'entité Fleur avec son type et sa couleur correctement ?

    Quand on met en place un modèle de données comme cela, il n'est pas toujours bon de coller un-pour-un avec la source de données qui va servir à  remplir la base (WebService, fichier CSV ou autre).
    - Le WebService peut décider un jour de changer de format (et qui sait, ne plus mettre de "_" mais des espaces ?) et il ne faudrait pas que tu aies à  changer tout ton code qui suppose que les fleurs sont décrites avec leur seul nom et ces "_" dans le cas où ça change
    - Pour manipuler tes données ensuite, naviguer dans ton modèle de données, manipuler tes fleurs, faire des NSPredicate dessus justement, etc, ça sera bien plus simple si tu manipules des objets qui ont une propriété 'type' et une propriété 'couleur' séparés, plutôt que d'avoir un nom un peu bâtard composé d'un mix des deux.

    Ainsi si tes entités Fleur avaient eu dès le départ 2 attributs bien distincts pour type et couleur, tu aurais eu beaucoup moins de mal à  faire ton NSPredicate. Rien ne t'empêche ensuite d'avoir une méthode 'fullname' qui reconstruit une sorte de nom complet de ta fleur composé du type + la couleur, et tu pourrais alors écrire un [NSPredicate predicateWithFormat:@SELF.type == %@ OR SELF.fullname IN %@", nomDeFleurRecherchee, verifSet] par exemple.
  • MayerickMayerick Membre
    juillet 2014 modifié #5
    Merci pour cette réponse. C'est effectivement une solution à  laquelle je n'avais pas pensé car il m'est possible de récupérer les deux attributs dans un nom qui comme tu le dis et je m'en rend compte maintenant est bâtard. Mais tu as parfaitement raison, si j'ajoute un attribut type à  chaque Fleur, je pourrais utiliser des prédicats pour filtrer une variété de fleur en particulier et en même temps comparer si son nom complet correspond à  celui d'une autre (même si en fait je cherchais là  aussi à  savoir si les deux fleurs étaient de la même variété (d'où la justesse de ta réponse), c'est juste que je passais par leur nom complet pour déterminer ça. Mais je compare plus tard si leurs noms sont strictement identiques, donc tout roule).


    Après la solution que tu m'as donné plus haut fonctionne très bien, mais peut-être est ce un peu moins élégant que d'ajouter un nouvel attribut ? Je pense ne pas y avoir pensé car ça me fait en quelque sorte dupliquer une information, si je peux obtenir A à  partir de B, je ne voyais pas l'intérêt d'enregistrer A en tant qu'attribut, j'essaie de faire attention à  la mémoire et je me disais qu'il valait mieux retrouver tout ça au moment voulu. Mais au niveau performance un prédicat est peut-être plus efficient que de créer de nouvelles strings à  chaque fois pour pouvoir les comparer ? Je suppose en tout cas.


    Je vais donc m'en tenir à  ta solution de créer un attribut type qui sera la première partie du nom complet. Pour moi pas de soucis que le nom ou les "_" changent en cours de route, je suis seul sur le projet, mais merci de pointer cette éventualité.
  • AliGatorAliGator Membre, Modérateur
    juillet 2014 modifié #6
    Je suis d'accord avec toi que tu dupliquer les données en base n'est pas franchement idéal.


    Mais ne peux-tu pas te passer entièrement du nom complet ?

    Si tu veux chercher toutes les fleurs qui sont exactement des "Rose_Rouge" tu peux tout à  fait faire un prédicat qui filtre toutes les fleurs dont le type est "Rose" et la couleur est "Rouge" ;-)

    Pour moi, il faut te débarrasser définitivement de ce nom complet bâtard.


    Après si tu tiens vraiment à  le garder, une autre possibilité c'est de faire une méthode qui s'appelle nom_complet et qui retourne la concaténation de type + "__" + couleur, et tu utilises cette propriété calculée nom_complet dans ton prédicat. Comme ça, tu ne stockes pas les données en double, le nom complet n'étant pas stocké mais juste recalculé à  la demande.
    (NB : Cette solution ne marchera pas avec CoreData car le prédicat est converti une clause SQL quand tu l'utilises avec une NSFetchRequest et les clauses SQL ne peuvent pas utiliser une propriété calculée via du code ObjC, mais si ton modèle n'est pas du CoreData mais des NSSet et NSArray sur lesquels tu fais des filterUsingPredicate alors pas de soucis)
  • Non malheureusement je ne peux pas me débarrasser du nom complet. Enfin à  priori. Je vais essayer d'être un peu plus clair car je sais que tu n'es pas devin et donc tu ne peux pas voir exactement ce dont j'ai besoin. En fait pour imager, j'ai un dictionnaire qui contient des vases (set) qui contiennent des tiges qui ont pour attribut le nomDeLaFleurQuilsRepresentent. Grâce à  ce nom je peux retrouver la fleur correspondante dans le set lesFleursExistantes mais je peux aussi déterminer la variété de la fleur que la tige représente. Ensuite pour chaque vase existant (je fais donc une boucle for sur tous les objets du dictionnaire triée dans un ordre), je cherche à  savoir si au moins une tige présente dans ce nouveau vase (nSet), contient comme attribut soit la variété de nomDeFleurRecherchee soit une des variété présente dans aSet.


    Je pourrais attribuer directement une Fleur aux objets Tige mais ça me semble être trop lourd pour les simple besoins que j'ai. Une tige n'a pas besoin de connaà®tre les attributs de la fleur qu'elle représente,mais simplement son nom pour pouvoir construire plus tard le nom de l'image correspondante. De plus une Fleur ne possède pour le moment qu'un attribut nom, comme je retrouve la variété à  partir du nom elle ne connaà®t pas sa variété ni sa couleur, même si ça peut paraà®tre bizarre je l'avoue.


    Je n'utilise pas coreData donc cette solution devrait fonctionner. Juste ce que j'ai du mal expliquer c'est que je possède déjà  le nom complet, ce qui me manque c'est la variété qui est composée du premier objets de nomComplet componentsSepareted.., donc je peux en effet utiliser une méthode qui calcule la variété à  partir du nom, avant de passer ça en argument dans le prédicat. C'est déjà  un peu ce que tu me proposais à  la fin de ton deuxième message il me semble.
Connectez-vous ou Inscrivez-vous pour répondre.