Premiers pas en OCaml/Filtrage de motif

Leçons de niveau 14
Une page de Wikiversité, la communauté pédagogique libre.
Début de la boite de navigation du chapitre
Filtrage de motif
Icône de la faculté
Chapitre no 8
Leçon : Premiers pas en OCaml
Chap. préc. :Structures de données
Chap. suiv. :Manipulation sur les listes
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Premiers pas en OCaml : Filtrage de motif
Premiers pas en OCaml/Filtrage de motif
 », n'a pu être restituée correctement ci-dessus.


Le filtrage de motif permet soit de gérer différents cas en fonction des valeurs d'une expression, soit d'accéder aux éléments d'un type construit (ou les deux à la fois).

Principe général[modifier | modifier le wikicode]

Le filtrage de motif s'effectue avec les mots clefs match et with encadrant l’expression que l’on souhaite manipuler, suivi d'un ou plusieurs motifs, et à chaque motif est associée une expression à retourner.

Filtrage de motif sur les entiers[modifier | modifier le wikicode]

En fonction de la valeur de l’expression Random.int 6 différentes chaînes de caractère peuvent être retournées :

# match Random.int 6 with
  | 0 -> "chou-fleur"
  | 1 -> "cerise"
  | 2 -> "chocolat"
  | _ -> "poivre"
  ;;
- : string = "chou-fleur"

Le caractère souligné à la fin est un joker qui récupère toutes les valeurs possibles qui n'ont pas été listées avant lui.

Filtrage de motif sur les listes[modifier | modifier le wikicode]

Déconstruction[modifier | modifier le wikicode]

Le filtrage de motif est le moyen privilégié pour manipuler les listes.

On peut alors accéder à un ou plusieurs éléments du côté de la tête, séparé(s) de la queue de la liste, et gérer le cas où la liste est vide.

# match [1; 2; 3; 4; 5] with
  | head :: tail -> tail
  | [] -> []
  ;;
- : int list = [2; 3; 4; 5]

Ici on renvoie la queue de la liste au cas où la liste en entrée n’est pas vide, et on renvoie une liste vide si la liste en entrée est vide elle aussi.

Il est possible de déconstruire plusieurs éléments de la tête de la liste :

# match [1; 2; 3; 4; 5; 6] with
  | h1 :: h2 :: h3 :: tail -> h1 + h2 + h3
  | h1 :: h2 :: tail -> h1 + h2
  | h1 :: tail -> h1
  | [] -> 0
  ;;
- : int = 6
# match [1; 2] with
  | h1 :: h2 :: h3 :: tail -> h1 + h2 + h3
  | h1 :: h2 :: tail -> h1 + h2
  | h1 :: tail -> h1
  | [] -> 0
  ;;
- : int = 3

Définissons ce filtrage de motif dans une fonction pour tester plusieurs valeurs d'entrée :

# let sum_head_until_3 lst =
    match lst with
    | h1 :: h2 :: h3 :: tail -> h1 + h2 + h3
    | h1 :: h2 :: tail -> h1 + h2
    | h1 :: tail -> h1
    | [] -> 0
  ;;
val sum_head_until_3 : int list -> int = <fun>
# sum_head_until_3 [1; 2; 3; 4; 5] ;;
- : int = 6

# sum_head_until_3 [5; 6] ;;
- : int = 11

# sum_head_until_3 [15] ;;
- : int = 15

# sum_head_until_3 [] ;;
- : int = 0

L'ordre dans lequel les motifs sont placés est parfois significatif, c’est le cas ici.

OCaml parcourt les motifs potentiels du premier au dernier dans l'ordre. C'est donc le premier motif qui peut correspondre à la valeur de l’expression d'entrée qui sera sélectionné, et c’est l’expression correspondant à ce motif qui sera retournée.

Voyons ce qui se passe avec les mêmes motifs spécifiés dans l’ordre inverse :

# match [1; 2; 3; 4; 5; 6] with
  | [] -> 0
  | h1 :: tail -> h1
  | h1 :: h2 :: tail -> h1 + h2
  | h1 :: h2 :: h3 :: tail -> h1 + h2 + h3
  ;;
Warning 11: this match case is unused.
Warning 11: this match case is unused.
- : int = 1

