Quelques exercices de programmation

Table des matières

Extremum d’une liste   liste rec

Énoncé

Implémenter deux fonctions min_lst et max_lst dont l’appel sur une liste non vide s’évalue en le min et le max des éléments de cette liste. Sur une liste vide, une exception sera levée. Par exemple min_list [0;1;2;3] s’évalue en 0.

Corrigé   corrige

let rec min_lst lst =
  match lst with
  |[] -> failwith "Empty list"
  |[x] -> x
  |head::tail -> min head (min_lst tail)
let rec max_lst lst =
  match lst with
  |[] -> failwith "Empty list"
  |[x] -> x
  |head::tail -> max head (max_lst tail)

Itérer sur les tableaux   tableau impératif func

Énoncé

Implémenter une fonction array_iter prenant en entier une fonction à effet de bord ('a -> unit' ) et appliquant cette fonction successivement à tous les éléments d’un tableau passé en argument en commençant au premier élément.

Par exemple array_iter (fun x -> print_int x) [|1;2;3|] affichera 123.

Cette fonction réplique le comportement de la fonction Array.iter du module Array que vous n’utilisez pas dans cet exercice.

Corrigé   corrige

Ici il suffit de parcourir le tableau via une boucle for. Attention aux dépassements d’indice.

let array_iter f array =
  let n = Array.length array in
  for i = 0 to (n-1) do
    f (array.(i))
  done

Comparer deux listes   liste rec

Énoncé

Implémentez une fonction is_greater de comparaison stricte de liste s’appuyant sur l’ordre lexicographique (ordre du dictionnaire). Par exemple is_greater [] [1] s’évalue en false, is_greater [1.2] [1.1; 1.5] s’évalue en true et is_greater [1.1] [1.1; 1.5] s’évalue en false.

Corrigé   corrige

Il suffit de parcourir simultanément les deux listes et de s’arrêter dès qu’on trouve une bonne raison: soit une liste et vide, et l’autre est plus grande, soit un des deux éléments de tête est plus grand que l’autre. Si les deux sont vides, on doit renvoyer false car la comparaison est stricte.

let rec is_greater lst1 lst2 =
  match lst1, lst2 with
  |[], _ -> false
  |_, [] -> true
  |h1::t1, h2::t2 when h1 > h2 -> true
  |h1::t1, h2::t2 when h1 < h2 -> false
  |h1::t1, h2::t2 -> is_greater t1 t2

Inversion de liste   liste rec

Énoncé

Implémenter une fonction rev dont l’appel sur une liste s’évalue en cette liste dont l’ordre des éléments a été changé. Par exemple rev [0;1;2;3] s’évalue en [3;2;1;0].

Corrigé   corrige

Le principe est de travailler avec une liste contenant ce qui a déjà été inversé (le début de la liste déjà traité, ici already_done_lst). Ensuite, on place en tête de cette liste le premier élément de la liste à traiter, puis on traite la suite de la liste, qui sera donc placée devant l’élément qu’on vient de traiter.

let rec rev_with_acc lst already_done_lst =
  match lst with
  |[] -> already_done_lst
  |head::tail -> rev_with_acc tail (head::already_done_lst)
let rev lst = rev_with_acc lst []

Recherche dans un tableau   impératif tableau ref

Énoncé

Implémentez une fonction array_mem qui s’évalue à vrai si un tableau passé en argument contient un élément passé en argument. Par exemple array_mem 5 [|1;2;3|] s’évalue en false et array_mem [|3;5;6|] s’évalue en true.

Cette fonction réplique le comportement de la fonction Array.mem du module Array que vous n’utilisez pas dans cet exercice.

Corrigé   corrige

On parcourt le tableau, si jamais on trouve e, on passe la référence result à true. Attention ici, pas de return et la boucle for ira à son terme.

let array_mem e array =
  let result = ref false in
  let n = Array.length array in
  for i = 0 to (n-1) do
    result := !result || (array.(i) = e)
  done;
  !result

Décomposition en base \(b\)   liste rec

Énoncé

Implémentez une fonction base_b qui s’évalue en la liste des coefficients de la décomposition en base \(b\) de son argument \(n\). Les coefficients de poids le plus faible seront en tête de liste. Par exemple, base_b 2 10 s’évalue en [0;1;0;1] car \(10 = 2^1 + 2^3\).

Corrigé   corrige

On peut exploiter la relation de récurrence suivante: \(n = (n \text{ modulo } b) × 1 + b *(n/b)\) avec \(/\) la division entière. Le coefficient de poids faible est obtenu via le modulo, et les suivant via un appel récursif sur \(n/b\).

let rec base_b b n =
  if n < b then
    [n]
  else
    (n mod b)::(base_b b (n/b))

Renversement d’un tableau   tableau impératif

Énoncé

Implémenter une fonction array_rev prenant en entier un tableau array et s’évaluant en un tableau [|a.(n); a.(n-1); ...|] Par exemple array_rev [|3;2;1|] s’évalue en [|1;2;3|].

Corrigé   corrige

Il faut initialiser un tableau résultat, le remplir avec une valeur du bon type puis une boucle for suffit pour remplir ce tableau par indice décroissant de array.

let array_rev array =
  let n = Array.length array in
  let result = Array.make n array.(n-1) in
  for i = 0 to (n-1) do
    result.(i) <- array.(n-1-i)
  done;
  result

On peut aussi procéder via Array.init ou bien Array.map.

Exponentiation rapide   rec

Énoncé

Implémentez une fonction exp qui prend deux entiers a et n et s’évalue en \(a^n\). La complexité sera en \(O(\log(n))\) et vous n’utiliserez pas de boucle. Par exemple exp 5 3 s’évalue en 125

Corrigé   corrige

Il s’agit de l’algorithme classique d’exponentiation rapide. Attention à ne pas faire deux appels récursifs mais bien un seul dans le cas n pair, sinon tout les gain en complexité sont perdus puisque les calculs sont dupliqués.

let rec exp a n =
  if n = 0 then
    1
  else if n mod 2 = 0 then
    exp (a*a) (n/2)
  else
    a * (exp a (n-1))

Image d’un tableau   tableau impératif func

Énoncé

Implémenter une fonction array_map prenant en entrée une fonction f et un tableau array et s’évaluant en un tableau [|f array.(0); f array.(1); ...|] Par exemple array_map (fun x -> x + x) [|1;2;3|] s’évalue en [|2;4;6|].

Cette fonction réplique le comportement de la fonction Array.map du module Array que vous n’utilisez pas dans cet exercice.

Corrigé   corrige

Ici il faut d’abord créer le tableau résultat. Pour ça il faut obtenir une valeur initiale pour les cases dont le type doit être le même que celui des images de f. Une approche simple pour ceci consiste à évaluer f sur la première case du tableau array. Il ne faut pas oublier de « renvoyer » de résultat obtenu.

let array_map f array =
  let n = Array.length array in
  let result = Array.make n (f array.(0)) in
  for i = 0 to (n-1) do
    result.(i) <- f (array.(i))
  done;
  result

Ici, il était aussi possible d’utiliser directement Array.init pour un code plus court.

Longueur d’une liste   liste rec

Énoncé

Implémentez une fonction list_length qui s’évalue en la longueur d’une liste passée en argument. Par exemple list_length [0;1;2] s’évalue en 3.

Corrigé   corrige

Fonction classique.

let rec list_length lst =
  match lst with
  |[] -> 0
  |head::tail -> 1 + list_length tail

Fusion triée   liste rec

Énoncé

Implémentez une fonction sorted_merge qui fusionne les éléments de deux listes triées dans l’ordre croissant en une seule liste triée.

Par exemple sorted_merge [0;2;3;6] [1;4] s’évalue en [0;1;2;3;4;6].

Corrigé   corrige

