IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Caml Discussion :

Fonction qui d'argument une chaîne de caractère renverrait l'objet qu'elle désigne


Sujet :

Caml

  1. #1
    Membre régulier
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Points : 110
    Points
    110
    Par défaut Fonction qui d'argument une chaîne de caractère renverrait l'objet qu'elle désigne
    Bonjour !

    Cela fait plusieurs fois que je suis confronté à un problème assez embêtant en Caml Light : Je ne connais pas de fonction permettant de passer de la chaîne de caractère représentant le nom d'un objet à cet objet !

    Il s'agirait donc d'une fonction de string dans 'a qui d'argument une chaîne de caractère renverrait l'objet dont le nom est la chaîne de caractère.

    Par exemple, admettons que cette fonction s'appelle read_string. On pourrait alors avoir la session suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    #read_string;;
    - : string -> 'a = <fun>
     
    #let objet=[1;2;3];;
    objet : int list = [1; 2; 3]
     
    #read_string "objet";;
    - : int list = [1; 2; 3]
    Une telle fonction serait très pratique notamment pour faire de l'interactif (mais aussi pour plein d'autres choses).

    Pour l'instant, à chaque fois que j'en ai eu besoin j'ai utilisé des match exhaustifs du genre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    #let ersatz x = match x with
              "objet_1" -> objet_1
            | "objet_2" -> objet_2
                       (...)
            | "objet_n" -> objet_n;;
    ersatz : string -> 'a = <fun>
    Sauf que là j'ai un peu plus d'un demi-millier d'objets à traiter donc même si je pourrais me démerder pour faire une fonction qui affiche le code servant à faire mon match exhaustif, ça fait un peu c***. Pis je me dis que ça pourra toujours servir dans le futur.

    Voilà, je résume :

    - Existe-t-il une telle fonction ?
    - Le cas non-échéant [lol], pourrait-on la programmer en Caml ?
    - Le cas contraire [c'est mieux] y aurait-il de la programmer en C ou je sais pas quoi puis de l'ajouter au fonctions Caml ?

    - Si aucun des cas précédents ne s'est présenté, y aurait-il moyen de contourner le problème ? Dans le cas présent, j'utilise du read_line pour faire de "l'interactif", un truc qui fasse que par exemple on lance un outil de recherche de mots et on peut directement taper le mot et appuyer sur entrée au lieu de devoir se farcir #rechercher "mot";; à chaque fois. Par exemple, rechercher étant une fonction de recherche dans une base de donnée, on pourrait avoir :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    let read () = 
    	print_string(">");
    	read_line();;
    read : unit -> string = <fun>
     
    exception stop;;
    Exception stop defined.
     
    let search strng =
    	if strng = "quit"
    	then raise stop
    	else rechercher strng;;
    search : string -> unit = <fun>
     
    let outil_de_recherche () = 
    	print_string("Go");
            print_newline();
    	try	while true 
    		do
    			search (read ())
    		done
    	with stop -> print_string("Stop");;
    outil_de_recherche : unit -> unit = <fun>
    Ce qui permet d'obtenir le résultat escompté. Seulement, ceci est possible car on a :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    rechercher : string -> unit = <fun>
    Mais admettons que rechercher soit de int list dans unit, ça ne marcherait pas. C'est dommage, non ? Alors, comment faire ?

  2. #2
    Membre éprouvé
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    832
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 832
    Points : 1 104
    Points
    1 104
    Par défaut
    Une telle fonctionnalité n'existe pas en Caml.. et heureusement !
    Elle encouragerait les gens à faire n'importe quoi (comme les variables variables de PHP), et d'ailleurs le code que tu donnes est un assez bon exemple.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    let ersatz x = match x with
              "objet_1" -> objet_1
            | "objet_2" -> objet_2
                       (...)
            | "objet_n" -> objet_n;;
    Si tu as des objets de 1 à n, il faut les stocker dans... un tableau !
    Il faut donc plutôt utiliser du code comme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    let ersatz nom = Scanf.sscanf nom "objet_%d" (fun i -> tableau_objets.(i))
    Si tu as des choses plus sophistiquées (par exemple une liste d'objets avec nom dans un jeu ou quoi), fais-toi une structure de données où tu stockes le nom de chaque objet avec ses caractéristiques, et utilise ensuite une table associative (par exemple une table de hachage) nom->objet.

  3. #3
    Membre éprouvé
    Avatar de Cacophrene
    Homme Profil pro
    Biologiste
    Inscrit en
    Janvier 2009
    Messages
    535
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Biologiste

    Informations forums :
    Inscription : Janvier 2009
    Messages : 535
    Points : 1 125
    Points
    1 125
    Par défaut
    Bonjour,

    Je vais même plus loin que bluestorm : non seulement ce n'est pas une fonctionnalité souhaitable, parce qu'elle peut facilement conduire à des programmes incorrects, mais une fonction qui renvoie une valeur de type quelconque à partir d'un argument de type fixé n'est pas vraiment typée. Regarde dans la bibliothèque standard : quelles sont les fonctions qui ont ce genre de signature ? En OCaml il y a notamment exit et les fonctions qui lèvent des exceptions (invalid_arg, failwith, raise)... ce n'est pas pour rien.

    Cordialement,
    Cacophrène

  4. #4
    Membre régulier
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Points : 110
    Points
    110
    Par défaut
    Citation Envoyé par bluestorm Voir le message
    Il faut donc plutôt utiliser du code comme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    let ersatz nom = Scanf.sscanf nom "objet_%d" (fun i -> tableau_objets.(i))
    Si tu as des choses plus sophistiquées (par exemple une liste d'objets avec nom dans un jeu ou quoi), fais-toi une structure de données où tu stockes le nom de chaque objet avec ses caractéristiques, et utilise ensuite une table associative (par exemple une table de hachage) nom->objet.
    Ah, merci beaucoup ! Je pense que c'est ça qu'il me faut (seulement je ne connais pas les fonctions que tu utilise : je connais seulement les fonctions de bases qui servent à mettre en place de petits algorithmes [et non à faire de vrai programmes, on a même jamais prononcé le mot "compilation"] qu'on voit en prépa).

    Sinon mes objets sont déjà stockés dans un tableau. En gros à l'heure actuelle ils ressemblent à ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    let Unexistant = {indice = 0; nom = ""; voisins = []; ligne = ""} and
    Les_Courtilles = {indice = 1; nom = "Les Courtilles"; voisins = [2]; ligne = "la ligne 13"} and
    Les_Agnettes = {indice = 2; nom = "Les Agnettes"; voisins = [1;9]; ligne = "la ligne 13"} and
    Carrefour_Pleyel = {indice = 3; nom = "Carrefour Pleyel"; voisins = [4;22]; ligne = "la ligne 13"} and
    St_Denis_Porte_de_Paris = {indice = 4; nom = "Saint-Denis - Porte de Paris"; voisins = [5;3]; ligne = "la ligne 13"} and
    Basilique_de_St_Denis = {indice = 5; nom = "Basilique de St-Denis"; voisins = [4;6]; ligne = "la ligne 13"} and
    St_Denis_Universite = {indice = 6; nom = "St-Denis - Université"; voisins = [5]; ligne = "la ligne 13"} and
    Pont_de_Levallois_Becon = {indice = 7; nom = "Pont de Levallois Bécon"; voisins = [8]; ligne = "la ligne 3"} and
    Anatole_France = {indice = 8; nom = "Anatole France"; voisins = [7;20]; ligne = "la ligne 3"} and
    Gabriel_Peri = {indice = 9; nom = "Gabriel Péri"; voisins = [2;10]; ligne = "la ligne 13"} and
    Mairie_de_Clichy = {indice = 10; nom = "Mairie de Clichy"; voisins = [9;11]; ligne = "la ligne 13"} and
    G11 = {indice = 11; nom = ""; voisins = [10;21;14]; ligne = "la ligne 13"} and
    W12 = {indice = 12; nom = ""; voisins = [65;17]; ligne = "walk"} and
    Porte_de_Champerret = {indice = 13; nom = "Porte de Champerret"; voisins = [20;19]; ligne = "la ligne 3"} and
    Porte_de_Clichy = {indice = 14; nom = "Porte de Clichy"; voisins = [11;16]; ligne = "changement"} and
    G15 = {indice = 15; nom = ""; voisins = [606;16]; ligne = "le RER C"} and
    G16 = {indice = 16; nom = ""; voisins = [15;14]; ligne = ""} and
    W17 = {indice = 17; nom = ""; voisins = [12;18]; ligne = "walk"} and
    W18 = {indice = 18; nom = ""; voisins = [17;34]; ligne = "walk"} and
    (...)
    et j'en ai 620 que je stocke dans un gros tableau (via deux gros tableaux que je fusionne en un seul, parce que mon implémentation de Caml Light ne me permet pas de définir un tableau à l'aide de plus de 4082 caractères, or comme mes noms sont bêtement lourds, ça monte vite ). Ca donne un truc du genre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    let Paris = [|Unexistant; Les_Courtilles; Les_Agnettes; Carrefour_Pleyel; St_Denis_Porte_de_Paris; Basilique_de_St_Denis; (...)
    Je ne sais pas si ce stockage des données est optimal mais aux vues des objets qui m'ont étés présentés en cours, ça m'a semblé être le cas. (Pour le nom des objets, je reconnais qu'il est bêtement compliqué et je pense que je vais le simplifier)

    Je pourrais faire une fonction qui balaie le tableau à la recherche d'un élément dont le nom serait identique (c'est d'ailleurs comme ça que j'avais fait au début - pour l'indice aussi d'ailleurs : je balayais le tableau en incrémentant un compteur jusqu'à ce que je tombe sur la bonne station - seulement je me suis dit que c'était pas top pour la complexité, et c'est pour ça que je vous posais la question. Ceci dit si je devais faire comme ça ça serait pas un vrai problème puisque je ne dois le faire qu'une fois au début de l'algorithme (ensuite je ne travaille qu'avec les indices des stations - d'où la nécessité de passer de l'indice d'une station à la liste de ces voisins et inversement en temps unitaire)

    utilise ensuite une table associative (par exemple une table de hachage) nom->objet.
    Est-ce que tu pourrais développer un peu ? Je suis débutant et on m'a jamais parlé de ça. Il y a un bon nombre des termes que tu utilise dont j'ai jamais entendu parler en cours d'info (table associative, table de hachage...)

    En tous cas encore merci pour vos réponses rapides les gars, c'est super sympa et en plus ça fait plaisir de voir qu'on peut se faire aider en Caml ! (je pensais que c'était pas utilisé du tout et que j'arriverais jamais à trouver des gens voulant bien m'aider)

  5. #5
    Membre éprouvé
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    832
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 832
    Points : 1 104
    Points
    1 104
    Par défaut
    Il y a plusieurs structures de données qui font des "tables associatives" : c'est un nom général que j'utilise pour parler des structures qui associent des valeurs à d'autres valeurs ("clés"). Un tableau est une table associative dont les clés sont toujours des entiers (de 0 à N-1).

    En Caml, tu as aussi les tables de hachage ( pour Caml Light, http://caml.inria.fr/pub/docs/manual.../node15.8.html ) et les "maps", basées sur des arbres équilibrés ( pour Caml Light, http://caml.inria.fr/pub/docs/manual...node15.10.html ).

    Tu n'as pas vraiment besoin de savoir comment c'est fait dedans. L'idée d'une table de hachage est un peu compliquée, mais en gros on s'arrange pour attribuer un "numéro" à chaque clé, pour pouvoir stocker ça dans des tableaux. On peut avoir des tables de hachages indexées par tout type qu'on sait "hacher" (produire un numéro), typiquement les chaînes de caractères (la façon précise dont on calcule le numéro est compliquée). La complexité est en O(1) (amorti) pour l'accès, l'ajout, la suppression, etc.
    Pour "Map", on utilise des arbres équilibrés, en gros une structure de donnée bien faite qui trie les éléments (donc, un petit peu comme dans un tableau trié, recherche dichotomique en O(log n)), où on peut en plus ajouter et retirer des éléments en O(log n), ce qui en pratique est en général aussi rapide qu'une table de hachage.


    Deux remarques :
    - si tu fais un programme sérieux, Objective Caml peut être plus adapté que Caml Light (le module Scanf que j'ai utilisé dans mon premier post n'existe pas en Caml Light, il faudra parser le nom à la main).
    - si tu utilises des ensembles de données relativement gros, et que tu veux des requêtes un peu puissantes dessus, il vaut mieux utiliser une base de données à part; il y a des interfaces entre Objective Caml et les principales bases de données (SQLite, MySQL, PostgreSQL...).

  6. #6
    Membre habitué
    Profil pro
    Inscrit en
    Avril 2009
    Messages
    128
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2009
    Messages : 128
    Points : 146
    Points
    146
    Par défaut
    Bonjour,

    Personnellement je t'aurais recommandé d'utiliser une liste-associative. C'est une structure de donnée qui offre des performances moindres par rapport aux autres type de table-associative, mais pour des programmes simples ne nécessitant pas de performance particulière c'est plus simple et aussi efficace. Pas besoin d'utiliser un basooka pour tuer une mouche

    ça te donnerait donc quelque chose comme cela :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    let my_assoc_list = [
      ("les Courtilles", Les_Courtilles);
      ("les Agnettes", Les_Agnettes);
      ("Carrefour Pleyel", Carrefour_Pleyel);
      ("St Denis Porte de Paris", St_Denis_Porte_de_Paris);
      (* etc ... *)
    ]
    et pour récupérer un élément à partir de son nom on fait comme ceci avec OCaml (tu traduiras pour caml light) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    let this = List.assoc "les Agnettes" my_assoc_list
    même si la liste en question contient un million d'éléments le temps d'accès d'un élément ne sera pas perceptible pour un humain.

    sinon tu parlais de l'indice des stations, avec les listes-associatives ou les tables de hachage les clés d'accès peuvent très bien être autre chose qu'un string, comme un int ou autre chose.

    Citation Envoyé par drunkskater Voir le message
    Est-ce que tu pourrais développer un peu ? Je suis débutant et on m'a jamais parlé de ça. Il y a un bon nombre des termes que tu utilise dont j'ai jamais entendu parler en cours d'info (table associative, table de hachage...)
    la qualité des article wikipedia est variable selon les domaines, mais dans le domaine de l'informatique les articles sont de très bonne qualité, tu peux t'y référer sans soucis,
    http://fr.wikipedia.org/wiki/Tableau_associatif
    http://fr.wikipedia.org/wiki/Table_de_hachage
    http://fr.wikipedia.org/wiki/Arbre_équilibré

    Citation Envoyé par drunkskater Voir le message
    En tous cas encore merci pour vos réponses rapides les gars, c'est super sympa et en plus ça fait plaisir de voir qu'on peut se faire aider en Caml ! (je pensais que c'était pas utilisé du tout et que j'arriverais jamais à trouver des gens voulant bien m'aider)
    la communauté ocaml (car il n'y a pas de communauté caml light) est effectivement un très petite communauté, mais elle est dynamique et réactive !

  7. #7
    Membre régulier
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Points : 110
    Points
    110
    Par défaut
    re-bonjour !

    Alors tout d'abord merci beaucoup bluestorm et adtunum pour vos réponses ! Je m'excuse de ne pas avoir pu répondre plus tôt, mais bon, qui dit vacances dit pas d'Internet (une excellente chose au demeurant), d'où mon retard.

    @ bluestorm :

    Merci pour toutes ces précisions, je vais essayer de me renseigner un peu plus sur ces "maps", ça a l'air intéressant.

    Sinon mon programme n'est pas sérieux du tout, en fait il s'agit d'un programme qui trouve des itinéraires entre deux stations de métro du réseau parisien en tenant compte de certaines contraintes. Pas de quoi fouetter un chat, ça s'inscrivait comme petite illustration "concrète" dans un "pseudo-TIPE" sur la théorie des graphes (problème du voyageur de commerce, etc...). Le truc est inintéressant au possible vu qu'il s'agit d'une recherche exhaustive, mais ça m'a permis de me familiariser un peu avec le langage Caml. Puis le temps passant j'ai rajouté quelques outils, et là je me dis que maintenant que j'ai modélisé tout le réseau ça serait dommage de ne pas s'en servir, c'est pourquoi je voudrais améliorer un peu le truc, histoire d'en faire un truc utilisable.

    Mais je pense que je vais quand même le traduire en OCaml parce que j'ai des problèmes de compilation avec CamlLight...

    @ adtunum :

    Merci pour tes précisions (et pour tes liens !). Pour la liste-associative, j'ai tendance à ne pas aimer ce genre de truc parce qu'on m'a traumatisé en cours d'info avec le fait que la complexité devait être optimale (même pas le droit d'utiliser des fonctions du genre "list_length" ou des trucs du genre quand ça faciliterait pourtant bien la tâche ), mais c'est vrai que dans mon cas ça conviendrait aussi bien !

    Sinon j'ai un "petit problème" : dans la suite, si l'utilisateur tape "Chatelet" ou "châtelet" au lieu de "Châtelet", ça va planter. Je voudrais éviter ça.

    Je pensais donc faire une fonction qui transforme "Châtelet" et toutes les autres variantes en "chatelet", et qui compare ensuite les chaînes de caractères obtenues.

    Pour ça, je me disais qu'il allait falloir que d'une façon ou d'une autre j'identifie "A" et "à" à "a". J'ai pour ce faire plusieurs idées mais je ne sais pas si elles sont bonnes. je m'était dis que je pourrais par exemple faire un tableau ou une liste du type [("a","a");("A";"a");("à","a");("b","b");("B";"b") ... ], et ensuite on balaie la liste en jusqu'à trouver l'élément correspondant dans le premier élément d'un couple, et à ce moment on renvoit le deuxième élément de ce couple. A moins qu'il n'y ait certaines opérations sur les codes ASCII qui permettent de virer des majuscules ou des accents facilement ?

  8. #8
    Membre éprouvé
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    832
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 832
    Points : 1 104
    Points
    1 104
    Par défaut
    Ta méthode est bonne, à part qu'il vaut mieux utiliser un tableau ou une table associative (accès arbitraire en temps constant ou quasi-constant) qu'une liste associative.

    Honnêtement, je pense que pour le coup, on peut utiliser une "bonne solution" avec une bonne complexité. Passer d'une liste associative à autre chose c'est essentiellement simplissime, il faut changer un peu le code de construction de la structure (sachant qu'on peut facilement écrire un convertisseur liste -> structure), et remplacer les appels à List.assoc par des appels à Hashtbl.get ou Map.get. C'est essentiellement la même interface.

    Je comprends l'intérêt de favoriser la simplicité plutôt que la complexité quand la complexité optimale demande un effort de programmation supplémentaire. Ici, tout est déjà tout cuit, donc ça ne change pas grand chose.


    Edit : j'ai retrouvé le code suivant dans mes caves. Ça pourrait t'intéresser.

    Code ocaml : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    (* liste_filtre renvoie un graphe (une liste de couples) représentant la fonction filtre
       "triviaux" désigne le sous-graphe correspondant à l'indentité ([('a', 'a'); ('b', 'b')...]),
       et à la conversion majuscule_minuscule ([('A', 'a'); ...])
    *)
    let liste_filtre =
      let triviaux =
        let trivial c = [(c, c); (Char.uppercase c, c)] in
        let rec lettres = function
          0 -> ['a']
        | n -> Char.chr (n + Char.code 'a') :: lettres (n - 1)
        in
        List.flatten (List.map trivial (lettres 25))
      in
      let accentue lettre = List.map (fun accent -> (accent, lettre)) in
      let points = List.map (fun point -> (point, '.')) in
      let espaces = List.map (fun espace -> (espace, ' ')) in
      triviaux
      @ accentue 'a' ['á'; 'à'; 'À'; 'â'; 'Â'; 'ä'; 'Ä'; 'Æ']
      @ accentue 'e' ['é'; 'É'; 'è'; 'È'; 'ê'; 'Ê'; 'ë'; 'Ë']
      @ accentue 'i' ['í'; 'Í'; 'î'; 'Î'; 'ï'; 'Ï']
      @ accentue 'o' ['ó'; 'ô'; 'ö'; 'ò'; 'õ'; 'Ô']
      @ accentue 'u' ['ú'; 'ù'; 'û'; 'ü'; 'Ù'; 'Ú'; 'Ü'; 'Û']
      @ accentue 'y' ['ÿ']
      @ accentue 'c' ['ç'; 'Ç']
      @ accentue 'n' ['ñ']
      @ points ['.'; ';'; ':'; '?'; '!'; '¿'; '¡'; ',']
      @ espaces [' '; '-'; '('; ')'; '"'; '\''; '@'; '*'; '\n'; '\t'; '\r';
    	     '«'; '»'; '<'; '>'; '{'; '}'; '_'; '/'; '\\'; '['; ']'; '%'; '&';
    	     '´'; '`'; '^'; 'º'; 'æ'; '°'; '+'; '¨'; '$'; '=';
    	     '0'; '1'; '2'; '3'; '4'; '5'; '6'; '7'; '8'; '9'; '¹'; '²'; '³'
    	    ]
     
    let rec assoc clé = function
      [] -> raise Not_found
    | (c, valeur) :: _ when c = clé -> valeur
    | _ :: queue -> assoc clé queue
     
    (* filtre met en place un cache pour ne pas utiliser la fonction assoc (linéaire) à chaque fois :
       le filtrage est en temps constant *)
    let filtre =
      let cache = Array.init 256
        (fun i ->
           try assoc (Char.chr i) liste_filtre
           with Not_found -> ' ')
      in
      fun c -> cache.(Char.code c)
     
    let traiter_mot mot =
      for i = 0 to String.length mot - 1 do
        mot.[i] <- filtre mot.[i]
      done

  9. #9
    Membre habitué
    Profil pro
    Inscrit en
    Avril 2009
    Messages
    128
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2009
    Messages : 128
    Points : 146
    Points
    146
    Par défaut
    tu pourrais peut-être aussi utiliser la distance de Levenshtein, pour les cas où l'utilisateur ferait une coquille ou bien s'il ne connaît juste pas l'orthographe correcte d'une station
    http://fr.wikipedia.org/wiki/Distance_de_Levenshtein

    @ bluestorm :

    sinon oui c'est vrai qu'utiliser une table de hashage ou un map ne complique pas beaucoup les choses, dans mon poste on a l'impression que je te reprends ce qui n'était pas le cas, si on regarde l'heure j'avais posté plus ou moins simultanément et donc n'avais pas vu le message qui précède auquel cas j'aurais formulé différemment. Donc désolé bluestorm pour avoir donné cette impression !

  10. #10
    Membre habitué
    Profil pro
    Inscrit en
    Avril 2009
    Messages
    128
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2009
    Messages : 128
    Points : 146
    Points
    146
    Par défaut
    autrement ceci peut peut-être être utile :
    http://cristal.inria.fr/~xleroy/software.html#agrep

  11. #11
    Membre régulier
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Points : 110
    Points
    110
    Par défaut
    Merci pour le code ! Je pense que je vais m'en servir, ça m'évitera d'avoir à retrouver toutes les accentuations possibles de chaque caractère
    Bon par contre je n'ai encore jamais fait d'OCaml donc il va falloir que je me familiarise un peu avec la syntaxe...

    Une petite question (naïve) : quelle est l'intérêt de faire la distinction entre "a" et 'a' en Caml ? Je m'excuse si ma question est stupide, mais je dois avouer que je n'ai pas la moindre idée de la réponse. Et si je vous citais mon cours sur le type Char et le type String vous hallucineriez devant sa nullité - en gros c'est un truc du genre :

    Caractères :
    char. On les note avec des côtes. Ex : 'a'.
    Remarque : int_of_char renvoie le code ASCII d'un caractère. La fonction inverse est char_of_int.

    Chaînes de caractères :
    string. On les note avec des guillemets. Ex : "bonjour"
    Remarque : string_length donne la longueur de la chaîne de caractère, et sub_string permet de ne conserver d'une partie de la chaîne. (la numérotation est identique à celle des tableaux.

    Lien entre char et string :
    #let s = "bonjour";;
    s : string = "bonjour"
    #s.[3];;
    - : char = `j`
    [et encore, je crois que sub_string n'était même pas dans mon cours]. Bref, vous en conviendrez, ce n'est pas ça qui va m'aider à comprendre.

    Sinon pour l'instant pour l'instant pour travailler sur les chaînes de caractères, j'ai un genre de pile (une référence sur une chaîne de caractères), et fonction pop et une fonction push basées sur sub_string, et je n'utilise jamais le type "caractère". Est-ce une bonne idée ?

    Un autre truc : pour ce qui est des fonction type "match". On m'a dit que les match s'effectuaient toujours en temps unitaire. Alors qu'il est évident qu'un truc du type :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    let fonction argument =
    if argument = x1 then y1
    else     if argument = x2 then y2
              else     if argument = x3 then y3
                        else     if   (...) ;;
    sera en temps linéaire par rapport au nombre de valeurs possibles d'argument. Donc bien entendu je m'interroge : comme une fonction du type match marche-t-elle ? Vous n'êtes bien entendu pas obligés de rentrer dans les détails (d'autant que je n'y comprendrais sans doute pas grand chose), mais c'est un truc qui me turlupine depuis un petit bout de temps (et une question que mon prof n'a pas jugée assez intéressante pour y répondre).

    Merci aussi pour les liens, et notamment pour la distance de Levenshtein, je n'en avais jamais entendu parler et effectivement je me demandais si un truc du genre existait ! Je pense que je vais essayer de faire un truc du genre pour que par exemple si l'utilisateur tape "Châtlet", l'ordinateur lui demande "Vouliez-vous dire Châtelet ?".

    Une fois encore, je m'excuse pour le côté un peu stupide de certaines de mes questions, mais il faut bien vous dire que bien qu'ayant fait deux trimestre de Caml et ayant pas mal travaillé avec les listes et les tableaux, je n'ai quasiment jamais touché aux autres objets Caml, et je n'ai même jamais compilé de programme Je suis donc en fait un grand débutant qui connait bien la syntaxe de CamlLight et c'est tout

  12. #12
    Membre éprouvé
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    832
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 832
    Points : 1 104
    Points
    1 104
    Par défaut
    adtunum > merci pour la précision
    De toute façon, contrairement à d'autres dans le coin, je ne suis pas ronchon.

    drunkskater >

    Les différences syntaxiques entre Caml Light et Ocaml sont assez petites. Tu en trouveras une liste (peut-être pas exhaustive) sur ce post.


    Au sujet des chaînes et lettres, je ne sais pas trop quoi dire de plus que ton cours. Le type "char" contient une seule lettre, une chaîne contient une suite contiguë de lettres. Comme une ('a list) est une liste d'éléments de type 'a (tu peux d'ailleurs écrire une fonction de conversion de string vers 'a list).

    La chose importante à comprendre c'est qu'une string est implémentée grosso-modo comme un tableau : copier la chaîne est une opération linéaire, tout comme "substring" (linéaire en la taille de la sous-chaîne), et la concaténation des chaines est linéaire en la *somme* des longueurs des deux paramètres.
    Si on est obligé de copier la sous-chaîne (au lieu de juste conserver le décalage dans la grande chaîne), c'est parce que les chaînes sont modifiables (un choix de design assez douteux, qu'on considère maintenant comme une erreur).

    Sinon pour l'instant pour l'instant pour travailler sur les chaînes de caractères, j'ai un genre de pile (une référence sur une chaîne de caractères), et fonction pop et une fonction push basées sur sub_string, et je n'utilise jamais le type "caractère". Est-ce une bonne idée ?
    Non, c'est une très mauvaise idée. Comme je l'ai dit plus haut, ta fonction "pop" est linéaire, donc l'itération de pop jusqu'à l'obtention d'une chaîne vide donne un algorithme quadratique. Comme ça ressemble à la manipulation de listes, la plupart des débutants font ça, mais c'est une mauvaise pratique.

    Tu as deux solutions :

    - Si jamais ta manipulation sur la chaîne s'exprime *vraiment* bien comme une manipulation récursive sur une liste avec les opérations "head / tail", convertit ta chaîne en (char list) avant la manipulation. La conversion est linéaire, et après les head/tail seront en O(1), donc tu évites le coût quadratique. Mais en pratique l'utilisation d'une liste n'est que très rarement le meilleur choix.

    - Recode des algorithmes en passant par paramètre, non pas une chaîne, mais une *position* dans une chaîne. Au lieu de passer la sous-chaîne "tail" dans tes appels récursifs, tu donnes l'indice du premier caractère de cette sous-chaîne. Coût constant (accès en O(1), string_length en O(1)) et se plie très bien à la grande majorité des applications, en particulier permet facilement de passer d'une chaîne à un tableau (utile par exemple pour l'algorithme dynamique de calcul de la distance de Levenshtein).



    Pour ton problème de match / else if : à moins que tu ne fasses de la génération dynamique de code Caml, le nombre de cas de ta conditionnelle est constant. Ce n'est donc pas vraiment un coût "linéaire", et il est inutile (c'est non pertinent et légèrement toxique) de se préoccuper du coût de 3 tests imbriqués. Si tu as plus de 10 tests les uns à la suite des autres, c'est le signe non pas d'un problème de performance, mais d'un problème de conception : factorise ton code pour éliminer ces tests successifs (en manipulation un tableau de cas par exemple).
    L'implémentation de match est optimisée de façon assez complexe^[1]. Tu as le droit de considérer qu'un match "simple", c'est à dire qui se contente de tester différents cas d'un constructeur somme, est en temps constant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    match a with
    | A x -> ..
    | B (y, z) -> ..
    | C ->
    | D v -> ..
    Ces matchs structurels (y compris sur les booléens, les listes (quand tu testes seulement ([] | :, etc.) sont très bien compilés, même quand ils sont imbriqués les uns dans les autres : (A [] -> ... | A (hd::tl) -> ..).

    Quand tu as des "when", tu perds cette propriété vu qu'il est nécessaire de tester toutes les conditions, de haut en bas, pour pouvoir choisir. Entre nous, sauf cas particuliers, je te déconseille la construction "when", j'ai remarqué que chez les débutants elle faisait plus de mal que de bien, en les encourageant à écrire du mauvais code, des filtrages non structurels, à un endroit où une suite de "if .. else if .." sont plus adaptés.

    [1] Si ça intéresse quelqu'un, on peut trouver une introduction à la compilation des filtrages dans ce document. C'est en fait un problème délicat, mais qu'il n'est pas du tout nécessaire de connaître pour utiliser le filtrage : autant il est utile d'avoir une idée du fonctionnement du Garbage Collector quand on s'intéresse aux performances d'un code, autant les connaissances sur le filtrage ne sont, à ma connaissance, jamais vraiment nécessaires, il suffit de savoir qu'il y a des heuristiques et que le compilo fait de son mieux. Si on veut aller encore plus loin, il y a les articles de Luc Maranget.

  13. #13
    Membre régulier
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Points : 110
    Points
    110
    Par défaut
    Non, c'est une très mauvaise idée. Comme je l'ai dit plus haut, ta fonction "pop" est linéaire, donc l'itération de pop jusqu'à l'obtention d'une chaîne vide donne un algorithme quadratique. Comme ça ressemble à la manipulation de listes, la plupart des débutants font ça, mais c'est une mauvaise pratique.
    Ah, oui, effectivement, je n'avais pas bien à l'esprit que sub_string était linéraire par rapport à la taille de la sous-chaîne (pourtant il me semble qu'on avait dû le dire au moment on a présenté la fonction). Bon ben je vais abandonner ça.

    Sinon, oui, je sais bien, j'ai tendance à utiliser trop de piles, je vais essayer de corriger ça

    - Si jamais ta manipulation sur la chaîne s'exprime *vraiment* bien comme une manipulation récursive sur une liste avec les opérations "head / tail", convertit ta chaîne en (char list) avant la manipulation. La conversion est linéaire, et après les head/tail seront en O(1), donc tu évites le coût quadratique. Mais en pratique l'utilisation d'une liste n'est que très rarement le meilleur choix.
    Ca, ok, j'ai compris et je vois comment faire.

    - Recode des algorithmes en passant par paramètre, non pas une chaîne, mais une *position* dans une chaîne. Au lieu de passer la sous-chaîne "tail" dans tes appels récursifs, tu donnes l'indice du premier caractère de cette sous-chaîne. Coût constant (accès en O(1), string_length en O(1)) et se plie très bien à la grande majorité des applications, en particulier permet facilement de passer d'une chaîne à un tableau (utile par exemple pour l'algorithme dynamique de calcul de la distance de Levenshtein).
    Je suis moins sûr d'avoir compris : En gros je balaie mes deux chaînes en vérifiant à chaque fois que chaine1.[i] ~ chaine2.[i] (où ~ est ma relation d'équivalence entre caractères) [et bien entendu si je tombe sur un séparateur, un espace ou un tiret par exemple, j'ignore et je passe au caractère suivant] ?

    Quel est l'avantage par rapport à un truc où on commencerait par transformer "Châtelet - Les Halles" et "chatelet les halles" en deux liste (ici égales à [ 'c' ; 'h' ; 'a'; 't' ; 'e' ; 'l' ; 'e'; 't' ; 'l' ; 'e' ; 's' ; 'h' ; 'a' ; 'l' ; 'l' ; 'e' ; 's' ]) avant de tout simplement vérifier l'égalité entre les deux listes ?

    Sinon grosso modo une structure comme ça :

    - On demande un nom de station de départ sous forme d'une chaîne de caractère. Ensuite, on balaie le tableau des stations en regardant si le nom d'une station correspond (aux accents et caractères spéciaux près).
    * Si c'est le cas, on fait une référence sur la station correspondant
    * Si ce n'est pas le cas, on balaie à nouveau le tableau, mais en regardant si le nom d'une station correspond à une distance de Levenshtein de 1. Si c'est le cas, on demande à l'utilisateur si c'était ce qu'il voulait dire, sinon on peut éventuellement recommencer avec une distance de 2 cette fois.
    * Si après une distance de levenshtein de 3 ou 4 par exemple, aucune station n'a été trouvée, on propose à l'utilisateur d'utiliser la fonction "rechercher" qui permet de rechercher une station par nom ou par ligne.

    - Une fois que la station de départ est validée, on fait pareil avec l'arrivé. Ensuite on demande les options supplémentaires (éviter une station, etc...). Tout ça se stocke dans des références.

    - On finit par lancer le programme à l'aide de toutes les références qui ont été "remplies" précédemment.


    Ca vous semble correcte comme procédure ou est-ce qu'il faut revoir ça ? Parce qu'à l'heure actuelle, si je veux aller de Vavin à Parmentier sans prendre le RER, sans changer à Châtelet et sans passer par Odéon, il faut taper :

    itineraire Vavin Parmentier (sans_changer_a Chatelet (eviter Odeon sans_RER));;

    Ce qui n'est pas très intuitif, sans compter que si on met "vavin" ça plante. vaudrait mieux demander pas à pas la station de départ, la station d'arrivée, les options supplémentaires, etc...

    Pour ton problème de match / else if : à moins que tu ne fasses de la génération dynamique de code Caml, le nombre de cas de ta conditionnelle est constant. Ce n'est donc pas vraiment un coût "linéaire", et il est inutile (c'est non pertinent et légèrement toxique) de se préoccuper du coût de 3 tests imbriqués. Si tu as plus de 10 tests les uns à la suite des autres, c'est le signe non pas d'un problème de performance, mais d'un problème de conception : factorise ton code pour éliminer ces tests successifs (en manipulation un tableau de cas par exemple).
    L'implémentation de match est optimisée de façon assez complexe^[1]. Tu as le droit de considérer qu'un match "simple", c'est à dire qui se contente de tester différents cas d'un constructeur somme, est en temps constant
    Oui, je me suis mal exprimé, je voulais dire qu'en gros c'était proportionnel au nombre de tests. Le truc c'est que je me demande comment marchent les match ou même les appels à un objets : Si je défini mettons un milliards d'objets, A1, ... , A1000000000, le temps d'appel à Ax va forcément être plus long que si j'avais 2 objets seulement, non ? M'enfin bon, je suppose que ça doit être un peu compliqué donc je laisse tomber, je reviendrai dessus (beaucoup) plus tard.

    Et pour génération dynamique de code Caml, justement : C'est possible ? Je voit absolument pas comment faire (cf mes premiers posts), or ça m'aurait été utile dans certains cas (pas dans ce programme).

    Deux dernières petites questions :

    - Pour l'instant, pour faire de "l'interactif" dans Caml Light, j'utilise read_line. Est-ce qu'il n'y a pas un truc mieux/plus adapté dans OCaml ?

    - Une fois le programme compilé, comment se présentera-t-il ? Je vous demande ça parce que pour l'instant je n'arrive même pas à compiler un simple "hello world" en Caml Light, il y a un bug qui m'échappe. Donc ce que je fais c'est que le lance Caml dans mon terminal, puis je fais include "nom_du_programme";; Mais sur Windows c'est déjà un peu plus chiant : soit il faut passer par WinCaml (beurk), soit il faut aller chercher l'invite de commande. Est-ce qu'il y aurait moyen en OCaml de compiler le programme en code machine de façon à ce que quelqu'un qui n'ait pas OCaml ait juste à cliquer sur une icône et que le programme s'ouvre dans l'invite de commande (ou n'importe ou, dans une fenêtre indépendante).

    Je vais reprendre mon programme et je vous dirait ce que ça donne quand j'aurai à nouveau accès à Internet (d'ici une quinzaine de jours normalement), en attendant je vais télécharger OCaml et voir si j'arrive à compiler des "Hello World"

    Et encore merci pour votre aide !

  14. #14
    Membre régulier
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Points : 110
    Points
    110
    Par défaut
    Bonjour !

    Je viens de rentrer de vacances, et j'ai trouvé un peu de temps pour commencer à réécrire mon programme. J'ai entre autre :

    - Supprimé la double propagation : Avant la recherche d'itinéraire se faisait par double propagation - je ne suis pas trop sûr de mes calculs de complexité mais j'ai fait des tests et cela diminuait effectivement le temps de calcul, mais de très peu. Comme de plus ça rendait plus difficile la lecture du programme et surtout que ça ne permettait pas d'avoir un graphe orienté (Or le réseau de métro parisien n'est pas "bidirectionnel" cf les quelques fourches et autres boucles), je l'ai supprimée.

    - Tout réécrit en fonctionnel : Avant tout était impératif avec des références sur des listes qui me servaient de pile. J'ai décidé d'écrire des fonctions récursives terminales parce qu'il parait que c'est mieux.

    - Traduit en Ocaml : Ca va y a pas eu trop de boulot


    J'ai mis le code en pièce jointe HTML (comme ça c'est coloré c'est plus agréable à lire). Mes questions sont donc :

    Mon code est-il lisible ? Par exemple, faut-il que j'explicite le nom des arguments plutôt que de me contenter de mettre l quand il s'agit d'une liste, v pour un tableau et s pour une station ?

    Les fonctions sont-elles bien programmées, ou y a-t-il moyen de les améliorer, par exemple en utilisant des fonctions déjà compilées ? Je pense notamment à la fonction move : il y a moyen de la programmer plus simplement avec du List.flatten, mais je ne crois pas que ça améliore la complexité (au contraire même il me semble). Et puis est-il judicieux de redéfinir localement la fonction aux à chaque fois, ou ne vaudrait-il pas mieux que j'en fasse une fonction indépendante prenant deux arguments en plus mais définie globalement ?

    Voilà, toutes les remarques sont les bienvenues, n'hésitez pas non plus à choisir mes choix de syntaxe et d'indentation (oui j'aimerais bien trouver des règles d'indentation bien histoire de m'y tenir)

    Bon après il reste toutes les options supplémentaires à traduire, la modélisation du réseau à modifier un peu et l'interface graphique à refaire
    Fichiers attachés Fichiers attachés

  15. #15
    Membre régulier
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Points : 110
    Points
    110
    Par défaut
    PS : Ah, je n'ai pas encore mis de commentaires dans le code.

    Alors en gros l'algorithme est simple. Une station possède un indice et une liste de voisins ( = liste des indices des stations adjacentes ).

    Pour trouver l'itinéraire, part de la station de départ (bonne idée) - On vérifie ensuite si la station d'arrivée a été atteinte. Le cas échéant, on arrête et on renvoie la liste des trajets sous la forme int list list (un trajet étant liste d'indice de stations), sinon on commence par effacer les stations déjà atteintes du réseau (histoire de ne pas y repasser), puis on se déplace dans toutes les stations adjacentes aux stations déjà atteintes. On vérifie, etc.

    check est la fonction qui permet de vérifier que la station d'arrivée n'a pas été atteinte (on cherche son indice dans la tête de chacune des listes composant la liste de trajets)

    move permet "prolonger le trajet d'un déplacement élémentaire".

    dep est en fait identique à move, si ce n'est qu'on commence par supprimer les stations déjà explorées pour ne pas y repasser (via la fonction del qui supprime un élément d'une liste)

    path est la fonction qui harmonise tout ça : j'applique check. Si ce n'est pas bon (c'est-à-dire qu'elle renvoie la liste vide), je me déplace avec dep, puis je check, etc. On en profite pour incrémenter un compteur.

    C'est assez simple mais compte tenu de la taille du réseau parisien ça marche bien. C'est moins rapide qu'avec la double propagation mais sur mon ordi qui est somme toute assez "moyen" ça prend généralement moins d'une seconde, donc tant pis...

  16. #16
    Membre éprouvé
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    832
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 832
    Points : 1 104
    Points
    1 104
    Par défaut
    - Tout réécrit en fonctionnel : Avant tout était impératif avec des références sur des listes qui me servaient de pile. J'ai décidé d'écrire des fonctions récursives terminales parce qu'il parait que c'est mieux.
    Non, ce n'est pas mieux en général. C'est mieux quand ça te permet d'écrire ton programme plus clairement, mais quand ce n'est pas le cas les boucles sont une bonne idée. Par exemple, pour itérer de 1 à N en effectuant une action à chaque fois, une boucle est plus claire qu'une fonction récursive (c'est pour ça qu'on utilise plus de boucles avec les tableaux; au contraire, la structure récursive des listes se prête bien à la récursivité; mais dans les deux sens, il y a des cas particuliers où ça change).

    Mon code est-il lisible ? Par exemple, faut-il que j'explicite le nom des arguments plutôt que de me contenter de mettre l quand il s'agit d'une liste, v pour un tableau et s pour une station ?
    Non, en effet ton code n'est pas très lisible. Je n'ai pas encore lu ton dernier post qui donne des indications, et j'ai du mal à comprendre ce que fait "move". Comme tu le suggères, donner des noms parlants aux variables est une bonne idée. Si tu suis une convention concise (s pour station, etc.), c'est bien aussi (tu n'es pas obligé d'utiliser toujours station_de_métro, des lignes de code très longues peuvent aussi nuire une fois à la lisibilité), mais il faut qu'à un endroit au moins le nom complet apparaisse. Dans les commentaires pourquoi pas, mais la méthode que je préfère et de l'utiliser dans du vrai code.

    Du reste, les indications de "sens" sont plus utiles que les indications de "structure". Je n'ai pas besoin que le nom d'une variable m'aide à deviner s'il s'agit d'une liste ou d'un tableau, ça se voit. Par contre, savoir si c'est une station, un trajet, un ensemble de trajets, etc., ça c'est utile.
    On utilise des noms "impersonnels" (liste, tableau, i, j, k, n, etc.) quand justement les variables n'ont pas de signification dans ton domaine d'application (ici une forme de géographie), qu'elles n'ont pas de contexte particulier. Quand tu peux utiliser une métaphore qui aide tes lecteurs à comprendre le code (ici station, ligne, trajet etc.), il faut en profiter.


    Un autre conseil, c'est de ne pas recoder les fonctions de la bibliothèque standard. Si tu utilises List.filter, tout le monde comprend ce que tu fais. Si tu recodes ta propre fonction qui ressemble très fort à List.filter, on ne sait pas si c'est la même chose, ou s'il y a une différence subtile que tu utilises dans ton code (par exemple certaines de tes fonctions tail-rec renversent leur accumulateur à la fin, d'autres non... est-ce que l'ordre des résultats est important ?).
    Tes deux premières fonctions, check et del, sont des instances de List.filter. La première est (let check l n = List.rev (List.filter (fun h -> List.hd h = n) l)), la deuxième (let del x l = List.filter (fun h -> h <> x) l).

    Enfin, une dernière remarque de la part de quelqu'un qui ne s'est pas encore assez plongé dans le code pour le comprendre : il y a une forme raccourcie de "match .. with" qui est le "function" : "function ..." équivaut à "fun x -> match x with ...". Pour cette raison, on met traditionnellement l'argument sur lequel on veut faire un filtrage de motif en dernier. Dans ton cas, tu as en général deux paramètres, la liste sur laquelle tu récures et un accumulateur pour l'aspect tail-rec. Tu pourrais mettre l'accumulateur en premier et utiliser "function".
    Ceci dit, la question du choix de l'ordre des paramètres est assez délicate et il n'y a pas pas toujours de "bonnes" solution. Ce n'est pas du tout une science exacte.

  17. #17
    Membre éprouvé
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    832
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 832
    Points : 1 104
    Points
    1 104
    Par défaut
    Une autre remarque sur le nom des variables : quand tu as deux variables de natures très différentes, évite de leur donner des noms proches. Dans "dep" tu as quatre variable nommées l1, l2, l3, l4, donc elles sont essentiellement indiscernables pour le lecteur. Pourtant, elles n'ont même pas le même type, puisque tu écris (h2 :: h1) (note : le fait de suivre une convention "l -> h::t" est par contre une bonne idée).

    Il y a aussi quelque chose de curieux dans ton code, lié à l'utilisation régulière de List.hd. En général, pour explorer des listes, on utilise plutôt le filtrage, pour la bonne raison que ça force à considérer aussi le cas où la liste est vide. Si tu utilises List.hd, tu envoies le message (au lecteur du code) que tu sais, pour une raison ou une autre, que la liste ne sera jamais vide. Pourquoi ? À quoi correspondent ces listes ? La liste est-elle bien la structure de donnée adaptée dans ce cas ? Est-ce que le premier élément de la liste a un rôle différent des éléments suivants ?
    Autant de questions auxquels il faudrait, pour rendre le code plus lisible, répondre, soit sous forme de commentaire soit (encore mieux) sous forme de code (un commentaire, ça vieillit et ça devient faux, le code ne ment jamais sur ce que fait le programme, même si ce n'est pas toujours ce que le codeur voulait).

  18. #18
    Membre régulier
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Points : 110
    Points
    110
    Par défaut
    Citation Envoyé par bluestorm Voir le message
    Non, ce n'est pas mieux en général. C'est mieux quand ça te permet d'écrire ton programme plus clairement, mais quand ce n'est pas le cas les boucles sont une bonne idée. Par exemple, pour itérer de 1 à N en effectuant une action à chaque fois, une boucle est plus claire qu'une fonction récursive (c'est pour ça qu'on utilise plus de boucles avec les tableaux; au contraire, la structure récursive des listes se prête bien à la récursivité; mais dans les deux sens, il y a des cas particuliers où ça change).
    Oui, j'avais mis "Il parait que c'est mieux" par provocation parce qu'au début, étant donné que j'avais beaucoup de mal à comprendre les programmes récursifs terminaux "avec accumulateur" alors que je comprenais bien ceux impératif avec des piles je ne voyais absolument pas l'intérêt. Maintenant je commence à le voir un peu même si dans le cas de ma fonction "move" par exemple je comprenais beaucoup mieux avec les manipulation sur les piles.

    Non, en effet ton code n'est pas très lisible. Je n'ai pas encore lu ton dernier post qui donne des indications, et j'ai du mal à comprendre ce que fait "move". Comme tu le suggères, donner des noms parlants aux variables est une bonne idée. Si tu suis une convention concise (s pour station, etc.), c'est bien aussi (tu n'es pas obligé d'utiliser toujours station_de_métro, des lignes de code très longues peuvent aussi nuire une fois à la lisibilité), mais il faut qu'à un endroit au moins le nom complet apparaisse. Dans les commentaires pourquoi pas, mais la méthode que je préfère et de l'utiliser dans du vrai code.
    Argh, c'est ce que je craignais. Bon ben je vais remettre ça en commençant à donner des noms plus explicites à mes arguments - le problème ça va être de les choisir

    Du reste, les indications de "sens" sont plus utiles que les indications de "structure". Je n'ai pas besoin que le nom d'une variable m'aide à deviner s'il s'agit d'une liste ou d'un tableau, ça se voit. Par contre, savoir si c'est une station, un trajet, un ensemble de trajets, etc., ça c'est utile.On utilise des noms "impersonnels" (liste, tableau, i, j, k, n, etc.) quand justement les variables n'ont pas de signification dans ton domaine d'application (ici une forme de géographie), qu'elles n'ont pas de contexte particulier. Quand tu peux utiliser une métaphore qui aide tes lecteurs à comprendre le code (ici station, ligne, trajet etc.), il faut en profiter.
    Oui, ça va de soit effectivement, mais j'ai été habitué à mettre v dès qu'il y avait un tableau, l dès qu'il y avait une liste et n dès qu'il y avait un entier alors j'ai un peu gardé ces mauvaises habitudes...

    Un autre conseil, c'est de ne pas recoder les fonctions de la bibliothèque standard. Si tu utilises List.filter, tout le monde comprend ce que tu fais. Si tu recodes ta propre fonction qui ressemble très fort à List.filter, on ne sait pas si c'est la même chose, ou s'il y a une différence subtile que tu utilises dans ton code
    Arf, pourant j'y ai fait gaffe ! M'enfin à ma décharge il faut dire ces fonctions n'existent pas en Caml Light et que j'ai traduit laborieusement en OCaml - manuel "à la main". D'ailleurs le choc quand j'ai vu qu'on ne pouvait pas faire "hd l" ou tl l pour obtenir la tête ou la queue de la liste l !
    Bon en tous cas il va falloir que je relise le manuel pour voir ce qui existe déjà...

    (par exemple certaines de tes fonctions tail-rec renversent leur accumulateur à la fin, d'autres non... est-ce que l'ordre des résultats est important ?)
    Très juste, ne m'en était rendu compte mais non, ce n'est pas très grave. L'ordre des "trajets" n'a pas d'importance puisqu'ils sont tous à priori aussi bons et qu'il seront par la suite triés de façon à ce que ceux nécessitant le moins de changements et de correspondances à pied sortent en premiers. Mais pas de problème, j'ai vu qu'il existait un module List.sort donc je penserai à l'utiliser.

    Bon ensuite il y a un petit problème que je n'avais pas, c'est que les trajets sont "à l'envers", dans ma version impératives ils étaient écris dans l'autre sens, mais bon, c'est pas très grave, je mettrai un miroir en temps voulu.

    Tes deux premières fonctions, check et del, sont des instances de List.filter. La première est (let check l n = List.rev (List.filter (fun h -> List.hd h = n) l)), la deuxième (let del x l = List.filter (fun h -> h <> x) l).
    Ahh, ok, merci beaucoup ! Par contre j'avoue que je ne suis pas très familier avec les écritures du genre "fun h -> h <> x", faut que je revoie ça.

    Enfin, une dernière remarque de la part de quelqu'un qui ne s'est pas encore assez plongé dans le code pour le comprendre : il y a une forme raccourcie de "match .. with" qui est le "function" : "function ..." équivaut à "fun x -> match x with ...". Pour cette raison, on met traditionnellement l'argument sur lequel on veut faire un filtrage de motif en dernier. Dans ton cas, tu as en général deux paramètres, la liste sur laquelle tu récures et un accumulateur pour l'aspect tail-rec. Tu pourrais mettre l'accumulateur en premier et utiliser "function".
    Ceci dit, la question du choix de l'ordre des paramètres est assez délicate et il n'y a pas pas toujours de "bonnes" solution. Ce n'est pas du tout une science exacte.
    Et encore merci, j'ignorais totalement et je m'étais justement dit que c'était pas mal de mettre systématiquement l'accumulateur en dernier

    Une autre remarque sur le nom des variables : quand tu as deux variables de natures très différentes, évite de leur donner des noms proches. Dans "dep" tu as quatre variable nommées l1, l2, l3, l4, donc elles sont essentiellement indiscernables pour le lecteur. Pourtant, elles n'ont même pas le même type, puisque tu écris (h2 :: h1) (note : le fait de suivre une convention "l -> h::t" est par contre une bonne idée).
    Ok, je vais essayer de changer tout ça de toutes façons...

    Il y a aussi quelque chose de curieux dans ton code, lié à l'utilisation régulière de List.hd. En général, pour explorer des listes, on utilise plutôt le filtrage, pour la bonne raison que ça force à considérer aussi le cas où la liste est vide. Si tu utilises List.hd, tu envoies le message (au lecteur du code) que tu sais, pour une raison ou une autre, que la liste ne sera jamais vide. Pourquoi ? À quoi correspondent ces listes ? La liste est-elle bien la structure de donnée adaptée dans ce cas ? Est-ce que le premier élément de la liste a un rôle différent des éléments suivants ?
    Autant de questions auxquels il faudrait, pour rendre le code plus lisible, répondre, soit sous forme de commentaire soit (encore mieux) sous forme de code (un commentaire, ça vieillit et ça devient faux, le code ne ment jamais sur ce que fait le programme, même si ce n'est pas toujours ce que le codeur voulait).
    En fait cette utilisation régulière de List.hd vient du fait que je l'ai traduit depuis le hd de Caml Light. Par contre, effectivement, les listes ne seront jamais vides (on part d'une int list list non vide qui ne fait que grossir au cours de l'algorithme ), donc je ne vois pas trop le problème.

    J'avoue que j'ai été un peu léger sur la description du fonctionnement de l'algorithme et que donc ça doit paraître un peu obscure. Je détaille ça tout de suite !

  19. #19
    Membre régulier
    Étudiant
    Inscrit en
    Juillet 2010
    Messages
    102
    Détails du profil
    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juillet 2010
    Messages : 102
    Points : 110
    Points
    110
    Par défaut
    Description rapide de l'algorithme. Je risque de me répéter un peu et de pas être très clair, dans ce cas dites-le moi, je réexpliquerai en détail !

    Le réseau de métro parisien est un graphe dont les sommets sont les stations. - A noter qu'il s'agit d'un graphe orienté, mais cela n'est pas important pour le moment.

    Pour modéliser ce graphe j'ai choisi de faire correspondre à chaque station plusieurs éléments (d'où l'utilisation d'un enregistrement), dont :
    - Un indice, qui correspondra à la place de cette station dans le tableau qui correspond au réseau
    - Une liste de voisins, c'est-à-dire de stations où l'on peut se rendre en un déplacement élémentaire (La liste ne contient pas ces stations mais seulement leurs indices)
    Ce choix est discutable, mais comme j'ai modélisé tout le réseaux, je pense qu'il est un peu tard pour revenir dessus (mais rien n'est impossible)

    On passe donc facilement d'un indice à une station, d'une station à un indice ou encore d'un indice à une liste de voisins.

    Le but du programme est - dans un premier temps - de trouver des trajets minimaux en nombre de déplacements élémentaires pour se rendre d'une station à une autre. Pour cela, on utilise un algorithme simple : on explore le graphe, par "séries de un déplacement élémentaire" jusqu'à tomber sur la bonne station. On a alors un ou plusieurs trajets optimaux en terme de déplacement élémentaires.

    Pour éviter de revenir bêtement sur nos pas, supprime une station du réseau lorsqu'on l'a explorée (en fait, on la supprime juste avant de l'explorer)

    Pour ce qui est de la mise en oeuvre de l'algorithme :

    Un trajet est une liste d'indices de stations, donc une int list. Les différents trajets possible seront donc stockés dans une int list list.

    Au début de l'algorithme, on commence par transformer la première station en une liste d'un trajet : par exemple, pour la station d'indice i on forme [[i]] : A ce stade, un seul trajet, celui qui parcourt la station d'indice i. On regarde si la station d'arrivée est atteinte (auquel cas elle est identique à celle de départ). Si ce n'est pas le cas, on effectue une "prolonge le trajet d'un déplacement élémentaire dans toutes les directions possibles", c'est-à-dire qu'on va transformer cette liste de trajets en une nouvelle liste contenant tous les trajets de coût un depuis la station de départ. Pour cela, il faut regarder les voisins de la station d'indice i. S'il sont donnés par [a;b;c], alors la liste de trajet possible sera [[a; i]; [b; i]; [c; i]]. On regarde ensuite à nouveau si la station d'arrivée à été atteinte : il suffit de regarder si son indice appartient à au moins une liste représentant les trajets : c'est le boulot de check. D'où l'utilisation de List.hd (on n'utilise que la tête, et la liste, ne l'étant pas au départ et ne faisant que s'aggrandir, ne peut être vide). On itère le processus jusqu'à tomber sur la station d'arrivée, et on renvoie alors seulement les trajets qui ont permis d'y arriver, c'est-à-dire les listes dont la tête est l'indice de cette station.

    Je n'ai pas parlé de la suppression des stations. C'est tout bête : pour ne pas repasser par une station, on la "supprime", ou plutôt on l'isole : on va en quelque sorte supprimer les chemins qui y mènent en la supprimant de la liste des voisins des autres stations : il sera ainsi impossible d'y revenir ! En revanche, comme on n'a pas touché à la liste de SES voisins, on peut tout de même en partir, et la suppression peut donc être faite avant le déplacement. En gros, sur un graphe orienté ( --><-- ) le fait de supprimer un seul sens de parcours ( ---<-- ) suffit, donc on s'en contente.

    En même temps, on incrémente un compteur qui représente le nombre de déplacements élémentaires. En fait comme j'ai après une fonction qui "interpréte" les trajets", il est inutile et je vais sans doute le virer pour alléger un peu tout ça (il faudra par contre le remettre autre part du coup).

    L'algorithme étant décrit, tout va de soit :

    check
    vérifie si la station a été atteinte en cherchant la présence dans la tête d'une des listes au moins de l'indice de la station d'arrivée

    del
    servira d'auxiliaire pour isoler les stations en supprimant leur indice dans la liste des voisins de chaque stations.

    move
    sert à former des nouveaux trajets en prolongeant les anciens d'un déplacement élémentaire en ajoutant chaque indice de la liste de voisins de chacune des dernières stations atteintes (= tête des listes)

    dep
    , c'est simplement move en prenant la précaution d'isoler la station au préalable.

    path
    c'est juste ce qui harmonise : check, puis dep, puis check, puis dep, jusqu'à ce qu'on tombe sur la bonne station.

    Voilà, j'espère que j'ai été un peu plus clair. J'ai l'impression de m'être pas mal répété et de ne pas avoir apporté grand chose à ce que j'ai déjà dit, donc si c'est le cas je m'en excuse. En même temps je vois pas trop ce qu'il y a à dire de plus que ce que j'ai dit là...

  20. #20
    Membre éprouvé
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    832
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 832
    Points : 1 104
    Points
    1 104
    Par défaut
    Oui effectivement c'est clair, et peut-être un peu trop. Je dois avouer que de toute façon, j'avais fini par comprendre ton code (en le réécrivant fonction par fonction en utilisant la bibliothèque standard). Je pourrais te donner ma version, mais je pense qu'il serait plus formateur que tu fasses le nettoyage toi-même.

    Si ça t'intéresse, j'ai utilisé List.filter, List.concat et List.map.


    En fait, ton algo fait ce qu'on appelle un "parcours en largeur", et que tu as peut-être déjà vu en cours. Il le fait d'une manière assez lourde : dans un parcours classique sur les graphes, on associe à chaque noeud une information binaire ou ternaire (déja visité ou pas ? est-ce que je n'ai jamais visité, je suis en cours de visite ou j'ai déjà visité ?), qu'on nomme classiquement "couleur" (blanc/noir ou blanc/gris/noir). On associe donc une couleur à chaque noeud, par le biais d'un tableau par exemple, et avant de passer par un noeud on vérifie que sa couleur convient (si je suis déjà passé par là, j'annule).

    Dans ton algorithme, tu fais ça de façon assez compliquée puisqu'au lieu de changer un simple booléen pour chaque noeud, tu itères sur la structure du graphe en la modifiant pour "isoler", comme tu dis, les noeuds concernés. C'est lourd à coder (et en plus nettement plus lent, linéaire au lieu de temps constant, mais ça n'est pas le plus important ici) pour pas grand chose.

    Bref, en fait tu as recodé (et peut-être redécouvert) un algo déjà connu, avec une approche visuelle mais pas forcément la plus efficace. Ça me semble très raisonnable et si tu arrives à produire un code vraiment lisible pour ce que tu fais ce sera déjà une belle réussite, mais si en plus tu veux optimiser la rapidité, si ça t'intéresse, le BFS (bread first search) classique utilise :
    - plutôt qu'un filtrage des arrêtes, une table de couleurs (par exemple un tableau de booléens) pour les sommets déjà visités
    - plutôt qu'un ensemble de listes qu'on fait toutes grandir de 1 à chaque itération, une file des "prochaines sommets à visiter" que l'on parcourt un par un, mais par ordre de distance au point de départ

Discussions similaires

  1. Problème avec une chaîne de caractère en argument de fonction
    Par R3v3n4nt dans le forum Interfaces Graphiques
    Réponses: 0
    Dernier message: 09/03/2013, 13h10
  2. Réponses: 8
    Dernier message: 12/02/2013, 01h08
  3. [PowerShell] Récupérer une chaîne de caractères dans un objet WMI
    Par Zipper963 dans le forum Scripts/Batch
    Réponses: 5
    Dernier message: 15/01/2013, 10h00
  4. Réponses: 3
    Dernier message: 12/12/2008, 10h47
  5. Réponses: 5
    Dernier message: 14/06/2008, 23h06

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo