In [1]:
Sys.command "ocaml -version";;
Out[1]:
Merci à Pierre et Clarence, élèves de la préparation à l'agrégation en 2018/2019, de m'avoir autorisé à utiliser une partie de leur implémentation pour cette (proposition de) correction.
La question de programmation pour ce texte était donnée en page 5, et était assez courte :
"Implanter dans l’un des langages de programmation autorisés l’algorithme de test de permutation."
Dans l'ordre, on va :
"5340"
en un mot [|5;4;3;0|]
,[|1;0;2;3|]
,En bonus, on implémente aussi le test de moyenne, et on illustre le bon fonctionnement de notre implémentation sur les exemples du texte.
In [4]:
type mot = int array ;;
Out[4]:
On présente deux implémentations, la première n'est pas très réfléchie et se trouve être avoir une complexité temporelle en $\mathcal{O}(|mot|^2)$, tandis que la seconde est plus réfléchie et fonctionne en temps linéaire $\mathcal{O}(|mot|)$.
On va supposer que chaque valeur du mot d'entrée est bien dans $\{0,\dots,n-1\}$, et on vérifie simplement que chaque valeur est présente une et une seule fois (si $n=|mot|$).
In [6]:
let est_permut (m : mot) : bool =
let p = Array.length m in
let permut_bool = ref true in
let i = ref 0 in
while !i<p && !permut_bool do
permut_bool := !permut_bool && ( Array.exists (fun x -> x = !i) m );
incr i
done;
!permut_bool;;
(* Complexité temporelle au pire en O(p^2) *)
(* Complexité spatiale en O(p) *)
Out[6]:
In [7]:
est_permut [|3; 5; 1; 2; 4; 0|];; (* true ! *)
est_permut [|3; 5; 1; 3; 4; 0|];; (* false ! il manque le 2, il y a deux fois le 3 *)
Out[7]:
Out[7]:
In [8]:
let est_permut_lineaire (m : mot) : bool =
let p = Array.length m in
let tous_vus = Array.make p false in
Array.iter (fun mi ->
tous_vus.(mi) <- true
) m;
Array.for_all (fun b -> b) tous_vus
;;
(* Complexité temporelle au pire en O(p) *)
(* Complexité spatiale en O(p) *)
Out[8]:
In [9]:
est_permut_lineaire [|3; 5; 1; 2; 4; 0|];; (* true ! *)
est_permut_lineaire [|3; 5; 1; 3; 4; 0|];; (* false ! il manque le 2, il y a deux fois le 3 *)
Out[9]:
Out[9]:
In [10]:
let echange t i j =
let tmp = t.(i) in
t.(i) <- t.(j);
t.(j) <- tmp;
;;
Out[10]:
In [11]:
Random.self_init ();;
Out[11]:
On génère une permutation aléatoire facilement, en faisant plein d'échanges aléatoires (nombre et localisation aléatoires). Attention, on essaie pas de generer selon la loi uniforme dans $\Sigma_p$, c'est beaucoup plus difficile !
In [12]:
let permutation_aleatoire p =
let m = Array.init p (fun i -> i) in
for _ = 1 to Random.int (10*p) do
echange m (Random.int p) (Random.int p);
done;
m
;;
Out[12]:
In [13]:
permutation_aleatoire 10;;
Out[13]:
In [13]:
permutation_aleatoire 10;;
Out[13]:
In [13]:
permutation_aleatoire 10;;
Out[13]:
Cette petite fonction permet de mesurer le temps machine mis pour calculer une fonction f ()
:
In [14]:
let timeit f () =
let debut = Sys.time () in
let res = f () in
let fin = Sys.time () in
Printf.printf "\nTemps pour calculer f() = %f seconde(s)." (fin -. debut);
res
;;
Out[14]:
On va s'en servir avec cette fonction, qui test nombre_test
fois la vérification f_est_permut
sur une permutation aléatoire de taille taille
.
In [17]:
let test f_est_permut nombre_test taille () =
Printf.printf "\nDébut de %i tests de taille %i..." nombre_test taille;
flush_all ();
for _ = 1 to nombre_test do
let m = permutation_aleatoire taille in
assert (f_est_permut m);
done
;;
Out[17]:
In [26]:
timeit (test est_permut_lineaire 100 1000) ();;
timeit (test est_permut_lineaire 100 2000) ();;
timeit (test est_permut_lineaire 100 4000) ();;
Printf.printf "\n\n\n";;
flush_all();;
Out[26]:
Out[26]:
Out[26]:
Out[26]:
Out[26]:
In [27]:
timeit (test est_permut 100 1000) ();;
timeit (test est_permut 100 2000) ();;
timeit (test est_permut 100 4000) ();;
Printf.printf "\n\n\n";;
flush_all();;
Out[27]:
Out[27]:
Out[27]:
Out[27]:
Out[27]:
On peut afficher ces trois valeurs, juste pour mieux les visualiser :
In [37]:
let trouve_omega (m : mot) : mot =
let p = Array.length m in
let omega = Array.make p 0 in
for i=0 to (p-1) do
omega.(i) <- (i + m.(i)) mod p
done;
omega;;
Out[37]:
In [36]:
trouve_omega [|4;4;1|];;
trouve_omega [|5;3;4;0|];;
Out[36]:
Out[36]:
Note : on peut être plus rapide avec une fonction Array.init
au lieu de Array.make
:
In [32]:
Array.make;;
Array.init;;
Out[32]:
Out[32]:
In [38]:
let trouve_omega (m : mot) : mot =
let p = Array.length m in
Array.init p (fun i -> ((i + m.(i)) mod p))
;;
Out[38]:
In [ ]:
trouve_omega [|4;4;1|];;
trouve_omega [|5;3;4;0|];;
Out[ ]:
Out[ ]:
On a d'abord besoin de convertir un caractère comme '5'
en un entier 5
.
Cela peut se faire manuellement comme ça :
In [ ]:
let entier (s : char) : int = match s with
| '0'-> 0
| '1'-> 1
| '2'-> 2
| '3'-> 3
| '4'-> 4
| '5'-> 5
| '6'-> 6
| '7'-> 7
| '8'-> 8
| '9'-> 9
;;
Out[ ]:
In [ ]:
let entier (s : char) : int =
(int_of_char s) - (int_of_char '0')
;;
Out[ ]:
Pour la transformation de la chaîne en un mot, on peut le faire manuellement comme ça :
In [ ]:
let transfo_mot (m : string) : mot =
let p = String.length m in
let mot_tableau = Array.make p 0 in
for i = 0 to (p - 1) do
mot_tableau.(i) <- entier (m.[i])
done;
mot_tableau;;
(* Complexité temporelle et spatiale en O(p) *)
Out[ ]:
In [ ]:
transfo_mot "5241";;
Out[ ]:
Mais on peut aussi accélérer un peu :
In [ ]:
Array.init
Out[ ]:
In [ ]:
let transfo_mot (m : string) : mot =
Array.init (String.length m) (fun i -> (entier m.[i]))
(* Complexité temporelle et spatiale en O(p) *)
Out[ ]:
In [47]:
transfo_mot "5241";;
Out[47]:
In [48]:
let test_permut (m : string) : bool =
est_permut (trouve_omega (transfo_mot m));;
(* Complexité temporelle en O(p) *)
Out[48]:
In [110]:
let test_permut_mot (m : mot) : bool =
est_permut (trouve_omega m);;
(* Complexité temporelle en O(p) *)
Out[110]:
Quelques exemples :
In [49]:
test_permut "433";; (* pas jonglable *)
Out[49]:
In [52]:
test_permut "432";; (* pas jonglable *)
Out[52]:
In [53]:
test_permut "441";; (* jonglable *)
Out[53]:
In [50]:
test_permut "5241";; (* jonglable *)
Out[50]:
In [51]:
test_permut "9340";; (* jonglable *)
Out[51]:
In [67]:
let test_moyenne_mot (m : mot) (b : int) : bool =
let p = Array.length m in
let s = ref 0 in
for i = 0 to (p - 1) do
s:= !s + m.(i)
done;
!s / p = b (* on teste l'égalité *)
;;
Out[67]:
In [68]:
test_moyenne_mot [|5;4;3;0|] 3;;
Out[68]:
On va finir par quelques exemples :
In [69]:
let test_moyenne (m : string) (b : int) : bool =
test_moyenne_mot (transfo_mot m) b;;
Out[69]:
In [70]:
test_moyenne "433" 3;;
Out[70]:
In [71]:
test_moyenne "443" 3;;
Out[71]:
In [72]:
test_moyenne "432" 3;;
Out[72]:
In [73]:
test_moyenne "5430" 3;;
Out[73]:
La fonction suivante donne la liste des mots de taille p
commençant par debut
(dont la somme des coefficients vaut somme
) concaténée avec acc
, en temps $\mathcal{O}(m^p)$.
In [114]:
let rec partition_aux balles p debut somme acc =
let m = balles * p in
let manque = Array.fold_left (fun c x -> if x = (-1) then c + 1 else c) 0 debut in
if manque = 1 then
let t = Array.copy debut in
t.(p - 1) <- m - somme;
t :: acc
else
let nacc = ref acc in
for k = m - somme downto 0 do
let ndebut = Array.copy debut in
ndebut.(p - manque) <- k;
nacc := partition_aux balles p ndebut (somme + k) !nacc
done;
!nacc
;;
Out[114]:
In [115]:
let partition balles p =
let debut = Array.make p (-1) in
partition_aux balles p debut 0 []
;;
Out[115]:
In [116]:
let bp balles p = List.filter test_permut_mot (partition balles p);;
Out[116]:
Par exemples :
In [117]:
partition 3 3;;
Out[117]:
In [118]:
bp 3 3;;
Out[118]:
On voit que l'on retrouve les mots donnés en exemples dans le texte, 441, 423, 333 par exemple :
In [124]:
List.mem [|4; 4; 1|] (bp 3 3);;
List.mem [|4; 2; 3|] (bp 3 3);;
List.mem [|3; 3; 3|] (bp 3 3);;
Out[124]:
Out[124]:
Out[124]:
Et le mot 433 n'est pas dans la liste calculée :
In [125]:
List.mem [|4; 3; 3|] (bp 3 3);;
Out[125]:
Pour $m=4$, on commence à avoir beaucoup plus de réponses :
In [121]:
partition 4 4;;
List.length (partition 4 4);;
Out[121]:
Out[121]:
In [123]:
bp 4 4;;
List.length (bp 4 4);;
Out[123]:
Out[123]:
Voilà pour les deux questions obligatoires de programmation :
Bien-sûr, ce petit notebook ne se prétend pas être une solution optimale, ni exhaustive.