On parcourt simultanément les deux liste tant qu’il reste des éléments, et on place en premier la plus petite des deux tête, puis on continue. C’est la très classique fusion du tri fusion.

let rec sorted_merge lst1 lst2 =
  match lst1,lst2 with
  |[], lst | lst, [] -> lst
  |h1::t1, h2::t2 when h1< h2 -> h1::(sorted_merge t1 lst2)
  |h1::t1, h2::t2 -> (*h2 <= h1*) h2::(sorted_merge lst1 t2)

Produit matriciel   impératif matrice

Énoncé

Implémentez une fonction matrix_product qui réalise le produit de deux matrices représentées par des float array array dont les dimensions conviennent.

Si les dimensions ne conviennent pas, une exception sera levée.

Par exemple matrix_product [|[|1.; 0.|];[|0.; 1.|]|] [|[|5.; 6.|];[|7.; 8.|]|] s’évalue en [|[|5.; 6.|];[|7.; 8.|]|]

Corrigé   corrige

Il faut initialiser une matrice qui contiendra le résultat, on peut pour cela utiliser Array.make_matrix. Puis il suffit d’appliquer la définition du produit matriciel via une triple boucle for.

Attention aux borne des boucles, en OCaml la borne supérieure est atteinte.

let matrix_product m1 m2 =
  let l1 = Array.length m1 in
  let c1 = Array.length m1.(0) in
  let l2 = Array.length m2 in
  let c2 = Array.length m2.(0) in
  if c1 <> l2 then
    failwith "Dimensions incorrectes"
  else
    let result = Array.make_matrix l1 c2 0. in
    for i = 0 to l1-1 do
      for j = 0 to c2-1 do
        for k = 0 to c1-1 do
          result.(i).(j) <- result.(i).(j) +. (m1.(i).(k) *. m2.(k).(j))
        done
      done
    done;
    result

On pourra se référer à l’algorithme de Strassen si on souhaite une meilleur complexité pour la multiplication de matrices de grande dimension.

Appartenance à une liste   liste rec

Énoncé

Implémenter une fonction list_mem prenant en entrée un élément e et une liste lst et déterminant si e est une élément de lst. Par exemple list_mem 5 [1;2;3] s’évalue en false.

Cette fonction réplique le comportement de la fonction List.mem du module List que vous n’utilisez pas dans cet exercice.

Corrigé   corrige

Il suffit de parcourir la liste et de renvoyer true si on trouve e et false si on arrive à la fin. Attention dans le match on redéfinit la valeur de e si on écrit =e::tail.

let rec list_mem e lst =
   match lst with
   |[] -> false
   |head::tail when head = e -> true
   |head::tail (* head <> e *)-> list_mem e tail

Image d’une liste   liste rec func

Énoncé

Implémenter une fonction list_map prenant en entrée une fonction f et une liste lst et s’évaluant en une liste des images des éléments de lst par f. Par exemple list_map (fun x -> x + x) [1;2;3] s’évalue en [2;4;6].

Cette fonction réplique le comportement de la fonction List.map du module List que vous n’utilisez pas dans cet exercice.

Corrigé   corrige

Il suffit de parcourir récursivement la liste et d’appliquer f à tous les éléments rencontrés.

let rec list_map f lst =
  match lst with
  |[] -> []
  |head::tail -> (f head)::(list_map f tail)

Somme si   liste rec func

Énoncé

On considère une liste d’entiers lst et un prédicat sur les entiers p. Implémentez une fonction sum_if qui s’évalue en la somme des éléments de la liste qui vérifient le prédicat. Par exemple sum_if (fun x -> x mod 2 = 0) [1;3;5] s’évalue en 0

Corrigé   corrige

On parcourt simplement la liste en vérifiant le prédicat avant de sommer.

let rec sum_if p lst =
  match lst with
  |[] -> 0
  |head::tail when p head -> head + sum_if p tail
  |head::tail -> sum_if p tail

Indice du plus petit élément d’une liste   liste rec exception

Énoncé

Implémentez une fonction min_and_argmin qui s’évalue en un couple du plus petit élément d’une liste et de son indice dans la liste. Dans le cas d’une liste vide, une exception sera levée. Si la liste contient des doublons, min_and_argmin choisira le min avec l’indice le plus faible.

Par exemple min_and_argmin [1;2;5;0;7] s’évalue en (0,3).

Vous n’utiliserez pas de boucle.

Corrigé   corrige

On introduit une fonction auxiliaire avec un argument supplémentaire pour indiquant l’index courant dans la liste. On fait un cas particulier pour la liste vide et pour la liste à un seul élément pour éviter de systématiquement lever une exception.

Puis on appel cet fonction avec comme indice initial 0.

let rec min_and_argmin_with_rank current_index lst =
  match lst with
  |[] -> failwith "List vide"
  |[x] -> (current_index, x)
  |head::tail -> match min_and_argmin_with_rank (current_index + 1) tail with
                 |(value,index) when value >= head -> (head, current_index)
                 |(value, index) (* head > value *)-> (value,index)
let min_and_argmin lst =
  min_and_argmin_with_rank 0 lst

Zip   liste rec

Énoncé

Implémentez une fonction zip qui prend en argument deux listes en s’évalue en une liste des couples des éléments de même rang de ces deux listes. Si les listes sont de taille différente, une exception sera levée.

Par exemple zip [1;2;3] [4;5;6] s’évalue en [(1,4);(2,5);(3,6)].

Corrigé   corrige

Il suffit de parcourir récursivement les deux listes.

let rec zip lst1 lst2 =
  match lst1, lst2 with
  |[],[] -> []
  |[], _ | _, [] -> failwith "Listes de taille différente"
  |h1::t1, h2::t2 -> (h1,h2)::(zip t1 t2)

Renversement d’un tableau en place   tableau impératif

Énoncé

Implémenter une fonction array_inplace_rev prenant en entier un tableau array et inversant l’ordre de ses cases. Par exemple array_inplace_rev [|3;2;1|] modifiera le contenu du tableau en [|1;2;3|].

Corrigé   corrige

Il faut initialiser un tableau résultat, le remplir avec une valeur du bon type puis une boucle for suffit pour remplir ce tableau par indice décroissant de array.

let array_inplace_rev array =
  let n = Array.length array in
  let temp_result = Array.make n array.(n-1) in
  for i = 0 to (n-1) do
    temp_result.(i) <- array.(n-1-i)
  done;
  for i = 0 to (n-1) do
    array.(i) <- temp_result.(i)
  done

On pourrait s’économiser la première boucle via Array.init ou Array.map. On pourrait utiliser moins de mémoire en faisant un échange des cases des extrémités. Attention à ne pas faire deux fois l’échange en arrêtant la boucle pour i = n/2

let array_inplace_rev array =
  let n = Array.length array in
  for i = 0 to (n/2) do
    let a_i = array.(i) in
    array.(i) <- array.(n-1-i);
    array.(n-1-i) <- a_i
  done

Mémoïsation   dictionnaire récursif

Énoncé

On considère la suite \(u_n\) définit récursivement pour \(n > 0\) par

  • \(u_{2n} = u_n\)
  • \(u_{2n+1} = u_{2n-1} + u_{n}\)
  • \(u_0 = 1\), \(u_1 = 1\)

Implémentez une fonction u s’évaluant en le terme de rang n de la suite quand on lui passe n en argument. Pour minimiser le temps de calcul nécessaire tout en gardant une consommation mémoire raisonnable vous prendrez soit de mémoïser le calcul des termes nécessaires pour \(u_n\) à l’aide d’un dictionnaire tel qu’implémenté par le module Hashtbl de la bibliothèque standard d’OCaml. Par exemple, \(u_{70} = 87\)

Corrigé   corrige

