NSDictionary et NSTableView

FloFlo Membre
01:07 modifié dans API AppKit #1
Bonjour à  tous,

J'ai un NSMutableDictionary qui référence un certain nombre d'objet ayant chacun un certain nombre de champs.

Ce que je souhaiterai faire c'est présenter ces objets à  l'utilisateur de la manière suivante : Objet1.champ1 .... Objet1.champn puis objetN.champ1 .... ObjectN.champn

J'ai naturellement pensé à  NSTableView qui fait parfaitement cela mais à  priori c'est peu adapté à  la classe NSDictionary. Quelqu'un aurait-il une idée pour afficher mes objet de la manière la plus efficace possible ?

Merci d'avance pour vos réponses !

Réponses

  • Philippe49Philippe49 Membre
    mai 2009 modifié #2
    Au contraire, chaque ligne d'une NSTableView est naturellement un NS(Mutable)Dictionary dont les clés sont les identifiers des Colonnes.
  • AliGatorAliGator Membre, Modérateur
    01:07 modifié #3
    Je suis pas sûr d'avoir capté ta structure : ton NSDictionary contient N objets, chacun en face d'une clé différente ? Genre un NSDictionary ayant 3 clés "Obj1", "Obj2" et "Obj3", et la valeur de la clé "Obj1" est un objet ayant 3 propriétés "p1", "p2" et "p3" alors que la valeur associée à  la clé "Obj2" est un objet ayant les propriétés "p4" et "p5" ?

    Et si c'est ça, du coup tu veux afficher tes données comment ? Sous forme d'un truc hiérarchique (comme dans la vue liste dans le Finder où tu peux afficher le contenu d'un dossier avec un petit triangle) ? Ou tu vois ça autrement ?
  • FloFlo Membre
    01:07 modifié #4
    C'est vrai que c'est un peu nébuleux ce que je dis :

    Le NSMutableDictionary contient des objets chacun identifié par une clé (NSString), un petit schéma s'impose :

    (@clé1, monObjet1, @clé2, monObjet2, @cléN, monObjetN).

    Les objets monObjet sont du même type et contiennent tous les mêmes champs.

    Moi je voudrait afficher simplement le contenu du NSMutableDictionary, genre:

    colonne0        colonne1            colonne2              colonne3

    @clé1  monObjet1.champ1  monObjet1.champ2  monObjet1.champn
    @clé2  monObjet2.champ1  monObjet2.champ2  monObjet2.champn
    @cléN  monObjetN.champ1  monObjetN.champ2  monObjetN.champn

    J'ai lu sur le net qu'il y avait deux solutions pour faire ça dans une NSTableView :
    - utiliser un NSArray avec toutes les clés du NSMutableDictionary
    - utiliser un NSArray de NSDictionary

    Je trouve ces solutions lourdes et difficiles à  mettre en place, l'idéal serait une solution qui ne rajoute pas de structure de données à  mon NSMutableDictionary existant.

    C'est plus clair là  ou pas ?

  • FloFlo Membre
    01:07 modifié #5
    Un petit exemple avec les voitures :

    la voiture de:          nbRoue          couleur              commentaire

    @maVoiture              4                  verte            une Dodge Viper

    @saVoiture              2              on sait plus            à  la casse

    @uneVoiture            5                turquoise          pas encore sortie
  • AliGatorAliGator Membre, Modérateur
    01:07 modifié #6
    Oui plus clair... mais pourquoi utiliser alors un NSDictionary pour stocker tes objets et pas avoir un NSArray de tes objets ? Je veux dire c'est voulu, t'as une raison particulière derrière, ou c'est parce que t'as pas pensé à  NSArray et préféré mettre des données dans un NSDictionary avec des clés arbitraires ?

    Sinon ce que tu souhaites faire est tout à  fait envisageable, et ce sans rajouter d'objet modèle supplémentaire : pour savoir quel objet récupérer en fonction de la ligne, il suffit de récupérer le tableau "allKeys" de ton NSDictionary.
    - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView {<br />&nbsp; return [monDico count]; // nb lignes = nb clés dans dico<br />}<br />- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex<br />{<br />&nbsp; NSArray* keysList = [monDico allKeys];<br />&nbsp; NSString* keyName = [keysList objectAtIndex:rowIndex]; // récupérer le nom de la N-ième clé à  afficher dans la N-ième ligne<br />&nbsp; if ([[aTableColumn identifier] isEqualToString:@&quot;key&quot;])<br />&nbsp; {<br />&nbsp; &nbsp; return keyName; // pour cette colonne particulière, on affiche nom de la clé<br />&nbsp; } else {<br />&nbsp; &nbsp; id obj = [monDico objectForKey:keyName]; // sinon on récupère l&#39;objet correspondant<br />&nbsp; &nbsp; return [obj valueForKey:[aTableColumn identifier]]; // et on retourne la valeur du &quot;champ&quot; demandé (accès à  la propriété grâce au KVC) <br />&nbsp; }<br />}
    
    Après il suffit d'affecter à  tes colonnes l'identifier "key" pour la première, "champ1" pour la 2e, "champ2" pour la 3e... Et si tes objets contiennent bien une propriété nommée "champ1" (à  laquelle tu peux accéder grâce à  valueForKey, merci le KVC), cette valeur s'affichera dans la colonne adéquate, et pareil pour les autres.
  • FloFlo Membre
    01:07 modifié #7

    mais pourquoi utiliser alors un NSDictionary pour stocker tes objets et pas avoir un NSArray de tes objets ?


    Je souhaite ainsi profiter de l'accès en O(1) (hachage) d'un objet à  partir de sa clé (performance des recherches + mise à  jour dans ma base).

    <br />[monDico allKeys];<br />
    


    Oui je m'étais penché sur cette solution mais elle ma semblée peu réaliste, voilà  ce que dit la doc sur cette méthode :


    A new array containing the receiver's keys


    Imaginons j'ai 5000 objets dans mon NSMutableDictionary, il va falloir faire 5000 copies des 5000 clés à  chaque fois ? C'est pas terrible niveau perfs...
  • AliGatorAliGator Membre, Modérateur
    01:07 modifié #8
    dans 1242417250:
    Je souhaite ainsi profiter de l'accès en O(1) (hachage) d'un objet à  partir de sa clé (performance des recherches + mise à  jour dans ma base).
    Mouais... Vraiment pas persuadé que ça vaille le coup de se prendre le chou pour ça... surtout quand on sait que les NSArrays ne sont des tableaux que conceptuellement, mais que sous le capot Apple est tellement fort qu'ils ont optimisé un max de ce côté, de sorte que quand le nombre d'éléments dans le tableau devienne trop important, ça passe en accès par table de hachage ou techniques du genre.

    De plus, même s'il n'y avait pas cette optimisation, un NSArray est un tableau indexé, pas une liste chaà®née. Donc l'accès à  l'élément N d'un NSArray est en O(1) (c'est l'insertion/suppression à  la limite qui peut ne pas être en O(1) si c'est des éléments en plein milieu).
    D'ailleurs pour les NSDictionary et donc une table de hachage je dirais plutôt que c'est en O(ln(n)) plutôt qu'en O(1), vu que d'une part il faut trouver la liste chaà®née des valeurs dont le hash correspond au hash de l'objet recherché, puis parcourir enfin cette liste chaà®née... Alors qu'avec un tableau indexé, on récupère l'objet qui se trouve pointé par l'adresse (&tableau + requestedIndex) en gros hein. Donc linéaire, lui.

    cfarray_results.jpg
    Source: RidiculousFish


    dans 1242417250:
    Imaginons j'ai 5000 objets dans mon NSMutableDictionary, il va falloir faire 5000 copies des 5000 clés à  chaque fois ? C'est pas terrible niveau perfs...
    Ouh là  non non non ! Eventuellement déjà  je me demande si les NSDictionary ne contiennent pas déjà  un tableau de keys, donc il ne ferait que te le retourner. Mais même si ce n'est pas le cas, cela ne va pas faire une copie des 5000 clés !! Le referenceCounting est justement là  pour ça, encore heureux :D Ca ne va donc fait qu'un "retain" sur les clés lorsqu'il va construire le NSArray (mais ça restera les mêmes objets, les mêmes pointeurs, donc pas d'allocation pour dupliquer les clés en mémoire quand même !!)

    Faut pas oublier que Cocoa est quand même bien foutu, et qu'en particulier les classes les plus communes et utilisées comme NSString ou les classes de collections NSArray/NSDictionary/NSSet/... sont quand même pas mal optimisées (cf le lien plus haut)
  • FloFlo Membre
    01:07 modifié #9
    Oui je me suis mal exprimé, je voulais dire une copie des (5000) références des clés. Et puis on est sûr que ça construit un nouveau tableau avec toutes les références puisque la doc le dit...  ::)

    Ya un truc qu'est quasiment certain quand même, c'est que la recherche d'un élément à  partir d'une clé est plus rapide dans un NSDictionary que dans un NSArray sinon je vois pas l'intérêt de cette première classe...

    Après je le concède, est-ce que la différence vaut le coup quand on voit les difficultés que ça génère pour exploiter le NSDictionary dans une NSTableView.
  • 01:07 modifié #10
    dans 1242470301:

    Ya un truc qu'est quasiment certain quand même, c'est que la recherche d'un élément à  partir d'une clé est plus rapide dans un NSDictionary que dans un NSArray sinon je vois pas l'intérêt de cette première classe...


    Je sais pas si c'est plus rapide ou pas... je pense pas. Une NSArray si tu veux faire une recherche rapide il te suffit de faire un NSSortDescriptor avec un compare: en utilisant par exemple "hasPrefix:" de NSString. Ensuite tu fais ta fast enumeration et tu peux faire un break dès que tu rencontres le premier élément qui ne rempli plus la condition "hasPrefix:mySearch".

    Un NSDictionary c'est autre chose... mais ça dépend comment tu le construit je pense... Si tu fais une NSArray qui contient des NSDictionary, là  encore tu peux utiliser NSSortDescriptor.. le seul truc c'est qu'il se peut que tu veuilles effectuer la recherche sur plusieurs clés du dico, donc tu devras faire plusieurs sortDescriptors.


    D'ailleurs concernant la recherche, au lieu d'utiliser hasPrefix, tu peux aussi utiliser "rangeOfString:" qui te retournes (NSNotFound, 0) si jamais il ne trouve pas.

    Il reste toujours la question de savoir si un NSSortDescriptor est rapide. Personnellement je pense que c'est vraiment plus rapide qu'une énumération (me semble meme que la question a déjà  été évoquée?).


    Bref, je te conseille quand meme de construire ta TableView avec NSArray qui contient des NSDictionary avec les clés qui représentent les column du tableau.


    (Et puis je t'assure que même avec 5000 références, ta recherche ne durera qu'une seconde même pas).
  • AliGatorAliGator Membre, Modérateur
    01:07 modifié #11
    dans 1242470301:
    Ya un truc qu'est quasiment certain quand même, c'est que la recherche d'un élément à  partir d'une clé est plus rapide dans un NSDictionary que dans un NSArray sinon je vois pas l'intérêt de cette première classe...
    Bah non justement c'est loin d'être certain... Ca dépend comment tu veux parcourir ta collection de données... mais lis le lien que j'ai filé (= clique sur l'image/graphique de mon post précédent), tu verras tout y est expliqué sur l'optimisation qui est faite pour les NSArrays (et autres) dans Cocoa...

    Et donc comme Apple a sû optimiser tout ce qu'il y a en dessous pour que l'accès aux éléments d'un NSArray soit optimal (au même titre que pour un NSDictionary, note)... bah du coup certes les NSDictionary ont par concept une table de hashage sous le capot ça c'est un peu le principe... mais ça n'empêche pas d'avoir des NSArray super rapides pour les temps d'accès (sachant que ce n'est pas le même en random ou séquentiel bien sûr, et que c'est encore plus rapide si tu utilises la NSFastEnumaration "foreach" avec Objective-C 2.0)...

    Donc au final vu tout ce qui est mis en place sous le capot qui fait que ce genre de trucs est super optimisé, au point que la notion de "NSArray" et "NSDictionary" soit surtout conceptuelle (= un NSArray c'est pour stocker une liste/un tableau de données, alors qu'un NSDictionary c'est un ensemble de paires clé/valeur non ordonné) mais que ça ne présage pas de l'implémentation sous-jacente de ces classes pour autant (d'où le titre "our arrays.... that aren't" de l'article)...
    Tu risques + de te prendre les pieds dans le tapis à  réfléchir sous la forme "l'implémentation sous-jacente devrait être une table de hashage donc optimisée en O(log(n)) donc devrait être plus rapide dans tel cas de figure..." alors que y'a tellement de paramètres à  prendre en compte (accès séquentiel, random, optimisation interne selon le nombre d'éléments, type de boucle for/foreach pour accès aux éléments de la collection lors de boucles...) que c'est loin d'être aussi simple en plus...
  • FloFlo Membre
    01:07 modifié #12
    Ok les gars je m'incline  o:)

    Ce sera donc un NSArray ! Merci pour votre aide qui fut (comme toujours) très instructive et qui fait que c'est toujours un plaisir de poster une question sur ce forum !  :)
  • psychoh13psychoh13 Mothership Developer Membre
    01:07 modifié #13
    L'intérêt d'un dictionaire n'est pas du tout la rapidité, bien au contraire.
    Un tableau a pour but d'associer un nombre plus ou moins grand de valeur à  un index, mais dans un tableau théoriquement fini.
    En revanche, un dictionaire contient en théorie toutes les clés de l'univers, mais toutes ces clés ne sont pas forcément associées à  une valeur, et c'est ça la grosse différence.

    Le dictionaire n'a de réel intérêt que si tu as peu de valeurs pour beaucoup de clés possibles, par exemple, mettons que tu aies trois valeurs obj1, obj2 et obj3 ayant pour index respectifs 100, 45500, et 856410...
    Si tu voulais stocker ces valeurs dans un tableau, il te faudrait un tableau de 856.410 valeur avec tout de même 856.407 cases vides ! C'est idiot, alors dans ce cas là  tu utiliserais un dictionaire avec 3 valeurs et 3 clés de type nombre.

    C'est vraiment le seul avantage d'un dictionaire par rapport à  un tableau, c'est que tu as une infinité de clés. C'est pour ça d'ailleurs que lorsque tu fais [myArray objectAtIndex: idx] avec une valeur supérieure au -count de ton tableau tu as une exception, alors que si tu fais [myDict objectForKey: key] avec une clé qui n'est associée à  rien, tu reçois "nil", et ça c'est le cas dans toutes les implémentations. Tout simplement parce qu'on suppose que le dictionnaire possède toutes les clés mais qu'elles ne sont pas toutes associées à  une valeur.

    La vitesse n'est pas du tout l'avantage d'un dictionaire.

    Et pour ce qui est de la méthode -allKeys, le problème de celle-ci c'est qu'elle ne garantit pas de retourner les clés dans le même ordre, si tu changes une clé, tu risques d'avoir un ordre totalement différent.

    Autre chose, il faut remarquer que le dictionaire, par mesure de sécurité, copie les clés qu'on lui passe, donc pour commencer une clé est toujours conforme au protocol NSCopying, et ensuite ça signifie, pour les objets modifiables utilisés comme clés, qu'en utiliser -allKeys le dictionaire va sûrement créer une nouvelle instance de l'objet.
    Cependant, ce problème est réglé en utilisant des clés de type NSString, puisque la copie d'un objet non-modifiable ne fait qu'un retain.


    PS :
    dans 1242433371:
    (c'est l'insertion/suppression à  la limite qui peut ne pas être en O(1) si c'est des éléments en plein milieu).


    En regardant le tableau, c'est justement les opérations d'insertion/suppression qui sont plus efficace lorsqu'elles sont faites au beau milieu de la collection. ;)
Connectez-vous ou Inscrivez-vous pour répondre.