Ici le deuxième motif récupérera tous les éléments qui auraient pu correspondre pour le deuxième ou le troisième motif. En conséquence le deuxième et le troisième motif ne seront jamais utilisés. C'est pourquoi OCaml nous indique deux avertissements à ce sujet.

Filtrage des valeurs possibles[modifier | modifier le wikicode]

Le filtrage de motif peut aussi servir à traiter différentes valeurs possibles de l’expression d'entrée.

# let head_is lst =
    match lst with
    | 1 :: tail -> "head is one"
    | 2 :: tail -> "head is two"
    | 3 :: tail -> "head is three"
    | head :: tail -> "head = " ^ (string_of_int head)
    | [] -> "list is empty"
  ;;
val head_is : int list -> string = <fun>
# head_is [1; 10; 20; 30] ;;
- : string = "head is one"

# head_is [2; 40; 50; 60] ;;
- : string = "head is two"

# head_is [70; 80; 90] ;;
- : string = "head = 70"

# head_is [] ;;
- : string = "list is empty"

Filtrage de motif sur les tableaux[modifier | modifier le wikicode]

Le filtrage de motif est rarement utilisé sur les tableaux. D'une part cela requiert de connaître sa taille ou ses tailles possibles, et d’autre part cela n’est pas très idiomatique.

Voici cependant un exemple où le filtrage de motif s'applique sur le tableau Sys.argv ce qui permet de traiter différentes valeurs possibles de la ligne de commande avec lesquelles le script est appelé :

Copiez dans un fichier printsys.ml :

let () =
  match Sys.argv with
  | [| _; "--os-type" |] -> print_endline Sys.os_type
  | [| _; "--word-size" |] -> print_int Sys.word_size; print_newline ()
  | _ ->
      prerr_endline ("Usage:\n" ^ Sys.argv.(0) ^
        " ( --os-type | --word-size )")

Appelez ce script avec l'interpréteur ocaml :

$ ocaml printsys.ml
Usage:
printsys.ml ( --os-type | --word-size )

$ ocaml printsys.ml --os-type
Unix

$ ocaml printsys.ml --word-size
32

$ ocaml printsys.ml --banane
Usage:
printsys.ml ( --os-type | --word-size )

Filtrage de motif sur les tuples[modifier | modifier le wikicode]

Voici un exemple :

# match (1, 2, 3) with
  | (1, b, c) -> b + c
  | (a, 2, c) -> a + c
  | (a, b, 3) -> a + b
  | _ -> 0
  ;;
- : int = 5

Le filtrage peut contenir des valeurs données qui feront sélectionner une entrée donnée, et des variables qui récupéreront les autres valeurs utilisées dans le code qui suit cette entrée. Ici la première entrée est sélectionnée car les entrées sont testées dans l'ordre.

Filtrage de motif sur les variants[modifier | modifier le wikicode]

Reprenons le type my_variant défini à la page précédente :

type my_variant = Alpha | Beta | Gamma | Delta

Le filtrage de motif de ce variant se fera comme suit :

# let v_to_string v =
    match v with
    | Alpha -> "Alpha"
    | Beta -> "Beta"
    | Gamma -> "Gamma"
    | Delta -> "Delta"
  ;;
val v_to_string : my_variant -> string = <fun>

# v_to_string Beta ;;
- : string = "Beta"

Filtrage de motif sur les enregistrements[modifier | modifier le wikicode]

Reprenons le type vector défini à la page précédente, définissons une variable de ce type et réalisons un filtrage avec :

# type vector = {
    x: float;
    y: float;
    z: float;
  } ;;        
type vector = { x : float; y : float; z : float; }

# let v = { x = 1.0; y = 2.0; z = 3.0 } ;;
val v : vector = {x = 1.; y = 2.; z = 3.}

# match v with { x; y; z } -> x +. y +. z ;;
- : float = 6.

Il est possible de ne filtrer qu'un des éléments de l'enregistrement :

# match v with { x } -> x ;;
- : float = 1.

Il est également possible de filtrer pour une valeur particulière d'un des champs de l'enregistrement :

# match v with { x = 1.0; y; z } -> (y, z) | _ -> (0.0, 0.0) ;;
- : float * float = (2., 3.)