On commence par écrire une fonction auxiliaire récursive qui s’appuie sur un dictionnaire passé en argument pour mémoïser le calcul, ensuite on peut faire la fonction attendue qui va créer le dictionnaire nécessaire puis appeler la fonction auxiliaire en lui fournissant le dictionnaire attendu. Avant de réaliser un calcul, on vérifiera dans la fonction auxiliaire que le résultat de ce calcul n’est pas déjà stocké dans le dictionnaire, et après avoir réalisé un calcul, on ajoutera le résultat dans le dictionnaire. On remarque que si \(n = 2k+1\), \(n-2 = 2k-1\) pour gérer le deuxième cas de la relation de récurrence.

let rec u_with_dict dict n =
  match n with
  (*cas de base*)
  |0 -> 1
  |1 -> 1
  (*si on connait déjà le résultat, on le renvoie*)
  |n when Hashtbl.mem dict n -> Hashtbl.find dict n
  (*Sinon, on calcule le résultat, on l'ajoute au dictionnaire et on le renvoie*)
  |n when n mod 2 = 0 ->
    let u_k = u_with_dict dict (n/2) in
    Hashtbl.add dict n u_k; u_k
  |n -> (*n mod 2 = 1 ici *)
    let u_k = u_with_dict dict ((n-1)/2) and
        u_2k_minus_1 = u_with_dict dict (n-2) in
          let r = (u_k + u_2k_minus_1) in
            (Hashtbl.add dict n r; r)

let u n =
  let d = Hashtbl.create 5 in
  u_with_dict d n

Nombre de Strahler   arbre type_rec

Énoncé

On s’intéresse à des arbres d’arité non bornée, implémentés de la manière suivante:

  • Soit une feuille
  • Soit un nœud contenant une liste de fils

On suppose que les arbres sont implémentés par le type OCaml suivant:

type tree = Leaf | Node of tree list

Ici, on pourra considérer qu’un nœud qui n’est pas une feuille a au moins un fils, donc la liste associée n’est pas vide. Le nombre de Strahler d’un arbre \(t\), noté $s(t)est défini inductivement de la manière suivante:

  • \(s(t) = 1\) si \(t\) est une feuille
  • \(s(t)\) est le plus grand nombre de Strahler des enfants de \(t\) si un de ces enfants possède un nombre de Strahler plus grand que tous les autres
  • \(s(t)\) est 1 + le plus grand nombre de Strahler des enfants de \(t\) si au moins deux enfants ont un plus grand nombre de Strahler que tous les autres.

Par exemple l’arbre t = Node ([Node ([Leaf; Leaf]); Leaf; Leaf]) a un nombre de Strahler de 2. Implémenter une fonction strahler qui calcule le nombre de Strahler d’un arbre.

Corrigé   corrige

On commence par implémenter une fonction calculant le nombre de Strahler d’un arbre dont on connait le nombre de Strahler des enfants. Pour cela on parcourt la liste des nombres de Strahler des enfants, en renvoie le max et le nombre d’occurrences de ce max.

Ensuite, on peut implémenter la fonction strahler en calculant d’abord le nombre de Strahler des enfants d’un nœud puis en utilisant la fonction recherchant le max pour en déduire le nombre de Strahler du nœud. On peut obtenir la liste des nombre de Strahler des enfants via l’utilisation de la fonction List.map.

let rec get_max_and_max_occurrences strahler_children =
  match strahler_children with
  |[] -> failwith "Should be a Leaf"
  |[x] -> (x,1)
  |head::tail -> let (max,max_occ) = get_max_and_max_occurrences tail in
                 if max > head then
                   (max,max_occ)
                 else if max = head then
                   (max,1+max_occ)
                 else
                   (head, 1)
let rec strahler t =
  match t with
  |Leaf -> 1
  |Node children -> let (max, max_occ) = get_max_and_max_occurrences
                                           (List.map strahler children) in
                    if max_occ > 1 then
                      max +1
                    else
                      max

Chiffrement de César

Énoncé

Le chiffrement de César consiste modifier le contenu d’un message de manière en décalant chaque lettre d’un nombre de positions fixe dans l’alphabet. Par exemple, pour un décalage de 1, le mot « HAL » devient « IBM ». Implémentez une fonction caesar_encrypt qui chiffre une chaîne de caractère représentant un message à l’aide d’une clé de décalage. Par exemple caesar_encrypt "HAL" 1 s’évalue en "IBM". Attention, il vous est rappelé que les chaînes de caractère en OCaml sont immutables, il n’est pas possible de modifier une chaîne. Il est possible de convertir un caractère c en chaîne via String.make 1 c. Seules les lettres seront modifiées, les autres caractères seront laissés inchangés, et la casse sera conservée. caesar_encrypt "HaL" 1 s’évalue en "IbM".

Corrigé   corrige

Pour se simplifier la vie, on commence par implémenter une conversion de string vers char list. Ensuite on implémente une fonction pour chiffrer un seul caractère. Finalement, on implémente la conversion dans l’autre sens pour l’appliquer sur la liste des caractères chiffrés.

Attention aux modulos négatifs !

let rec string_to_list_from_i s i =
 if i = String.length s then
   []
  else
   s.[i]::(string_to_list_from_i s (i+1))

let string_to_list s =
  string_to_list_from_i s 0

let shift_letter k c =
  if (c >= 'a' && c <= 'z') then
    let x = int_of_char c in
    let x_26 = x - (int_of_char 'a') in
    let new_x = (x_26 + (k mod 26) + 26) mod 26 + (int_of_char 'a') in
    char_of_int new_x
  else if (c >= 'A' && c <= 'Z') then
    let x = int_of_char c in
    let x_26 = x - (int_of_char 'A') in
    let new_x = (x_26 + (k mod 26) + 26) mod 26 + (int_of_char 'A') in
    char_of_int new_x
  else
    c

let rec list_to_string lst =
  match lst with
    |[] -> ""
    |c::t -> (String.make 1 c)^(list_to_string t)

let caesar_encrypt s k =
  list_to_string  (List.map (shift_letter k) (string_to_list s))

Compter les 0   dict

Énoncé

On s’intéresse au nombre de 0 présents en fin d’écriture décimale de \(n!\). Implémentez une fonction qui compte ces 0. Vous éviterez de passer par le calcul direct de \(n!\) afin de ne pas vous exposer aux dépassent de capacité sur les entiers. Afin de rendre la fonction aussi efficace que possible vous pourrez exploiter la relation \(n! = n × (n-1)!\) et le fait qu’un 0 dans une écriture décimale correspond à l’existence d’un facteur \(10 = 2 × 5\) et vous mémoïserez les calculs intermédiaires.

Corrigé   corrige

Pour savoir combien de facteur 10 possède \(n!\), il suffit de compter les facteurs 2 et 5, et de prendre le minimum des deux.

Pour faire cela, on exploite la relation de récurrence \(n! = n × (n-1)!\) et on fait donc un appel récursif avec \(n-1\) et un calcul du nombre de facteurs 2 et 5 dans \(n\).

C’est le calcul du nombre de facteurs 2 et 5 dans \(n\) qui peut être mémoïsé en s’appuyant sur le fait que le nombre de facteur \(k\) dans \(n\) est 1 + le nombre de facteurs \(k\) dans \(\frac{n}{k}\) si \(n\) est divisible par \(k\), et 0 sinon.

Par ailleurs, on peut décompter indépendamment les facteurs 2 et 5. On commence donc par implémenter une fonction pour compter les facteurs avec mémoïsation.

Par exemple, on pourra vérifier que 25! possède 6 0 en fin d’écriture décimale.

let rec p_adic_val_memoisation dict n k =
  if n mod k > 0 then
    0
  else if Hashtbl.mem dict n then
    Hashtbl.find dict n
  else
    let res = 1 + p_adic_val_memoisation dict (n/k) k in
    Hashtbl.add dict n res; res

let rec p_adic_val_fact dict n k =
  if n = 0 then
    0 (* convention discutable ... *)
  else if n = 1 then
    0
  else
    (p_adic_val_memoisation dict n k) + (p_adic_val_fact dict (n-1) k )

let count_0_fact n =
  let dict_2 = Hashtbl.create 10 in
  let dict_5 = Hashtbl.create 10 in
  let _2_adic_val_fact = p_adic_val_fact dict_2 n 2 in
  let _5_adic_val_fact = p_adic_val_fact dict_5 n 5 in
  min _2_adic_val_fact _5_adic_val_fact

Découpage alterné de liste   liste rec

Énoncé

Implémentez une fonction alternate_split_list qui prend en argument une liste et s’évalue en un couple de liste, la première contenant les éléments de rang pair et la seconde les éléments de rang impair. Vous n’utiliserez pas de boucle dans cette fonction. Par exemple alternate_split_list [1;2;3] s’évaluera en ([1;3], [2])

Corrigé   corrige

On peut procéder via un filtrage en traitant les terme deux par deux, le premier étant de rang pair et le second de rang impair. Il suffit alors de les ajouter en tête des listes obtenues via un appel récursif sur la liste initial privée de ces deux éléments. Comme les éléments sont traités deux par deux, il faut un cas pour la liste vide et un cas pour la liste ne contenant qu’un seul élément.

let rec alternate_split_list lst =
  match lst with
  |[] -> ([],[])
  |[x] -> ([x], [])
  |h1::h2::t -> let even,odd = alternate_split_list t in
                (h1::even, h2::odd)

Évaluation d’un polynôme en un point (tableau)   tableau impératif référence

Énoncé

On considère un polynôme à coefficients float représenté par le tableau de ses coefficients. La case i contient le coefficient de degré i. Implémentez une fonction eval_at_array qui prend ce polynôme et un flottant et évalue ce polynôme en ce flottant.

La complexité sera linéaire en le degré du polynôme.

Par exemple eval_at_array [|5.;6.|] 1. s’évalue en 11.0.

Corrigé   corrige

Pour éviter les coûts liés aux calculs des puissances itérées, on peut calculer dans l’orde \(x^0\) puis \(x^1\), puis \(x^2\) en temps constant à partir de la puissance précédente. Ici on accumule le résultat dans la référence result.

let eval_at_array p x =
  let d = Array.length p in
  (*attention, ici d est le nombre de coefficients et non le degré*)
    let result = ref 0. in
    let current_x_power = ref 1. in
    for i = 0 to (d-1) do
      result := !result +. !current_x_power *. p.(i);
      current_x_power := !current_x_power *. x
    done ;
    !result

Calcul de la dérivée d’un polynôme (liste)   liste rec

Énoncé

On considère un polynôme à coefficients float représenté par la listes de ses coefficients. Le terme de rang i contient le coefficient de degré i. Implémentez une fonction diff_p_list qui prend ce polynôme et un flottant et s’évalue en la dérivée formelle de ce polynôme.

La complexité sera linéaire en le degré du polynôme.

Par exemple diff_p_list [5.; 6.; 7.] s’évalue en [6.; 14.].

Corrigé   corrige

On commence par faire une fonction récursive qui multiplie chaque coefficient par une séquence de valeur consécutives (qui sont censés être le rang des coefficients).

Ensuite, on utilise cette fonction à partir du coefficient d’ordre 1 et la première valeur associées (celle qui multiplie) sera donc aussi 1.

Il ne faut pas oublier de faire disparaitre le coefficient constant, décalant ainsi tous les termes pour bien réaliser une dérivée.

let rec diff_with_power lst current_power =
  match lst with
    |[] -> []
    |head::tail ->
      (head *. current_power) :: (diff_with_power tail (current_power +. 1.))

let diff_p_list p =
  match p with
    |[] -> []
    |p_0::p_next -> diff_with_power (p_next) 1.0

Nombre de diviseurs   impératif référence exception

Énoncé

Implémentez une fonction div qui calcule le nombre de diviseurs d’un entier n donné.

Par exemple div 5 s’évalue en 2 (1 et 5).

Dans le cas \(n=0\), div lèvera une exception.

Corrigé   corrige

Il suffit de faire une boucle for

let div n =
  if n = 0 then
    failwith "tout le monde divise 0"
  else
    let result = ref 0 in
    for i = 1 to n do
      if n mod i = 0 then
        result := !result +1
    done;
    !result

Expression arithmétique   type_rec arbre option

Énoncé

On considère des expression arithmétiques représentées par le type suivant

type expr = Value of int
          | Plus of expr * expr
          | Minus of expr * expr
          | Product of expr * expr
          | Div of expr * expr

Implémentez une fonction eval_expr qui évalue une telle expression. Vous utiliserez un type option pour gérer le cas des expressions dont la valeur n’est pas définie. Par exemple eval_expr (Plus (Value 1, Minus (Value 0, Value 1))) s’évalue en Some 0 et eval_expr (Div (Value 1, Value 0)) s’évalue en None.

Corrigé   corrige

Il s’agit de faire un parcours récursif de l’arbre représentant l’expression.

Il ne faut pas oublier de gérer les cas des expression qui non définies, qui se propagent vers le haut. Pour le faire, on évalue les sous expression, puis on fait un second filtrage pour gérer le cas d’une des sous expression ayant une valeur non définie.

Pour la division, il faut aussi gérer le cas où le diviseur vaut 0, et dans ce cas, la division n’est pas définie.

type expr = Value of int
          | Plus of expr * expr
          | Minus of expr * expr
          | Product of expr * expr
          | Div of expr * expr

let rec eval_expr e =
  match e with
  |Value i -> Some i
  |Plus (e1, e2) ->
    (match (eval_expr e1, eval_expr e2) with
       |_, None | None, _ -> None
       |Some v1, Some v2 -> Some (v1 + v2)
    )
  |Minus (e1, e2) ->
    (match (eval_expr e1, eval_expr e2) with
       |_, None | None, _ -> None
       |Some v1, Some v2 -> Some (v1 - v2)
    )
  |Product (e1, e2) ->
    (match (eval_expr e1, eval_expr e2) with
       |_, None | None, _ -> None
       |Some v1, Some v2 -> Some (v1 * v2)
    )
  |Div (e1, e2) ->
    (match (eval_expr e1, eval_expr e2) with
       |_, None | None, _ -> None
       |_, Some 0 -> None
       |Some v1, Some v2 -> Some (v1 / v2)
    )

Exponentiation rapide   impératif référence

Énoncé

Implémentez une fonction exp qui prend deux entiers a et n et s’évalue en \(a^n\). La complexité sera en \(O(\log(n))\) et vous n’utiliserez pas de fonction récursive. Par exemple exp 5 3 s’évalue en 125

Corrigé   corrige

Il s’agit de l’algorithme classique d’exponentiation rapide exploitant la relation \(a^{2n} = (a^2)^n\) et \(a^{2n+1} = a×a^{2n}\). On peut vérifier la correction via l’invariant de boucle a^n = !result * !current_base ^current_power avec en sortie !current_power =0

let exp a n =
  let result = ref 1 in
  let current_power = ref n in
  let current_base = ref a in
  while !current_power > 0 do
    if !current_power mod 2 = 0 then
      (
        current_power := !current_power /2;
        current_base := !current_base * !current_base;
      )
    else (*!current_power mod 2 = 1*)
      (
        result := !result * !current_base;
        current_power := !current_power -1;
      )
  done;
  !result

Bon parenthésage (impératif)   impératif référence

Énoncé

On considère une chaîne de caractères ne contenant que des '(' et ')'. Implémentez une fonction is_well_braced vérifiant s’il s’agit ou non d’une expression bien parenthésée. Par exemple is_well_braced "()(())" s’évalue à true, mais is_well_braced ")((())" s’évalue à false. Vous n’utiliserez pas de fonction récursive.

Corrigé   corrige

On s’appuie sur le fait que dans une expression bien parenthésée, le nombre de parenthèses ouvrantes depuis le début est toujours supérieur ou égal à celui des parenthèses fermantes et qu’à la fin, il y a autant de chaque paire. On compte donc le nombre de chaque espèces rencontré jusque là tant qu’on n’a pas vu de problème (et qu’on ne sort pas de la chaîne de caractères).

let is_well_braced s =
  let counter_open = ref 0 in
  let counter_close = ref 0 in
  let i = ref 0 in
  while !counter_open >= !counter_close && (!i < String.length s) do
    (if s.[!i] = '(' then
       counter_open := !counter_open + 1
     else if s.[!i] = ')' then
       counter_close := !counter_close + 1
     else
       failwith "contient des caractères interdits");
    i := !i+1
  done;
  !counter_open = !counter_close

Bon parenthésage multiple   Stack

Énoncé

On considère une chaîne de caractères ne contenant que des caractères parmi "()[]{}". Implémentez une fonction is_well_braced_multi vérifiant s’il s’agit ou non d’une expression bien parenthésée. Par exemple is_well_braced_multi "({})[]" s’évalue à true, mais is_well_braced_multi "([)]{}" s’évalue à false. Vous pourrez vous aider du module Stack de la bibliothèque standard OCaml.

Corrigé   corrige

Pour vérifier le bon parenthésage, on utilise une pile dans laquelle on entrepose chaque caractère ouvrant, et lorsqu’on rencontre un caractère fermant, on vérifie que le caractère ouvrant du dessus de pile correspond bien au caractère fermant lu. Dans le cas contraire on sait que l’expression n’est pas bien parenthésée. En fin de parcours de l’expression, on vérifie que la pile est bien vide.

let is_well_braced_multi s =
  let current_open  = Stack.create () in
  let i = ref 0 in
  let everything_ok_so_far = ref true in
  while !everything_ok_so_far && (!i < String.length s) do
    (if s.[!i] = '[' || s.[!i] = '(' || s.[!i] = '{' then
       Stack.push s.[!i] current_open
     else if not (Stack.is_empty current_open) then
       let c = Stack.pop current_open in
       if s.[!i] = ']' then
         everything_ok_so_far := !everything_ok_so_far && (c = '[')
       else if s.[!i] = ')' then
         everything_ok_so_far := !everything_ok_so_far && (c = '(')
       else if s.[!i] = '}' then
         everything_ok_so_far := !everything_ok_so_far && (c = '{')
       else
         failwith "contient des caractères interdits"
     else
       everything_ok_so_far := false
    );
    i := !i+1
  done;
  !everything_ok_so_far && (Stack.is_empty current_open)

Somme impaire   liste rec

Énoncé

On considère une liste d’entiers lst. Implémentez une fonction sum_odd qui s’évalue en la somme des éléments de la liste de rang impair. Par exemple sum_odd [1;3;5] s’évalue en 3

Corrigé   corrige

On parcourt simplement la liste en ne prenant pas le premier terme à chaque fois. Le décalage de 2 en 2 assure que le second terme est toujours de rang impair. Il ne faut pas oublier de gérer le cas de la liste à un seul élément et celui de la liste vide vu qu’on progresse de 2 éléments en 2 éléments.

let rec sum_odd lst =
  match lst with
  |[] -> 0
  |[x] -> 0
  |h1::h2::tail -> h2 + (sum_odd tail)

Hauteur d’un arbre ternaire   type_rec

Énoncé

On considère des arbres ternaires implémentés via le type suivant:

type ternary_tree = Leaf | Node of ternary_tree * ternary_tree * ternary_tree

Implémentez une fonction tree_height qui s’évalue en la hauteur d’un tel arbre. Par exemple

tree_height (Node (Leaf, Node (Leaf,Leaf,Leaf), Leaf))

s’évalue en 2.

Corrigé   corrige

Il faut simplement récupérer la hauteur du plus haut des enfants et y ajouter 1. On fait ceci en composant deux max.

type ternary_tree = Leaf
                  | Node of ternary_tree * ternary_tree * ternary_tree
let rec tree_height t =
  match t with
  |Leaf -> 0
  |Node (t1, t2, t3) ->
    1 + (max (tree_height t1)
             (max (tree_height t2) (tree_height t3)))

Liste aplatie   liste rec

Énoncé

On considère une liste de liste lst. Implémentez une fonction flatten_lst qui transforme cette liste en une liste de tous les éléments. Par exemple flatten [[1;2];[1;5;6];[1]] s’évalue en [1;2;1;5;6;1]. La complexité sera linéaire en le nombre d’éléments du résultat.

Corrigé   corrige

Ici on utilise l’opérateur de concaténation @ dont la complexité est la taille de la première liste. On a donc bien la complexité attendue.

let rec flatten lst =
  match lst with
  |[] -> []
  |head_lst::tail -> head_lst@(flatten tail)

Produit scalaire (tableau)   impératif tableau référence

Énoncé

On considère deux vecteurs de même dimension implémentés sous forme de float array. Implémentez une fonction dot qui réalise le produit scalaire de ces deux vecteurs.

Par exemple dot [|1.; 0.|] [|0.; 1.|] s’évalue en 0. car les deux vecteurs sont orthogonaux.

Corrigé   corrige

Il suffit de faire une boucle for pour réaliser la somme des produits terme à terme et le stocker dans une ref.

let dot v1 v2 =
  let n1 = Array.length v1 in
  let n2 = Array.length v2 in
  if n1 <> n2 then
    failwith "Dimensions incorrectes"
  else
    let result = ref 0. in
    for i = 0 to n1 -1 do
      result := !result +. v1.(i) *. v2.(i)
    done;
    !result

Briser les chaînes   liste rec

Énoncé

On considère une chaîne de caractère s. Implémentez une fonction split_on_char qui découpe s en plusieurs sous chaînes délimitées par un caractère passé en argument.

Par exemple, split_on_char "this is a sentence " ' ' s’évalue en ["this"; "is"; "a"; "sentence"; ""], le découpage des sous chaînes délimitées par des espaces. Ici le dernier espace de la chaîne a pour conséquence une chaîne vide à la fin de la liste.

Vous pourrez utiliser String.make 1 c qui crée une chaîne de longueur 1 ne contenant que le caractère c.

Corrigé   corrige

On procède en parcourant la chaîne récursivement depuis un indice i et en construisant les sous chaînes par accumulation et concaténation des lettres lues tant qu’on ne rencontre pas le caractère de séparation.

On peut ensuite appeler cette fonction récursive pour i = 0 et une chaîne des caractères lus jusque là initialement vide.

On aussi aurait pu utiliser String.sub pour obtenir les sous chaînes plutôt que de concatener.

let rec split_on_char_with_index previously_read i s sep_char =
  if i = String.length s then
    [previously_read]
  else if s.[i] = sep_char then
    previously_read :: split_on_char_with_index "" (i+1) s sep_char
  else
     split_on_char_with_index (previously_read^(String.make 1 s.[i])) (i+1) s sep_char

let split_on_char s sep_char =
  split_on_char_with_index "" 0 s sep_char

Découpage régulier de liste   liste rec

Énoncé

Implémentez une fonction size_k_split qui découpe une liste en une liste de blocs de termes consécutifs de taille donnée k. Par exemple, size_k_split 3 [0;1;2;3;4;5;6;7] s’évalue en [[0;1;2];[3;4;5][6;7]], le dernier bloc est éventuellement de taille inférieures à k s’il n’y a pas assez de termes.

Corrigé   corrige

On commence par implémenter une fonction qui sépare une liste entre ses k premier termes et le reste. Pour ceci, on procède en récupérant k-1 éléments sur la fin de la liste, et on ajoute le premier éléments aux k-1 éléments obtenus par appel récursif.

Puis on applique récursivement cette fonction sur la liste initiale.

let rec get_first_k_elems k lst =
if k = 0 then
  ([], lst)
else if lst = [] then
  ([], lst)
else
  let (elems, lst_end) = get_first_k_elems (k-1) (List.tl lst) in
  ((List.hd lst)::elems, lst_end)

let rec size_k_split k lst =
  match lst with
  |[] -> []
  |lst -> let (k_first, end_lst) = get_first_k_elems k lst in
          k_first::(size_k_split k end_lst)

Tri par insertion   liste rec

Énoncé

Implémentez une fonction insertion_sort qui réalise le tri par insertion d’une liste.

Par exemple insertion_sort ['a'; 'c'; 'b'; 'z'; 'a'] s’évalue en ['a'; 'a'; 'b'; 'c'; 'z']. Vous n’utiliserez par de boucle.

Corrigé   corrige

On commence par implémenter une fonction qui insère un élément e à la bonne place dans une liste déjà triée. Puis on trie la fin de la liste récursivement, et on insère la tête à la bonne place dans la fin triée de la liste.

let rec insert_at_correct_position e lst =
  match lst with
  |[] -> [e]
  |head::tail when e <= head -> e::head::tail
  |head::tail (*e > head*) -> head::(insert_at_correct_position e tail)

let rec insertion_sort lst =
  match lst with
  |[] -> []
  |head::tail -> insert_at_correct_position head (insertion_sort tail)

Filtrage d’une liste   liste rec func

Énoncé

Implémenter une fonction list_filter prenant en entrée un prédicat p et une liste lst et filtrant en une liste des éléments de lst vérifiant p. Par exemple list_filter (fun x -> x > 5) [1;7;3;8] s’évalue en [7;8].

Cette fonction réplique le comportement de la fonction List.filter du module List que vous n’utilisez pas dans cet exercice.

Corrigé   corrige

Il suffit de parcourir la liste et de ne garder que les éléments qui vérifient p.

let rec list_filter p lst =
   match lst with
   |[] -> []
   |head::tail when p head -> head::list_filter p tail
   |head::tail (* not p head *)-> list_filter p tail

Inversion de liste (impératif)   liste impératif référence

Énoncé

Implémenter une fonction list_rev dont l’appel sur une liste s’évalue en cette liste dont l’ordre des éléments a été changé. Par exemple list_rev [0;1;2;3] s’évalue en [3;2;1;0]. Vous n’utiliserez pas de fonction récursive.

Corrigé   corrige

Le principe est de travailler avec une référence vers liste contenant ce qui a déjà été inversé (le début de la liste déjà traité, ici already_done_lst) et une contenant ce qu’il reste à faire (ici todo). Ensuite, on place en tête de cette liste le premier élément de la liste à traiter, puis on le retire de la liste à traiter.

let list_rev lst =
  let already_done = ref [] in
  let todo = ref lst in
  while !todo <> [] do
    already_done := (List.hd (!todo)) :: !already_done;
    todo := (List.tl (!todo))
  done;
  !already_done;

Évaluation d’un polynôme en un point (liste)   liste rec

Énoncé

On considère un polynôme à coefficients float représenté par la listes de ses coefficients. Le terme de rang i contient le coefficient de degré i. Implémentez une fonction eval_at_list qui prend ce polynôme et un flottant et évalue ce polynôme en ce flottant.

La complexité sera linéaire en le degré du polynôme.

Par exemple eval_at_list [5.;6.] 1. s’évalue en 11.0.

Corrigé   corrige

Pour éviter les coûts liés aux calculs des puissances itérées, utilise la relation suivante (dite de Horner) \(\sum\limits_{i=0}^{d-1}a_i × x^i = a_0 + x × \left(\sum\limits_{i=0}^{d-2} a_{i-1}x^i\right)\).

let rec eval_at_list p x =
  match p with
  |[] -> 0.
  |head::tail -> head +. (x *. (eval_at_list tail x))

Fusion alternée   liste rec

Énoncé

Implémentez une fonction alternate_merge qui fusionne les éléments de deux listes en une seule en alternant un élément d’une liste puis un de la suivante tant que c’est possible.

Par exemple alternate_merge [0;2;4;6] [1;3] s’évalue en [0;1;2;3;4;6]. La première liste étant plus longue, on termine avec uniquement des éléments de celle ci (4 et 6).

Corrigé   corrige

On parcourt simultanément les deux liste tant qu’il reste des éléments, et on place en premier la tête de la première liste, suivie par la tête de la deuxième puis la fin de la fusion via un appel récursif.

let rec alternate_merge lst1 lst2 =
  match lst1,lst2 with
  |[], lst | lst, [] -> lst
  |h1::t1, h2::t2 -> h1::h2::(alternate_merge t1 t2)

Deuxième max d’une liste   liste rec option

Énoncé

On considère une liste lst dont on cherche le deuxième plus grand élément. Implémentez une fonction second_max qui renvoie, s’il existe cet élément. Vous utiliserez un type option pour gérer les cas où cet élément n’existe pas.

Par exemple, second_max [1; 1; 1] s’évalue en None et second_max [2; 23; 6] s’évalue en Some 6.

Corrigé   corrige

On utilise une fonction auxiliaire qui prends en argument les deux plus grands éléments rencontrés jusque là, éventuellement égaux. Quand un nouvel élément plus grand est trouvé, l’ancien max devient le second max. Ensuite on appelle cette fonction avec pour valeur initial du plus grand élément la tête de la liste dont on cherche le second max.

let rec second_max_with_acc lst current_max second_max =
  match lst with
    |[] -> if second_max = current_max then
             None
           else
             Some second_max
    |head::tail when head > current_max ->
      second_max_with_acc tail head current_max
    |head::tail when head > second_max ->
      second_max_with_acc tail current_max head
    |head::tail ->
      second_max_with_acc tail current_max second_max

let second_max lst =
  match lst with
    |[] -> None
    |head::tail ->
      second_max_with_acc tail head head

PGCD d’une liste   liste rec

Énoncé

Implémentez une fonction list_gcd qui calcule le PGCD de tous les éléments d’une liste.

Par exemple list_gcd [12; 18; 27] s’évalue en 3.

Corrigé   corrige

On commence par implémenter une fonction qui calcule le PGCD de deux nombres. Pour cela on procède de manière récursive via la relation gcd a b = gcd b (a mod b) et gcd a 0 = a (algorithme d’Euclide).

Ensuite, on peut calculer récursivement le PGCD de la fin de la liste, puis obtenir le PGCD de la liste en faisant le PGCD de la fin et de la tête. Dans le cas d’une liste vide, 0 est un bon diviseur, cas il est neutre pour l’opération de PGCD.

let rec gcd a b =
  if b = 0 then
    a
  else
    gcd b (a mod b)

let rec list_gcd lst =
  match lst with
  |[] -> 0
  |head::tail -> gcd head (list_gcd tail)

Simplification de fraction   enregistrement

Énoncé

On s’intéresse à des fractions implémentées à l’aide d’un enregistrement dont les champs sont numerator et denominator. Implémentez le type fraction correspondant, puis implémentez une fonction make_fraction a b qui s’évalue en la fraction \(\frac{a}{b}\) et implémentez une fonction simplify : fraction -> unit qui modifie en place la fraction de manière à la rendre irréductible.

Par exemple, si on pose let f = make_fraction 6 9 puis qu’on évalue simplify f, on aura une fraction dont le numérateur et le dénominateur sont 2 et 3.

Corrigé   corrige

Après avoir implémenté le type (on n’oublie pas le type de chaque champ), on implémente une fonction de calcul du PGCD pour simplifier la fraction via l’algorithme d’Euclide.

Puis on divise le numérateur et le dénominateur par le PGCD des deux pour simplifier.

type fraction = {mutable numerator : int;
                 mutable denominator : int}

let make_fraction a b =
  {numerator = a; denominator = b}

let rec gcd a b =
  if b = 0 then
    a
  else
    gcd b (a mod b)

let simplify f =
  let gcd_num_denom = gcd f.numerator f.denominator in
  f.numerator <- f.numerator / gcd_num_denom;
  f.denominator <- f.denominator / gcd_num_denom

Itérer sur une liste   liste rec func

Énoncé

Implémenter une fonction list_iter prenant en entrée une fonction à effet de bord f et une liste lst et appelant la fonction f sur chaque élément de lst dans l’ordre. Par exemple list_map (fun x -> print_int x) [1;2;3] affiche 123.

Cette fonction réplique le comportement de la fonction List.iter du module List que vous n’utilisez pas dans cet exercice.

Corrigé   corrige

Il suffit de parcourir récursivement la liste et d’appliquer f à tous les éléments rencontrés. Attention, il faut gérer le cas de la liste vide, une fonction à effet de bord a une image de type unit, ce qui signifie que pour la liste vide, on peut « ne rien faire » en s’évaluant en (), la seule valeur de type unit.

On prend soin d’appeler f sur la tête avant de réaliser l’appel récursif.

let rec list_iter f lst =
  match lst with
  |[] -> ()
  |head::tail -> let _ = f head in (list_iter f tail)

Recherche dichotomique   impératif tableau option référence

Énoncé

On considère un tableau trié d’éléments a et un élément de même type e. Implémentez une fonction dicho_search qui recherche l’élément e dans a et renvoie son indice s’il existe. On utilisera un type option pour gérer le cas où cet indice n’existe pas.

Par exemple, dicho_search 7 [|1;2;3;4;5;6|] s’évaluera en None.

Corrigé   corrige

Pour réaliser la recherche dichotomique, on pose deux références be et en correspondant respectivement à l’indice de la première case de la zone de recherche et à l’indice de la première case juste après la zone de recherche.

Initialement, on a be = 0 et en = Array.length a.

On applique ensuite l’algorithme classique de recherche dichotomique.

let dicho_search e a =
  let be = ref 0 in
  let en = ref (Array.length a) in
  let result = ref None in
  while !en > !be && !result = None do
    let middle_index = (!en + !be)/2 in
    let middle_value = a.(middle_index) in
    if middle_value = e then
      (
        result := Some middle_index
      )
    else if middle_value < e then
      be := (middle_index + 1) (* le +1 est important *)
    else (*middle_value < e*)
      en := middle_index
  done;
  !result

k-accessibles   impératif tableau liste rec référence

Énoncé

On considère un graphe représenté par listes d’adjacences implémentées par le type (int list) array. Implémentez une fonction k_reachable qui détermine la liste des sommets accessibles en exactement k étape à partir d’une liste de sommets initiaux.

Par exemple pour le graphe let g = [|[2;3];[2];[4];[4];[1]|], k_reachable g 2 [0;1] s’évalue en [4], qui est la liste des sommets accessibles à partir des sommets 0 et 1 en exactement 2 étapes dans g.

Corrigé   corrige

Pour calculer la liste demandée, on va procéder en calculant la liste des sommets accessible en exactement une étape et répéter ceci k fois à partir de la liste obtenue à l’étape précédente. Afin d’éviter des calculs inutiles, on va commencer par implémenter une fonction d’insertion dans doublons dans une liste.

let rec insert_no_duplicates x lst =
  match lst with
  |[] -> [x]
  |head::tail when head = x -> lst
  |head::tail (* head <>x *) -> head::(insert_no_duplicates x tail)

let k_reachable g k vertex_lst =
  let current_vertices = ref vertex_lst in
  for i = 0 to k-1 do
    let next_vertices = ref [] in
    while !current_vertices <> [] do
      let v = List.hd !current_vertices in
      List.iter
        (fun x ->
          next_vertices := insert_no_duplicates x !next_vertices)
        g.(v);
        current_vertices := List.tl !current_vertices
    done;
    current_vertices := !next_vertices
  done;
  !current_vertices

Recherche de mot dans un texte   impératif référence option

Énoncé

Implémentez une fonction ctrl_f qui recherche un mot w dans un texte t et renvoie son indice s’il est dans le texte. Vous utiliserez un type option pour gérer le cas où le mot n’est pas dans le texte.

Par exemple ctrl_f "test" "conteste" s’évalue en Some 3.

Corrigé   corrige

Pour chaque position de départ possible dans le texte, on parcourt toutes les lettres du mot recherché pour voir si on trouve un occurrence.

Attention à de pas dépasser les bornes des tableaux.

let ctrl_f w t =
  let result = ref None in
  for i = 0 to String.length t do
    let j = ref  0 in
    while !j + i < String.length t && !j < String.length w do
      if w.[!j] = t.[i+ !j] then
        j := !j +1
      else
        j :=  (String.length w) + 1
    done;
    if !j = String.length w then
      (*pas de passage dans le else, on a trouvé une occurrence*)
      result := Some i
    done;
  !result

Recherche dans un Arbre Binaire de recherche   type_rec option

Énoncé

On s’intéresse à une implémentation des arbres binaires de recherche à l’aide d’un type récursif dont le constructeur pour un arbre vide est nommé Empty et le constructeur pour un noeud est nommé Node et prend un triplet (fils gauche, (couple clé, valeur), fils droit) en paramètre. Implémentez le type récursif abr correspondant, les clés seront des string et les valeurs de int.

Implémentez une fonction abr_search qui recherche une valeur v associée à une clé k dans un ABR t. L’absence de l’élément sera gérée à l’aide d’un type option.

Par exemple =

abr_search "key" (Node (
                      Node (Empty, ("hey", 0), Empty),
                      ("jey",1),
                      (Node (Empty, ("key",2),Empty))
                    )
                 )

s’évaluera en 2 et

abr_search "ley" (Node (
                      Node (Empty, ("hey", 0), Empty),
                      ("jey",1),
                      (Node (Empty, ("key",2),Empty))
                    )
                )

s’évaluera en None.

Corrigé   corrige

On commence par implémenter le type de demandé.

Ensuite, il s’agit de faire un parcours récursif de l’arbre en descendant à gauche ou à droite selon la manière dont la valeur du noeud courant se compare à l’élément recherché.

En arrivant sur un arbre vide, on ne peut plus continuer, on sait qu’on ne pourra pas le trouver.

type abr = Empty | Node of abr * (string * int) * abr

let rec abr_search e t =
  match t with
  |Empty -> None
  |Node (l, (k,v), r) when k = e -> Some v
  |Node (l, (k,v), r) when k > e -> abr_search e l
  |Node (l, (k,v), r) (* k < e *) -> abr_search e r

Produit scalaire (liste)   liste rec

Énoncé

On considère deux vecteurs de même dimension implémentés sous forme de float list. Implémentez une fonction dot qui réalise le produit scalaire de ces deux vecteurs.

Par exemple dot [1.; 0.] [0.; 1.] s’évalue en 0. car les deux vecteurs sont orthogonaux.

Corrigé   corrige

On parcourt les liste en simultané et on somme les produits terme à terme au résultat obtenu par l’appel récursif.

let rec dot v1 v2 =
match v1, v2 with
|[], [] -> 0.
|_,[] | [],_ -> failwith "Dimensions incorrectes"
|h1::t1, h2::t2 -> (h1*.h2) +. (dot t1 t2)

PGCD et PPCM   rec

Énoncé

Implémentez une fonction gcd_and_lcm qui s’évalue en le couple du PGCD et PPCM des deux entiers passés en argument.

Par exemple gcd_and_lcm 15 9 s’évalue en (3,45).

Corrigé   corrige

On commence par implémenter une fonction qui calcule le PGCD de deux nombres. Pour cela on procède de manière récursive via la relation gcd a b = gcd b (a mod b) et gcd a 0 = a (algorithme d’Euclide).

Ensuite on se sert de la relation (gcd a b) × (lcm a b) = a×b

let rec gcd a b =
  if b = 0 then
    a
  else
    gcd b (a mod b)

let gcd_and_lcm a b =
  let gcd_ab = gcd a b in
  (gcd_ab, a*b / gcd_ab)

Insertion dans un Arbre Binaire de recherche   type_rec

Énoncé

On s’intéresse à une implémentation des arbres binaires de recherche à l’aide d’un type récursif dont le constructeur pour un arbre vide est nommé Empty et le constructeur pour un noeud est nommé Node et prend un triplet (fils gauche, (couple clé, valeur), fils droit) en paramètre. Implémentez le type récursif abr correspondant, les clés seront des string et les valeurs de int.

Implémentez une fonction abr_insert qui insère à la bonne place une valeur v associée à une clé k dans un ABR t. Si k est déjà dans l’ABR, on remplacera la valeur associée.

Par exemple

abr_insert "key" 2 (Node (
                        Node (Empty, ("hey", 0), Empty),
                        ("jey",1),
                        Empty))

s’évaluera en:

(Node (
     Node (Empty, ("hey", 0), Empty),
     ("jey",1),
     (Node (Empty, ("key",2),Empty))))

Corrigé   corrige

On commence par implémenter le type de demandé.

Ensuite, il s’agit de faire un parcours récursif de l’arbre en descendant à gauche ou à droite selon la manière dont la valeur du noeud courant se compare à l’élément recherché.

En arrivant sur un arbre vide, on insère le couple avec deux arbres vides comme enfants. Si jamais on trouve la clé dans l’arbre pendant la recherche de la bonne place, on remplace la valeur associée.

type abr = Empty | Node of abr * (string * int) * abr

let rec abr_insert key value t =
  match t with
  |Empty -> Node (Empty, (key, value), Empty)
  |Node (l, (k,v), r) when k = key ->
    Node (l, (key,value), r)
  |Node (l, (k,v), r) when k > key ->
    Node (abr_insert key value l, (k,v), r)
  |Node (l, (k,v), r) (*k > key*) ->
    Node (l, (k,v), abr_insert key value r)

Rotation de matrice   impératif matrice

Énoncé

Implémentez une fonction matrix_turn qui prend en argument une matrice carrée et s’évalue en cette matrice tournée d’un quart de tour dans le sens positif.

Par exemple matrix_turn [|[|1;2;3|];[|4;5;6|];[|7;8;9|]|] s’évalue en [|[|3;6;9|];[|2;5;8|];[|1;4;7|]|]

Corrigé   corrige

Pour faire cette rotation, on peut se rendre compte que le coefficient i,j est remplacé par j, n-1-i.

Afin de ne pas modifier la matrice initiale, il faut créer une matrice de dimension égales à l’aide de Array.make_matrix.

let matrix_turn m =
  let l = Array.length m in
  let c = Array.length m.(0) in
  if l <> c then
    failwith "Matrice non carrée"
  else
    let n = c in
    let result = Array.make_matrix l c m.(0).(0) in
    for i = 0 to n-1 do
      for j = 0 to n -1 do
        result.(i).(j) <- m.(j).(n-1-i)
      done
    done;
    result

Subset sum   rec liste

Énoncé

On s’intéresse au problème suivant: étant donné une liste lst d’entiers positifs et un entier positifs n, est-il possible de trouver un ensemble d’éléments de lst dont la somme est n ? Implémentez une fonction subset_sum qui renvoie la liste des éléments de lst qui somment à n si elle existe. Vous utiliserez un type option pour gérez le cas où une telle liste n’existe pas.

Par exemple subset_sum 6 [1;2;3;4;5] s’évalue en Some [2;4] (même si on pourrait aussi avoir Some [1;5]).

Quelle est la complexité d’une telle fonction par rapport au nombre d’éléments de la liste ? Comment pourrait-on améliorer cette complexité ?

Corrigé   corrige

On s’appuie sur la disjonction de cas suivante: si lst = head::tail alors on peut obtenir n avec les élément de lst dans deux cas:

  • Si on peut obtenir n uniquement avec les éléments de tail
  • Si on peut obtenir n-head avec uniquement les éléments de tail

Ceci donne donc une relation de récurrence, sachant que la seule valeur qu’on peut faire avec la liste vide est 0 et qu’aucune liste ne peut faire une valeur négative.

let rec subset_sum n lst =
  match n,lst with
  |n, _ when n < 0 -> None
  |0,[] -> Some []
  |_,[] -> None
  |n,head::tail -> (
    match subset_sum n tail, subset_sum (n-head) tail with
    |None, None -> None
    |Some lst_full, _ -> Some lst_full
    |_, Some lst_no_head-> Some (head::lst_no_head)
    )

La complexité est exponentielle en la taille de la liste dans le pire cas. On pourrait utiliser une approche avec mémoïsation pour réduire cette complexité.

Position du plus grand mot   impérarif référence

Énoncé

On considère une chaîne de caractères constituée uniquement de lettre et d’espaces. Implémentez une fonction search_largest_word qui s’évalue en la position de début et de fin du mot le plus long de cette chaîne.

Par exemple search_largest_word "ceci est une phrase vide" s’évalue en (13,18).

Corrigé   corrige

On va parcourir toute la chaîne en gardant en mémoire les meilleurs indices de début et de fin en plus des indices courants de début et de fin de mot. Quand on tombe sur un espace, on compare la longueur du mot et on met éventuellement à jour les meilleurs indices.

let search_largest_word t =
  let best_start = ref 0 in
  let best_end = ref 0 in
  let current_start = ref 0 in
  let current_end = ref 0 in
  for i = 0 to (String.length t) -1 do
    if t.[i] = ' ' then
      (
        current_end := i-1;
        (if !current_end - !current_start > !best_end - !best_start then
          (
            best_end := !current_end;
            best_start := !current_start;
          )
        );
        current_start := i+1;
      )
  done;
  current_end := (String.length t) -1;
  (if !current_end - !current_start > !best_end - !best_start then
     (
       best_end := !current_end;
       best_start := !current_start;
     )
  );
  (!best_start, !best_end)

Palindrome   impératif référence

Énoncé

Implémentez une fonction is_palindrome qui s’évalue en true si la chaîne passée en argument est un palindrome et false autrement. Les espaces seront ignorés.

Par exemple is_palindrome "ab ce cba" s’évalue en true.

Corrigé   corrige

Pour vérifier le caractère de palindrome, on va lire le mot depuis le début et la fin simultanément. Pour cela on introduit deux références correspondant à la position du curseur et début et fin de mot. Quand on rencontre un espace, on fait avancer les curseurs jusqu’à la prochaine lettre. Quand on rencontre deux lettre identiques pour les deux curseurs, on les fait avancer aussi.

Quand les deux curseurs se rejoignent, on a terminé.

Si jamais on trouve une lettre distincte, on peut stopper le parcours.

let is_palindrome s =
  let end_index = ref ((String.length s) -1) in
  let start_index = ref 0 in
  let no_problem_yet = ref true in
  while !end_index > !start_index && !no_problem_yet do
    if s.[!start_index] = s.[!end_index] then
       (* Si les lettres sont identiques, *)
       (* espace ou non, on peut progresser *)
       (
         start_index := !start_index + 1;
         end_index := !end_index - 1
       )
    else if s.[!start_index] = ' ' then
      start_index := !start_index + 1
    else if s.[!end_index] = ' ' then
      end_index := !end_index - 1
    else (*s.[!start_index] <> s.[!end_index]*)
        no_problem_yet := false;
  done;
  !no_problem_yet

Remerciements

Merci à J.Reichert pour ses nombreuses idées d’exercice.

Vladislav Tempez, 2024-09-09 Mon 16:42