7.3 Affichage des films et forum de discussion 303 Nous ne donnons pas le code d’insertion des messages similaire à ceux déjà vus pour les films ou les internautes. Vous pouvez le trouver dans le code de FilmCtrl.php.En revanche, il est plus intéressant d’examiner l’affichage des messages, qui doit se faire de manière hiérarchique, avec pour chaque message l’ensemble de ses descendants, le nombre de niveaux n’étant pas limité. Comme souvent avec ce type de structure, une fonction récursive permet de résoudre le problème de manière élégante et concise. La méthode afficheMess() est chargée d’afficher, pour un film, la liste des réponses à un message dont l’identifiant est passé en paramètre. Pour chacun de ces messages, on crée une ancre permettant de lui répondre, et, plus important, on appelle à nouveau (récursivement) la fonction AfficheMess() en lui passant l’identifiant du message courant pour afficher tous ses fils. La récursion s’arrête quand on ne trouve plus de fils. Le code présente une subtilité pour la gestion de la vue. Ce que l’on doit afficher ici, c’est un arbre dont chaque nœud correspond à un message et constitue la racine du sous-arbre correspondant à l’ensemble de ses descendants. Pour l’assemblage final avec le moteur de templates, on doit associer un nom d’entité à chaque nœud. C’est le rôle de la variable nom_groupe ci-dessous qui identifie de manière unique le nœud correspondant à un message et à ses descendants par la chaîne groupid ,oùid est l’identifiant du message. La fonction affichemess() renvoie la représentation HTML du nœud courant, ce qui correspond donc à une instanciation de bas vers le haut de l’ensemble des nœuds constituant l’arborescence. private function afficheMess ($id_film , $id_pere) { // Recherche des messages fils du père courant $ r eq u et e = "SELECT ∗ FROM Message " . "WHERE id_film=’$id_film ’ AND id_pere=’$id_pere ’" ; // On crée une entité nom_groupe pour placer la présentation // des réponses au message id_pere $nom_groupe = "groupe" . $id_pere ; $this−>vue−>setVar ($nom_groupe , " " ) ; // Affiche des messages dans une liste , avec appels récursifs $resultat = $this−>bd−>execRequete ( $requete ) ; while ($message = $this−>bd−>objetSuivant ( $resultat )) { // Appel récursif pour obtenir la mise en forme des // réponses $this−>vue−>reponses = $this−>afficheMess ($id_film , $message−>id ) ; // On place les informations dans la vue $this−>vue−>texte_message = $message−>texte ; $this−>vue−>id_pere = $message−>id ; $this−>vue−>sujet = $message−>sujet ; // Attention à bien coder le texte placé dans une URL $this−>vue−>sujet_code = urlEncode($message−>sujet ); $this−>vue−>email_createur = $message−>email_createur ; 304 Chapitre 7. Production du site // Décodage de la date Unix $idate = getDate($message−>date_creation); // Mise en forme de la date décod $this−>vue−>date_message = $idate [ ’mday ’ ] . "/" . $idate [ ’mon’] . "/ " . $idate [ ’ year ’ ]; $this−>vue−>heure_message = $idate [ ’hours ’ ] . " heures " . $idate [ ’minutes ’ ]; if ($id_pere != 0) $this−>vue−>prefixe= "RE : " ; else $this−>vue−>prefixe = "" ; // Création de la présentation du message courant , et // concaténation dans l ’entité $nom_groupe $this−>vue−>append($nom_groupe , " message") ; } // On renvoie les messages du niveau courant , avec toutes // leurs réponses return $this−>vue−>getVar($nom_groupe) ; } Au moment de l’appel initial à cette fonction, on lui donne l’identifiant 0, ce qui revient à afficher au premier niveau tous les messages qui ne constituent pas des réponses. Ensuite, à chaque appel à afficheMess(), on ouvre une nouvelle balise <ul>. Ces balises seront imbriquées dans le document HTML produit, qui donnera donc bien une présentation hiérarchique des messages. On peut remarquer ici le traitement des dates. Elles sont stockées dans la base sous la forme d’un « timestamp » Unix, qui se décode très facilement avec la fonction getDate(). Cette dernière renvoie un tableau (voir page 496) avec tous les éléments constituant la date et l’heure. Il reste à les mettre en forme selon le format souhaité. // Décodage de la date Unix $idate = getDate($message−>date_creation); // Mise en forme de la date décodée $date_affiche = $date [ ’mday ’ ] . "/ " . $idate [ ’mon’] . "/" . $idate [ ’year ’ ]; $heure_affiche = $idate [ ’hours ’] . "heures " . $idate[ ’minutes ’ ]; 7.4 RECOMMANDATIONS Nous abordons maintenant le module de prédiction qui constitue l’aspect le plus original du site. D’une manière générale, la recommandation de certains produits en fonction des goûts supposés d’un visiteur intéresse de nombreux domaines, dont bien entendu le commerce électronique. Les résultats obtenus sont toutefois assez aléatoires, en partie parce que les informations utilisables sont souvent, en qualité comme en quantité, insuffisantes. 7.4 Recommandations 305 L’idée de base se décrit simplement. Au départ, on sait que telle personne a aimé tel film, ce qui constitue la base de données dont un tout petit échantillon est donné ci-dessous. Personne Films Pierre Van Gogh, Sacrifice, La guerre des étoiles Anne Van Gogh, La guerre des étoiles, Sacrifice Jacques Batman, La guerre des étoiles Phileas Batman, La guerre des étoiles, Rambo Marie Sacrifice, Le septième sceau Maintenant, sachant que Claire aime Van Gogh (le film !), que peut-on en déduire sur les autres films qu’elle peut aimer? Tous ceux qui aiment Van Gogh aiment aussi Sacrifice, donc ce dernier film est probablement un bon choix. On peut aller un peu plus loin et supposer que Le septième sceau devrait également être recommandé, puisque Pierre aime Van Gogh et Sacrifice, et que Marie aime Sacrifice et Le septième sceau. La guerre des étoiles semble plaire à tout le monde ; c’est aussi une bonne recom- mandation, bien qu’on ne puisse pas en apprendre grand-chose sur les goûts spé- cifiques d’une personne. Enfin, il y a trop peu d’informations sur ceux qui aiment Rambo pour pouvoir en déduire des prédictions fiables. Les techniques de filtrage coopératif (collaborative filtering en anglais) reposent sur des algorithmes qui tentent de prédire les goûts de personnes en fonctions de leurs votes (qui aime quoi), ainsi que de toutes les informations recueillies sur ces personnes (profession, âge, sexe, etc). Ces algorithmes étant potentiellement complexes, nous nous limiterons à une approche assez simple. Un des intérêts de ce type d’application est de faire appel intensivement à un aspect important de SQL, les requêtes agrégat, que nous n’avons pas vu jusqu’à présent. 7.4.1 Algorithmes de prédiction Il existe essentiellement deux approches pour calculer des prédictions. Approche par classification On cherche à déterminer des groupes (ou classes) d’utilisateurs partageant les mêmes goûts, et à rattacher l’utilisateur pour lequel on souhaite réaliser des prédictions à l’un de ces groupes. De même, on regroupe les films en groupes/classes. En réorganisant par exemple le tableau précédent avec les personnes en ligne, les films en colonnes, un ’O’ dans les cellules quand la personne a aimé le film, on peut mettre en valeur trois groupes de films (disons, «Action », «Classique », et « Autres »), et deux groupes d’utilisateurs. 306 Chapitre 7. Production du site Batman Rambo Van Gogh Sacrifice Le septième sceau La guerre des étoiles Pierre OO O Anne OO O Marie OO Jacques O O Phileas OO O O Il y a une assez forte similarité entre Jacques et Phileas, et toute personne qui se verrait affecter à leur groupe devrait se voir proposer un des films du groupe « Action ». C’est vrai aussi, quoique à un moindre degré, entre le premier groupe de personnes et les classiques. Les informations sont plus clairsemées, et le degré de confiance que l’on peut attendre d’une prédiction est donc plus faible. Enfin, en ce qui concerne la guerre des étoiles, il ne semble pas y avoir d’affinité particulière avec l’un ou l’autre des groupes d’utilisateurs. Les algorithmes qui suivent cette approche emploient des calculs de distance ou de similarité assez complexes, et tiennent compte des attributs caractérisant chaque personne ou chaque film. De plus la détermination des groupes est coûteuse en temps d’exécution, même si, une fois les groupes déterminés, il est assez facile d’établir une prédiction. Approche par corrélation L’algorithme utilisé pour notre site effectue un calcul de corrélation entre l’internaute qui demande une prédiction et ceux qui ont déjà voté. La corrélation établit le degré de proximité entre deux internautes en se basant sur leurs votes, indépendamment de leurs attributs (âge, sexe, région, etc). L’idée de départ est que pour prédire la note qu’un internaute a sur un film f,on peut en première approche effectuer la moyenne des notes des autres internautes sur f. En notant par n a,f la note de a sur f, on obtient : n a,f = 1 N N i=1 n i,f (7.1) Cette méthode assez grossière ne tient pas compte de deux facteurs. D’une part chaque internaute a une manière propre de noter les films, qui peut être plutôt positive ou plutôt négative. La note attribuée à un film par un internaute a ne devrait donc pas être considérée dans l’absolu, mais par rapport à la note moyenne m a donnée par cet internaute. Si, par exemple, a donne en moyenne une note de 3,5, alors une note de 3 attribuée à un film exprime un jugement légèrement négatif, comparable à une note de 2,5 pour un internaute b dont la moyenne est de 3. D’autre part il faut tenir compte de la corrélation c a,b (oudegrédeproximité) entre deux internautes a et b pour estimer dans quelle mesure la note de b doit influer sur la prédiction concernant a. On obtient finalement une formule améliorée, qui effectue la moyenne des notes pondérée par le coefficient de corrélation : n a,f = m a + 1 C N i=1 c a,i (n i,f − m i ) (7.2) 7.4 Recommandations 307 C est la somme des corrélations. Elle permet de normaliser le résultat. Il reste à déterminer comment calculer la corrélation entre deux utilisateurs a et b.Ilya plusieurs formules possibles. Celle que nous utilisons se base sur tous les films pour lesquels a et b ont tous les deux voté. Ils sont désignés par j dans la formule ci-dessous. c a,b = j (n a,j − m a )(n b,j − m b ) j (n a,j − m a ) 2 j (n b,j − m b ) 2 (7.3) On peut vérifier que si deux internautes ont voté exactement de la même manière, le coefficient de corrélation obtenu est égal à 1. Si, d’un autre côté, l’un des inter- nautes vote de manière totalement « neutre », c’est-à-dire avec une note toujours égale à sa moyenne, on ne peut rien déduire et la corrélation est nulle. On peut noter également que la corrélation entre deux internautes est d’autant plus pertinente que le nombre de films qu’ils ont tous les deux notés est important. Il y a beaucoup d’améliorations possibles – et souhaitables. Elles visent à résoudre (partiellement) les deux problèmes de base du filtrage coopératif : l’absence d’infor- mation et le fait que certains films sont peu représentatifs (comme La guerre des étoiles dans l’exemple ci-dessus). 7.4.2 Agrégation de données avec SQL Toutes les requêtes vues jusqu’à présent pouvaient être interprétées comme une suite d’opérations effectuées ligne à ligne. De même leur résultat était toujours constitué de valeurs issues de lignes individuelles. Les fonctionnalités d’agrégation de SQL permettent d’exprimer des conditions sur des groupes de lignes, et de constituer le résultat en appliquant une fonction d’agrégation sur ce groupe. L’exemple le plus simple consiste à calculer le nombre de lignes dans une table. SELECT COUNT(∗ ) FROM Film La fonction COUNT() compte le nombre de lignes. Ici, le groupe de lignes est constitué de la table elle-même. Il est bien entendu possible d’utiliser la clause WHERE pour sélectionner la partie de la table sur laquelle on applique la fonction d’agrégation. SELECT COUNT(∗ ) FROM Film WHERE genre = ’Western ’ AND annee > 1990 La fonction COUNT(), comme les autres fonctions d’agrégation, s’applique à tout ou partie des attributs de la table. On peut donc écrire également. SELECT COUNT(id_realisateur) FROM Film WHERE genre = ’Western ’ AND annee > 1990 . ligne. De même leur résultat était toujours constitué de valeurs issues de lignes individuelles. Les fonctionnalités d’agrégation de SQL permettent d’exprimer des conditions sur des groupes de lignes,. récursive permet de résoudre le problème de manière élégante et concise. La méthode afficheMess() est chargée d’afficher, pour un film, la liste des réponses à un message dont l’identifiant est. l’ensemble de ses descendants. Pour l’assemblage final avec le moteur de templates, on doit associer un nom d’entité à chaque nœud. C’est le rôle de la variable nom_groupe ci-dessous qui identifie de manière