Quelques exercices de programmation
Table des matières
- Extremum d’une liste liste rec
- Itérer sur les tableaux tableau impératif func
- Comparer deux listes liste rec
- Inversion de liste liste rec
- Recherche dans un tableau impératif tableau ref
- Décomposition en base \(b\) liste rec
- Renversement d’un tableau tableau impératif
- Exponentiation rapide rec
- Image d’un tableau tableau impératif func
- Longueur d’une liste liste rec
- Fusion triée liste rec
- Produit matriciel impératif matrice
- Appartenance à une liste liste rec
- Image d’une liste liste rec func
- Somme si liste rec func
- Indice du plus petit élément d’une liste liste rec exception
- Zip liste rec
- Renversement d’un tableau en place tableau impératif
- Mémoïsation dictionnaire récursif
- Nombre de Strahler arbre type_rec
- Chiffrement de César
- Compter les 0 dict
- Découpage alterné de liste liste rec
- Évaluation d’un polynôme en un point (tableau) tableau impératif référence
- Calcul de la dérivée d’un polynôme (liste) liste rec
- Nombre de diviseurs impératif référence exception
- Expression arithmétique type_rec arbre option
- Exponentiation rapide impératif référence
- Bon parenthésage (impératif) impératif référence
- Bon parenthésage multiple Stack
- Somme impaire liste rec
- Hauteur d’un arbre ternaire type_rec
- Liste aplatie liste rec
- Produit scalaire (tableau) impératif tableau référence
- Briser les chaînes liste rec
- Découpage régulier de liste liste rec
- Tri par insertion liste rec
- Filtrage d’une liste liste rec func
- Inversion de liste (impératif) liste impératif référence
- Évaluation d’un polynôme en un point (liste) liste rec
- Fusion alternée liste rec
- Deuxième max d’une liste liste rec option
- PGCD d’une liste liste rec
- Simplification de fraction enregistrement
- Itérer sur une liste liste rec func
- Recherche dichotomique impératif tableau option référence
- k-accessibles impératif tableau liste rec référence
- Recherche de mot dans un texte impératif référence option
- Recherche dans un Arbre Binaire de recherche type_rec option
- Produit scalaire (liste) liste rec
- PGCD et PPCM rec
- Insertion dans un Arbre Binaire de recherche type_rec
- Rotation de matrice impératif matrice
- Subset sum rec liste
- Position du plus grand mot impérarif référence
- Palindrome impératif référence
- Remerciements
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 detail
- Si on peut obtenir
n-head
avec uniquement les éléments detail
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.