NSPredicate avec NSArray dans NSArray

BenjoBenjo Membre
mai 2015 modifié dans API UIKit #1

Bonjour à  tous,


 


Dans mon projet, j'ai un NSArray rempli lui même de NSArray :



NSArray *colorArray = @[;[UIColor blueColor], [UIColor redColor], [UIColor orangeColor], [UIColor purpleColor]];
NSArray *colorArray2 = @[;[UIColor redColor], [UIColor greenColor], [UIColor orangeColor], [UIColor purpleColor]];
NSArray *totalArray = @[;colorArray, colorArray2];

J'aimerais pouvoir faire une rechercher particulière dans "totalArray" par exemple dire : tous les array contenant [UIColor blueColor] et [UIColor redColor] à  la suite. Ou encore : tous les array égaux à  "colorArray2".


Pour cela j'ai donc pensé au NSPredicate et j'ai essayé ceci :



NSPredicate *predicate = [NSPredicate predicateWithFormat:@SELF CONTAINS %@", colorArray];
NSArray *result = [totalArray filteredArrayUsingPredicate:predicate];
NSLog(@%@", result);

Mais "result" est vide. J'ai fait un petit schéma pour que vous compreniez mieux.


Comment puis-je effectuer une requête avec des array avec un NSPredicate ? Avez vous une idée sur la question ?


Merci d'avance :)


Réponses

  • Joanna CarterJoanna Carter Membre, Modérateur
    mai 2015 modifié #2
    Tu peut utiliser [NSPredicate predicateWithBlock:] et, dans le block, faire la comparaison entre la "cible" et chaque NSArray.
  • BenjoBenjo Membre

    Merci pour ta réponse rapide Joanna. Mais du coup, une fois que j'analyse un objet, par exemple en premier je vais analyser "colorArray", comment je peux faire pour dire "si cet array contient [bleu, rouge] à  la suite alors return true" ?


  • BenjoBenjo Membre

    Je peux essayer de créer un NSMuatbleArray et de boucler sur l'array que j'analyse. Je regarde si une couleur correspond et j'entre son index dans le mutableArray. Ensuite j'analyse si les index se suivent. Le problème c'est que ça me parait bien complexe comme méthode...


  • BenjoBenjo Membre

    On peut essayer ça :



    NSArray *colorArray = @[;[UIColor blueColor], [UIColor redColor], [UIColor orangeColor], [UIColor purpleColor]];
    NSArray *colorArray2 = @[;[UIColor redColor], [UIColor blueColor], [UIColor orangeColor], [UIColor purpleColor]];
    NSArray *totalArray = @[;colorArray, colorArray2];

    NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
    NSArray *array = evaluatedObject;

    if ([array containsObject:[UIColor blueColor]] && [array containsObject:[UIColor orangeColor]]) {
    return YES;
    }

    return NO;
    }];

    Sauf que je ne peut pas dire : "si array contient bleu et orange à  la suite].


  • AliGatorAliGator Membre, Modérateur
    mai 2015 modifié #6
    Ca dépend ce que tu veux faire.

    Si tu veux trouver " tous les array égaux à  "colorArray2" ", ce n'est pas "SELF CONTAINS %@ qu'il faut utiliser comme NSPredicate (qui teste, pour chaque élément, si cet élément (tableau) CONTIENT l'autre tableau), mais SELF == %@".
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@SELF == %@", colorArray];
    NSArray *result = [totalArray filteredArrayUsingPredicate:predicate];
    NSLog(@%@", result);
    // retourne un tableau contenant un seul élément (car il n'y a qu'un résultat)
    // Ce résultat se trouve être le tableau colorArray, justement. Normal.
    Si par contre tu veux sortir tous les tableaux contenant la couleur bleue [UIColor blueColor], c'est bien "SELC CONTAINS %@ qu'il faut que tu utilises... et auquel tu dois alors lui passer un unique élément UIColor. Car il va exécuter ce prédicat sur chaque tableau-de-couleurs de ton tableau totalArray (donc sur le tableau colorArray puis sur le tableau colorArray2), et pour chaque tableau-de-couleurs il va tester si ce tableau contient ton objet. Comme tu as des tableaux-de-couleurs, pour être cohérent l'opérateur CONTAINS" attend à  ce que tu lui passes une couleur, pour savoir si ton tableau-de-couleurs contient une-couleur.

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@SELF CONTAINS %@", [UIColor blueColor]];
    NSArray *result = [totalArray filteredArrayUsingPredicate:predicate];
    NSLog(@%@", result);
    // result contient bien un seul élément (car il n'y a qu'un résultat)
    // Ce résultat se trouver être le tableau colorArray, le seul qui contient la couleur bleue.
    UIColor blueColor], [UIColor redColor", il testerais si colorArray contient ce tableau, ce qui n'est vrai non pas si "colorArray contient blueColor et contient aussi redColor", mais n'est vrai que si "colorArray contient lui-même un sous-tableau de 2 éléments, blueColor et redColor". Ce qui n'est pas la même chose !

    Si tu veux sortir " tous les array contenant [UIColor blueColor] et [UIColor redColor] " alors ton NSPredicate va plutôt ressembler à  ceci : " [NSPredicate predicateWithFormat:@SELF CONTAINS %@ AND SELF CONTAINS %@, [UIColor blueColor], [UIColor redColor]] .

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@SELF CONTAINS %@ AND SELF CONTAINS %@", [UIColor blueColor], [UIColor redColor]];
    NSArray *result = [totalArray filteredArrayUsingPredicate:predicate];
    NSLog(@%@", result);
    // Encore une fois, result contient un résultat, qui est colorArray
    // Ce qui est bien ce qui est attendu car c'est le seul sous-tableau de totalArray
    // qui contient à  la fois blue et red.
    Il faut être cohérent... et avec ta structure (tableau de tableaux et predicates prenant des tableaux et itérant sur des tableaux...) y'a moyen de s'y perdre (en Swift et donc avec des tableaux typés tu y verrais plus clair :P) donc décompose bien ta réflexion pas à  pas, quitte dans ta tête à  donner un petit nom plus explicite pour les objets que tu manipules, genre parler de "ColorList" pour ton NSArray de UIColor, et de "Tableau de ColorList" pour les NSArray de NSArray de UIColor, pour ne pas les confondre.
  • BenjoBenjo Membre

    Merci AliGator pour ta réponse ça fait du bien d'y voir plus clair ! Déjà  ça me met au point sur un bon nombre de choses. Par contre j'ai toujours un petit soucis... Si je veux dire : "tous les array contenant [UIColor blueColor] et [UIColor redColor] à  la suite" comment puis-je dire ça ? ça fait littéralement plusieurs heures que je me casse la tête avec un algorithme pouvant me sortir "tous les array contenant [UIColor blueColor] et [UIColor redColor] à  la suite" mais impossible je n'y arrive vraiment pas. Je ne vois juste pas comment c'est possible de demander ça en fait.


  • AliGatorAliGator Membre, Modérateur

    comment je peux faire pour dire "si cet array contient [bleu, rouge] à  la suite alors return true" ?

    C'est effectivement toute la difficulté. Même en dehors de NSPredicate, il n'y a pas à  ma connaissance de méthode toute faite pour savoir si un tableau A contient les éléments, dans l'ordre, d'un tableau B.

    Donc, en dehors de réfléchir à  l'aspect NSPredicate, il faudrait déjà  écrire une méthode qui, pour un tableau A, regarde si un tableau B est un sous-ensemble du tableau A, comme par exemple B=[3,4] pour le tableau A=[1,2,3,4].

    Ce qui n'est déjà  pas si immédiat en soi.

    Je peux essayer de créer un NSMuatbleArray et de boucler sur l'array que j'analyse. Je regarde si une couleur correspond et j'entre son index dans le mutableArray. Ensuite j'analyse si les index se suivent. Le problème c'est que ça me parait bien complexe comme méthode...

    Cela ne marcherait pas, car si tu as [bleu, vert, bleu, rouge], tu risques de comparer l'index du premier bleu (0) avec l'index du premier rouge (3), et conclure qu'ils ne se suivent pas...
  • BenjoBenjo Membre


    Donc, en dehors de réfléchir à  l'aspect NSPredicate, il faudrait déjà  écrire une méthode qui, pour un tableau A, regarde si un tableau B est un sous-ensemble du tableau A, comme par exemple B=[3,4] pour le tableau A=[1,2,3,4].




    Pour ça on peut utiliser les NSSet avec la méthode subsetOf:. Mais cette méthode à  ses limites puisque par exemple si je teste un tableau A[1, 1] sur un tableau B[1, 2, 3, 4], il va me retourner vrai alors que c'est faux. Il va prendre le 1 du tableau A et le comparer au tableau B et dire que c'est vrai. Or ce n'est pas ce que je cherche ici mais je vais voir si je peux m'appuyer là  dessus.


     


    En fait, je souhaite créer un algorithme me permettant cela pour un algo de Mastermind. Par exemple, dire que si je tombe sur telle possibilité, alors tu supprimes les possibilités contenant [1, 2] au bon endroit vous voyez ?


     


    ça fait pas mal de temps que je réfléchis dessus c'est assez complexe...

  • AliGatorAliGator Membre, Modérateur
    Voilà  ma version (pas testé sous toutes les coutures, mais bon, je te laisse lire et valider)

    BOOL arrayContainsArray(NSArray* bigArray, NSArray* subArrayToFind)
    {
    NSUInteger subLen = [subArrayToFind count];
    NSUInteger maxIdx = [bigArray count] - subLen;
    for (NSUInteger idx = 0; idx < maxIdx; ++idx) {
    if ([[bigArray subarrayWithRange:NSMakeRange(idx, subLen)] isEqualToArray:subArrayToFind]) {
    return YES;
    }
    }
    return NO;
    }
    L'idée est, pour un tableau A dont on veut savoir s'il contient un sous-tableau B, de parcourir le tableau A de 0 à  len( B)-len(A), et à  chaque itération de voir si le sous-tableau A[idx..<idx+len( B)] est équivalent au tableau B. Si pendant l'itération sur A on trouve qu'une "slice" de A correspond effectivement à  B, alors B est bien un sous-tableau de A donc on retourne YES.

    Utilisation :

    NSArray *colorArray1 = @[;[UIColor blueColor], [UIColor redColor], [UIColor orangeColor], [UIColor purpleColor]]; // match
    NSArray *colorArray2 = @[;[UIColor redColor], [UIColor greenColor], [UIColor orangeColor], [UIColor purpleColor]]; // no match
    NSArray *colorArray3 = @[;[UIColor redColor], [UIColor orangeColor], [UIColor blueColor], [UIColor purpleColor]]; // no match
    NSArray *colorArray4 = @[;[UIColor redColor], [UIColor greenColor], [UIColor blueColor], [UIColor orangeColor], [UIColor blueColor], [UIColor redColor], [UIColor grayColor]]; // match

    NSArray *totalArray = @[;colorArray1, colorArray2, colorArray3, colorArray4];
    NSArray* colorsToFind = @[;[UIColor blueColor], [UIColor redColor]];

    NSPredicate* pred = [NSPredicate predicateWithBlock:^BOOL(NSArray* arrayOfColors, NSDictionary *bindings) {
    return arrayContainsArray(arrayOfColors, colorsToFind);
    }];
    NSArray* results = [totalArray filteredArrayUsingPredicate:pred];
    NSLog(@%@", results);
  • BenjoBenjo Membre

    J'ai regardé ta solution effectivement elle fonctionne bien merci beaucoup. Par contre j'ai parfois un crash qui m'indique "Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSArray subarrayWithRange:]: range {0, 5} extends beyond bounds [0 .. 3]" je suis en train de regarder pour corriger. En tout cas merci beaucoup ça m'aide énormément.


  • AliGatorAliGator Membre, Modérateur
    mai 2015 modifié #12
    Ah ouais, sans doute parce que j'ai déclaré maxIdx comme un NSUInteger, alors que si subArray est plus long que bigArray, forcément c'est négatif. Mais comme je l'ai déclaré NSUInteger (donc unsigned), un nombre négatif interprété en unsigned ça donne en fait un nombre positif super-mega-grand (underflow) et il exécute quand même la boucle (en plantant dès les première itération car il essaye de prendre une tranche de taille subLen=5 dans un tableau qui ne fait que 3 éléments)

    Du coup soit tu rajoutes un "return NO" dès le début quand subArray.count > bigArray.count, pour sortir tout de suite dans ce genre de cas... soit (plus simple) tu changes le type de maxIdx en NSInteger au lieu de NSUInteger, comme ça il peut être négatif, et dans ce cas la boucle for ne s'exécutera pas puisque dès la première itération idx<maxIdx sera faux.
  • FKDEVFKDEV Membre
    mai 2015 modifié #13

    Sinon il y a le hack suivant :

    -convertir les couleurs en texte: "#RRGGBB"

    -les concaténer dans un chaà®ne qui représentera le tableau

    -utiliser les fonctions de recherche de sous-chaà®ne de NSString type rangeOfString:options:range:


     


    Pas le plus efficace en CPU, ni le plus gratifiant pour l'égo en terme d'algo mais surement le plus fiable et le plus rapide à  mettre en place.


  • LarmeLarme Membre
    mai 2015 modifié #14


    Sinon il y a le hack suivant :

    -convertir les couleurs en texte: "#RRGGBB"

    -les concaténer dans un chaà®ne qui représentera le tableau

    -utiliser les fonctions de recherche de sous-chaà®ne de NSString type rangeOfString:options:range:


     


    Pas le plus efficace en CPU, ni le plus gratifiant pour l'égo en terme d'algo mais surement le plus fiable et le plus rapide à  mettre en place.




    Pas bête.


     


    Mais rangeOfString:options:range: retourne uniquement la première occurence, non ? Du coup, faudrait juste boucler (ou utiliser un NSRegularExpression. Dans son cas, tu peux avoir Rouge, Bleu, Rouge, Bleu et tu voudrais peut-être les 2.


     


    Avec même en bonus, la possibilité de faire un NSFormatter custom qui se charge de faire des UIColor/NSString. J'en parle car j'ai dû en faire un hier (pas sur des UIColor/NSString, mais bon).


  • FKDEVFKDEV Membre
    mai 2015 modifié #15

    Oui il faut boucler.


     


    En fait en y re-pensant, il y a mieux : utiliser le même principe mais avec NSData et sa méthode rangeOfData:options:range:


     


    Les couleurs sont alors à  convertir en uint32_t qu'on colle dans un NSMutableData.


  • AliGatorAliGator Membre, Modérateur
    FKDEV pourquoi se compliquer la vie ainsi alors que mon algo posté au dessus et qui tient en moins de 10 lignes fonctionne pour tous types de tableau sans avoir besoin de convertir en NSString/NSData dans un sens puis dans l'autre et de parcourir de multiples fois ton tableau (un pour la conversion, un pour boucler sur rangeOfData: et un pour la conversion inverse...)

    Tu te prends bien la tête pour faire compliqué ^^
  • BenjoBenjo Membre

    Merci à  tous pour vos réponses


     


     




    Ah ouais, sans doute parce que j'ai déclaré maxIdx comme un NSUInteger, alors que si subArray est plus long que bigArray, forcément c'est négatif. Mais comme je l'ai déclaré NSUInteger (donc unsigned), un nombre négatif interprété en unsigned ça donne en fait un nombre positif super-mega-grand (underflow) et il exécute quand même la boucle (en plantant dès les première itération car il essaye de prendre une tranche de taille subLen=5 dans un tableau qui ne fait que 3 éléments)


    Du coup soit tu rajoutes un "return NO" dès le début quand subArray.count > bigArray.count, pour sortir tout de suite dans ce genre de cas... soit (plus simple) tu changes le type de maxIdx en NSInteger au lieu de NSUInteger, comme ça il peut être négatif, et dans ce cas la boucle for ne s'exécutera pas puisque dès la première itération idx<maxIdx sera faux.




     


    Effectivement après avoir changé en NSInteger je n'ai plus de crash. Je cherchais du mauvais coté et je pensais plutôt qu'il y avait un problème lié à  la boucle et à  la taille du Range. Merci beaucoup.


     


    @FKDEV et @Larme


    Effectivement c'est une solution mais comme le dit Ali, son algo marche pour tous les types de tableau et c'est bien pratique. Par contre, je me demande sur des tableau plus grand laquelle des solutions est la plus performante. car quitte à  "m'embêter un peu" à  convertir les couleurs en string, pourquoi pas car mais je ne sais pas ce qui est le plus rapide entre NSPredicate et une boule avec un Range. Car là  je travaille avec des tableau de taille 4 et des petits tableau de taille 2. Mais à  l'avenir je pourrais surement travailler avec des tableaux de taille 100 donc à  voir aussi coté performances.


    Comme je l'ai dit précédemment, j'utilise cela pour la recherche de solutions au Mastermind. Donc au début je calcul les possibilités (1296 avec 6 couleurs et 4 pions par lignes) puis j'en enlève au fur et à  mesure. Du coup je me disais que pour le défi je pourrais paramétrer le nombre de couleurs et de pions par ligne.


    Mais quand je vois déjà  le temps de calcul qui est d'environ 1sec pour 6 couleurs et 4 pions, je me dis qu'il va falloir optimiser quoi...


  • AliGatorAliGator Membre, Modérateur
    mai 2015 modifié #18
    Heu nan mais attend y'a un problème de conception là  si c'est pour un mastermind...


    UIColor c'est une représentation visuelle d'une couleur. C'est fait pour l'UI.

    Ton algo pour tester les possibilités et la recherche de solutions c'est la partie métier, qui doit manipuler des objets modèle.


    Un algo de résolution de problème ne doit pas manipuler de l'UI. C'est contraire au MVC. Qu'est ce qui se passe si demain tu veux utiliser le même algo mais que le bleu tu l'affiches un peu différemment qu'avec une [UIColor blueColor] mais avec une couleur #3333FF a la place ? Ou si tu veux aussi utiliser ton algo pour un outil en ligne de commande qui affiche les résultats dans la console ?


    Ton algo devrait plutôt manipuler des entiers représentant des index de couleur, ou un enum par exemple. Genre si tu as 6 couleurs utilise des tableaux de nombres de 1 à  6 (ou 0 à  5), pas des UIColor ! Comme ça ton algo (métier) marchera quelle que soit la vue / représentation graphique que tu mets derrière. MVC powa.


    Et en plus tu manipuleras ainsi des scalaires plutôt que des objets. Bien plus efficace et performant que de manipuler des objets, plus économe en mémoire (pas d'allocation d'un NSObject/UIColor, un Int suffit), plus rapide et plus sûr pour les comparaisons ("==" plutôt que "isEqual:", surtout avec UIColor et les questions de ColorSpace & co...), etc...
  • FKDEVFKDEV Membre
    La solution d'Ali est meilleure sur le principe bien sur. Je ne l'avais pas lu en fait, je pensais que c'était plus compliqué.

    Mais bon, ça vaut toujours le coup d'avoir des alternatives.


    Cela dit attention avec la comparaison des couleurs car isEqual peut facilement échouer :

    http://stackoverflow.com/questions/970475/how-to-compare-uicolors
  • AliGatorAliGator Membre, Modérateur
    mai 2015 modifié #20


    Cela dit attention avec la comparaison des couleurs car isEqual peut facilement échouer :
    http://stackoverflow.com/questions/970475/how-to-compare-uicolors

    D'ou mon dernier post. C'est vraiment pas logique et pas une bonne chose de faire un algo comme ça sur des UIColor, et anti-MVC de toute façon.
  • BenjoBenjo Membre

    @AliGator


    Alors oui c'est ce que je suis en train de faire je vous rassure. De base, c'était juste un petit test pour trouver un array dans un autre array je ne gère pas mon Mastermind avec des UIColor dans mes array. Après j'ai beaucoup de boucles pour enlever des solutions mais vraiment beaucoup de boucles donc bon il faut que je regarde un peu pour optimiser.


     


    @FKDEV


    J'ai jamais eu ce problème de comparaison de couleur qui échoue c'est bizarre je vois pas d'où ça vient...


  • AliGatorAliGator Membre, Modérateur
    ça me paraà®t logique que la comparaison de 2 UIColor ne soit pas si simple vu que les conversions entre colorspaces font forcément des erreurs d'approximation.
  • BenjoBenjo Membre

    Après il faut faire une approximation. Mais je ne vois en quoi comparer par exemple deux couleurs vertes peut échouer j'ai du mal à  comprendre...


  • AliGatorAliGator Membre, Modérateur
    C'est loin d'être aussi simple de faire des approximations dans le domaine de la colorimétrie. Déjà  simplement parce que tous les espaces colorimétriques ne sont pas euclidiens et aussi parce que chaque personne, chaque oe“il, mais aussi chaque calibration d'écran joue sur ces espaces non-vectoriels.


    Le bilan c'est que par exemple il n'existe aucune formule de mesure de "distance entre 2 couleurs" qui fonctionne pour toutes les couleurs. Si tu calcules la distance entre 2 verts qui te semble à  l'oe“il nu parfaitement identiques, tu vas avoir une formule qui te diras qu'il sont très proches l'un de l'autre (d<1.0 disons, arbitrairement). En appliquant exactement la même formule à  deux bleus qui eux aussi sont impossible à  distinguer l'un de l'autre à  l'oe“il nu, tu auras par contre d=1.7 et donc ta formule va considérer qu'ils sont différents. De même tu peux avoir un orange et un marron, qu'à  l'oe“il nu vont te sembler immédiatement différents sans l'ombre d'un doute, dont la distance entre les deux sera 0.9...


    à‰videmment j'ai sorti des chiffres bidons mais c'est juste pour dire que la perception colorimétrique est tellement complexe et nous percevons pas les différences de couleurs de façon aussi intense entre 2 bleus qu'entre 2 rouges ou 2 verts (c'est du aux nombres de cônes et bâtonnets dans nos yeux) que c'est loin d'être simple.


    Ainsi une formule de distance comme par exemple d=sqrt(R*R+V*V+B*B) qu'on aurait envie d'utiliser (algèbre d'un espace vectoriel 3D euclidien classique) pour vérifier si 2 couleurs sont "proches" ou "à  considérer comme identiques à  un petit delta d'approximation près" ne sont pas applicables pour les couleurs.

    De même si pensais dire "la composante rouge des 2 couleurs sont égales... à  un deltaR près", le problème est que le bon "deltaR" (marge d'erreur) n'est pas le même selon les valeurs de Vert et Bleu des couleurs (pour une couleur dans les rouges ou V et B sont proches de zéro ca sera pas la même échelle que si V et B sont forts ou que V est fort et B faible, etc) justement à  cause de cette perception colorimétrique et du fait que l'espace RGB n'est pas euclidien donc n'obéit pas aux règles habituelles de la géométrie 3D et formules de distance classiques.



    Bon après si tu veux en savoir + le sujet est très vaste et je vais pas faire un cours plus détaillé mais je t'invite à  te documenter sur Google si le sujet t'intéresse car le monde des couleurs est loin d'être aussi simple qu'on le pense.
Connectez-vous ou Inscrivez-vous pour répondre.