Travaux Pratiques #3
Algo & Prog avec R
Table des matières
1. Factorielles
Les choix algorithmiques et d’implémentation que vous ferez influent sur la qualité du programme en terme de complexité et donc de vitesse d’exécution.
Fonctions factorielle
Programmez plusieurs fonctions prenant un entier positif n
et retournant la factorielle n! de n. Par exemple, 5! est égal à 120.
- Par récurrence, sous la forme d’une fonction
FactRec(n)
, en supposant le problème résolu pour n-1. VérifierFactRec(5)
grâce à la fonction prédéfiniefactorial
.FactRec <- function(n) { if(n > 1) return(FactRec(n-1) * n) else return (1) } cat('FactRec(0) =',FactRec(0), '\n') cat('FactRec(5) =',FactRec(5), '\n')
FactRec(0) = 1 FactRec(5) = 120
- Par une itération (boucle while) sous la forme d’une fonction
Fact(n)
.Fact <- function(n) { acc <- 1; while(n > 1) { acc <- acc * n n <- n - 1 } return(acc) } cat('FactRec(0) =',FactRec(0), '\n') cat('FactRec(5) =',FactRec(5), '\n')
FactRec(0) = 1 FactRec(5) = 120
- R est-il capable de calculer 1000! ?
factorial(1000)
[1] Inf
- Pour chronométrer un calcul en R, il suffit d’appeler la fonction
system.time
:system.time(replicate(1000,factorial(100)))
. Qui est le plus rapide entreFact(100)
etFactRec(100)
?## On apprendra plus tard à écrire une version vectorisée FactVec <- function(n) { return(prod(1:n)) } cat('Test : FactRec(100)\n') system.time(replicate(2000, FactRec(100))) cat('Test : Fact(100)\n') system.time(replicate(2000, Fact(100))) cat('Test : FactVec(100)\n') system.time(replicate(2000, FactVec(100))) cat('Test : factorial(100)\n') system.time(replicate(2000, factorial(100)))
Test : FactRec(100) utilisateur système écoulé 0.166 0.000 0.165 Test : Fact(100) utilisateur système écoulé 0.011 0.000 0.011 Test : FactVec(100) utilisateur système écoulé 0.004 0.000 0.004 Test : factorial(100) utilisateur système écoulé 0.001 0.000 0.001
Petites factorielles
Ecrivez une fonction itérative AfficherFact(n)
faisant afficher toutes les factorielles des entiers de 0 à 10, une par ligne.
- En utilisant la fonction
Fact
ouFactRec
, combien votre programme fera-t-il de multiplications ?cat('Voici les factorielles des entiers de [1,10] :\n') i <- 1 while (i <= 10) { ## la boucle d'iteration effectue en tout 1+2+3+...+10 = 55 multiplications cat(i,'! =', Fact(i), '\n') i <- i + 1 } ## Si l'on remplace 10 par N, on obtiendrait N(N+1)/2 multiplications, polynome du ## second degre. On dit que l'algorithme a un cout QUADRATIQUE. Pas fameux...
Voici les factorielles des entiers de [1,10] : 1 ! = 1 2 ! = 2 3 ! = 6 4 ! = 24 5 ! = 120 6 ! = 720 7 ! = 5040 8 ! = 40320 9 ! = 362880 10 ! = 3628800
- Sans utiliser cette fonction. Tâchez de faire baisser le nombre de multiplications ! Combien votre programme fera-t-il de multiplications ?
## Si l'on veut faire tomber le nombre de multiplications, il ne FAUT PAS ## utiliser une fonction factorielle qui repart chaque fois du debut, mais maintenir ## dans une variable f la derniere factorielle calculee ! AfficherFact <- function(n) { cat('Voici les factorielles (rapides) des entiers de [1,", n, "] :\n', sep = "") i <- 1 acc <- 1 while (i <= n) { ## la boucle effectue en tout 10 multiplications !!! cat(i,'! =', acc, '\n') i <- i + 1 acc <- acc * i } ## Le cout d'affichage des factorielles des entiers de [1,N] est donc maintenant de N, ## le cout est devenu LINEAIRE. Difficile de faire mieux dans le cas present... } AfficherFact(10)
Voici les factorielles (rapides) des entiers de [1,10] : 1 ! = 1 2 ! = 2 3 ! = 6 4 ! = 24 5 ! = 120 6 ! = 720 7 ! = 5040 8 ! = 40320 9 ! = 362880 10 ! = 3628800
2. Quelques fonctions prédéfinies pour les conversions
L’exercice suivant est un complément de cours. Cherchez la documentation sur les fonctions intToBits
, strtoi
, et as.hexmode
.
- Comment demande-t-on au toplevel R de voir l’écriture binaire (base 2) de 233 ? Que remarquez-vous ?
intToBits(233)
- Sur papier, quel est le résultat de l’addition 11010 + 10111 en binaire ? Vérifiez votre réponse au toplevel.
strtoi("11010", base = 2) + strtoi("10111", base = 2)
- Quelle est l’écriture hexadécimale (base 16) de l’entier qui s’écrit 164 en décimal ? Vérifiez-le au toplevel.
as.hexmode(164)
- Sur papier, quel est le résultat de l’addition 3F + A2 en hexadécimal ? En binaire ? Vérifiez votre réponse au toplevel.
as.hexmode("3F") + as.hexmode("A2") as.integer(as.hexmode("3F") + as.hexmode("A2"))
3. Épluchages d’entiers KEY
En utilisant l’idée d’épluchage d’un entier, programmez les fonctions suivantes.
Somme des chiffres d’un nombre
SomCh <- function(n, base=10) { n <- abs(n); base <- abs(base) acc <- 0; while(n > 0) { acc <- acc + n %% base; n <- n %/% base; } return(acc) } SomChBin <- function(n) SomCh(n, base = 2)
- La fonction
SomCh(n)
prenant un entiern
, et retournant la somme des chiffres den
en base 10.SomCh(3456)
[1] 18
- La fonction
SomChBin(n)
retournant cette fois la somme des chiffres den
en binaire.SomChBin(3456)
[1] 4
- Généraliser en une fonction
SomCh(n, base)
retournant la somme des chiffres du nombre pour une base quelquonque en ajoutant un second paramètrebase
.as.hexmode(3456) SomCh(3456, base = 16)
[1] "d80" [1] 21
Renversement d’un nombre
Renverser <- function(n, base = 10) { n <- abs(n); acc <- 0; while(n > 0) { acc <- acc * base + n %% base; n <- n %/% base; } return(acc) }
- La fonction
Renverser(n)
prenant un entier positifn
et retournant l’entier obtenu en prenant les chiffres den
en sens inverse.Renverser(3456) Renverser(34560)
[1] 6543 [1] 6543
- La fonction
Renverser(n, base)
prenant un entier positifn
et retournant l’entier obtenu en prenant les chiffres den
en baseb
en sens inverse.## 3456 en décimal devient 110110000000 en binaire ## qui se renverse en (0000000)11011 en binaire soit 27 en décimal Renverser(3456, base = 2) Renverser(as.hexmode("ABC"), base = 16)
[1] 27 [1] "cba"
4. Jeu de hasard KEY
Virginie lance trois dés numérotés de 1 à 6.
- Si elle obtient une somme de 18, elle gagne 50 euros,
- entre 10 et 17, elle gagne 5 euros,
- sinon elle ne gagne rien.
- Écrivez une fonction
JeuHasard
utilisant la fonctionsample
pour simuler un lancer de dés, puis renvoyant le gain.Pour faire la somme des valeurs renvoyées par
sample
, utilisez la fonctionsum
ainsi :sum(sample(...))
.JeuHasard <- function() { somme <- sum(sample(1:6, 3, replace=TRUE)) if(somme <10) {return(0)} else if(somme <18) {return(5)} else {return(50)} }
- Écrire une simulation où Virginie joue jusqu’à ce que son gain total dépasse 50.
gain <- 0 partie <- 0 while(gain < 50) { gain <- gain + JeuHasard() partie <- partie + 1 cat("Partie", partie, ":", gain, "\n") }
Partie 1 : 5 Partie 2 : 5 Partie 3 : 5 Partie 4 : 10 Partie 5 : 10 Partie 6 : 10 Partie 7 : 10 Partie 8 : 15 Partie 9 : 20 Partie 10 : 25 Partie 11 : 30 Partie 12 : 30 Partie 13 : 35 Partie 14 : 40 Partie 15 : 45 Partie 16 : 50
- Quelle est la probabilité de gagner 50 euros ? Quelle est l’espérance de gain ? Proposer un tarif pour jouer à ce jeu ? Justifier.
## Estimation de l'espérance par simulation n <- 10000 gains <- replicate(n, JeuHasard()) cat("Esperance simulée :", sum(gains)/n, "\n") ## Calcul théorique de l'espérance prob50 <- 1 / 6**3 tirages <- expand.grid(1:6, 1:6, 1:6) sommes <- rowSums(tirages) prob5 <- (sum(sommes >= 10) - 1)/ nrow(tirages) cat("Esperance théorique", 50*prob50 + 5*prob5, '\n')
Esperance simulée : 3.3375 Esperance théorique 3.333333
5. Algorithme d’Euclide UCANCODE
Lisez le début de la page wikipedia du Plus Grand Diviseur Commun (PGCD), puis attentivement la section sur l’algorithme d’Euclide. Calculer le PGCD de 8 et 20 par la méthode soustractive, puis par divisions.
Nous allons implémenter plusieurs fonctions pour le calcul du PGCD de deux entiers relatifs. Nous utiliserons la définition du plus grand au sens de la divisibilité.
Méthode soustractive
Le PGCD de deux entiers a et b est aussi celui de a et de a - b.
- Programmez une fonction récursive
PgcdSubRec(a,b)
- Programmez une fonction itérative
PgcdSubIter(a,b)
PgcdSubRec <- function(a, b) { ExecPgcdSubRec <- function(a, b) { if(a > b) ifelse(b == 0, a, ExecPgcdSubRec(b, a)) else ifelse(a == 0, b, ExecPgcdSubRec(a, b - a)) } return(ExecPgcdSubRec(abs(a), abs(b))) } PgcdSubIter <- function(a, b) { a <- abs(a) b <- abs(b) if(a == 0) return(b) while(b != 0) { ## cat(a, b, "\n") if(b >= a) { b <- b - a } else { a <- a - b } } return(a) }
La fonction TestPGCD
effectue des tests simples pour vérifier que le résultat est correct.
TestPGCD <- function(pgcd) { system.time( stopifnot( pgcd(12,8) == 4, pgcd(8,12) == 4, pgcd(87,116) == 29 ## Ajouter des tests ## ... )) }
Il faut passer en paramètre la fonction à tester.
TestPGCD(PgcdSubRec) TestPGCD(PgcdSubIter)
Ajouter des tests à la fonction pour vérifier des cas généraux (premiers entre eux ou pas, négatifs, etc) et des cas limites (0, 1, etc).
TestPGCD <- function(pgcd) { system.time( stopifnot( ## identité pgcd(12,12) == 12, ## non premiers entre eux pgcd(12,8) == 4, pgcd(8,12) == 4, ## nombres négatifs pgcd(-8,12) == 4, pgcd(8,-12) == 4, pgcd(-8,-12) == 4, ## premiers entre eux pgcd(27,64) == 1, pgcd(64,27) == 1, ## nombres négatifs pgcd(-64,27) == 1, pgcd(64,-27) == 1, pgcd(-64,-27) == 1, ## avec 1 pgcd(64,1) == 1, pgcd(1,64) == 1, pgcd(1,1) == 1, ## avec 0 pgcd(63,0) == 63, pgcd(0,63) == 63, pgcd(0,0) == 0 )) }
Méthode par divisions
Le PGCD de deux entiers a et b est aussi celui de a et du reste de la division de b par a.
- Programmez une fonction récursive
PgcdDivRec(a,b)
- Programmez une fonction itérative
PgcdDivIter(a,b)
PgcdDivRec <- function(a,b) { ExecPgcdSubRec <- function(a, b) ifelse(b == 0, a, PgcdDivRec(b, a %% b)) ExecPgcdSubRec(abs(a), abs(b)) } PgcdDivIter <- function(a, b) { a <- abs(a) b <- abs(b) while(b != 0) { ## cat(a, b, "\n") r <- a %% b a <- b b <- r } return(a) }
N’oubliez pas de tester vos fonctions.
TestPGCD(PgcdDivRec) TestPGCD(PgcdDivIter)
La fonction PerfPGCD
mesure le temps total d’exécution d’une fonction pour des cas de test avec différents ordres de grandeur.
PerfPGCD <- function(pgcd) { n <- 2**seq(1, 31, 3) n <- c(n-1, n) df <- expand.grid(a = n, b = n) ## print(df) ## afficher les cas de tests system.time(apply(df, 1, function(x) pgcd(x[1], x[2]))) }
Comparez les performances des différentes fonctions et interprétez les résultats.
PerfPGCD(PgcdDivRec) PerfPGCD(PgcdDivIter)
utilisateur système écoulé 0.025 0.000 0.025 utilisateur système écoulé 0.006 0.000 0.006
Fraction irréductible
Comment feriez-vous pour savoir si la fraction 51/85 est irréductible ? En d’autres termes, peut-on la simplifier ? Par combien ?
Programmez une fonction AfficherFraction(a,b)
qui prend en paramètres deux entiers a
et b
représentant une fraction a/b
et affiche sa fraction irréductible c/d
.
AfficherFraction <- function(a, b) { d <- PgcdDivIter(a, b) cat(sprintf("%d/%d = %d/%d\n", a, b, a/d, b/d)) }
AfficherFraction(58,87)
58/87 = 2/3
Exercice UCAnCODE
Vous avez maintenant confiance dans la correction et l’efficacité de votre programme de calcul du PGCD. Allez résoudre l’exercice UCAnCODE !
6. Représentation des nombres en machines
La fonction typeof
renvoie le type d’un objet.
typeof(2105)
[1] "double"
la reponse du « top level » est interessante.
- Qu’est ce qu’un double en R ?
double fait partie des 6 basic atomic vector types de R. donc 2015 est un vector (des cellules contigues) d’une seule cellule.
- Pourquoi ca rend double ?
Voir la réponse ici.
- Comment travailler avec un entier ?
typeof(2015L) v <- 2015 typeof(as.integer(v))
[1] "integer" [1] "integer"
- Comment sont représentés les entiers en machine ?
intToBits(2015)
[1] 01 01 01 01 01 00 01 01 01 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [26] 00 00 00 00 00 00 00
Les entiers sont représentés dans un système binaire (base 2). Le système binaire le plus courant est l’équivalent en base deux de la numération de position que nous utilisons en base dix dans la vie courante.
- les objets de base de R sont les vecteurs.
Même un entier « tout seul » est représenté par un vecteur … d’une seule cellule. C’est comme ça : basic types ; expressions.