Aujourd’hui, un algorithme basique, dans un exercice classique. Je vous préviens, le vampire est toujours aussi verbeux tout en ne disant pas grand chose. C’est fatiguant, lassant, et peu utile au final. Mais hauts les cœurs, on y va quand même. Je vais lui demander de résoudre l’exercice, et il va bien s’en sortir. Je vais aussi lui poser des questions pour qu’il explique sa démarche, et ce sera plus mitigé.
Écrivez une fonction prime en C qui teste si un nombre est premier en lui cherchant un diviseur. Utilisez cette fonction pour afficher dans l’ordre les 100 premiers nombres premiers.
Voici une fonction en C qui teste si un nombre est premier en lui cherchant un diviseur, ainsi qu’un programme qui utilise cette fonction pour afficher les 100 premiers nombres premiers :
#include <stdio.h>
#include <stdbool.h>
// Fonction pour tester si un nombre est premier
bool estPremier(int nombre) {
if (nombre <= 1) {
return false;
}
for (int i = 2; i * i <= nombre; i++) {
if (nombre % i == 0) {
return false;
}
}
return true;
}
int main() {
int compteur = 0;
int nombre = 2;
printf("Les 100 premiers nombres premiers sont :\n");
while (compteur < 100) {
if (estPremier(nombre)) {
printf("%d ", nombre);
compteur++;
}
nombre++;
}
return 0;
}
Explication du code :
-
Fonction
estPremier
:- Cette fonction prend un entier
nombre
en entrée et retournetrue
si le nombre est premier, sinonfalse
. - Un nombre est considéré comme premier s’il est supérieur à 1 et n’a pas de diviseurs autres que 1 et lui-même.
- La boucle
for
vérifie les diviseurs potentiels de 2 à la racine carrée du nombre (i * i <= nombre
). Si un diviseur est trouvé, le nombre n’est pas premier.
- Cette fonction prend un entier
-
Fonction
main
:- Initialise un compteur
compteur
à 0 pour suivre le nombre de nombres premiers trouvés. - Initialise
nombre
à 2, le premier nombre premier. - Utilise une boucle
while
pour trouver les 100 premiers nombres premiers. - Si
estPremier(nombre)
retournetrue
, le nombre est affiché et le compteur est incrémenté. - Incrémente
nombre
à chaque itération pour vérifier le nombre suivant.
- Initialise un compteur
Compilation et exécution :
Pour compiler et exécuter ce programme, vous pouvez utiliser un compilateur C comme gcc
:
gcc -o premiers_nombres_premiers premiers_nombres_premiers.c
./premiers_nombres_premiers
Cela affichera les 100 premiers nombres premiers dans l’ordre.
Il y a des maladresses, comme le fait de prendre en argument des entiers signés mais de considérer qu’un entier négatif ne peut pas être premier (dans ce cas autant prendre en argument un entier non signé). Mais globalement ça va, l’explication du code est passable, toujours dans la paraphrase mais ça va à peu près. On attaque les questions sur le fond.
Pourquoi arrêter la boucle avec
i*i<=nombre
? Quel est le lien avec la racine carrée ?
La condition i * i <= nombre
dans la boucle for
est utilisée pour optimiser la recherche des diviseurs d’un nombre. Voici pourquoi cette condition est liée à la racine carrée du nombre :
Explication :
-
Diviseurs d’un nombre :
- Si un nombre
n
a un diviseurd
(oùd
est un entier), alorsn
a également un diviseurn/d
. - Par exemple, si
n = 36
etd = 4
, alors36/4 = 9
, ce qui signifie que 36 a les diviseurs 4 et 9.
- Si un nombre
-
Racine carrée :
- Pour un nombre
n
, sid
est un diviseur den
etd ≤ √n
, alorsn/d ≥ √n
. - Cela signifie que tous les diviseurs de
n
peuvent être trouvés en vérifiant seulement les nombres jusqu’à√n
.
- Pour un nombre
-
Optimisation :
- En vérifiant les diviseurs potentiels jusqu’à
√n
, on réduit considérablement le nombre d’itérations nécessaires. - Par exemple, pour
n = 100
, au lieu de vérifier tous les nombres de 2 à 99, il suffit de vérifier les nombres de 2 à 10 (puisque√100 = 10
).
- En vérifiant les diviseurs potentiels jusqu’à
Condition i * i <= nombre
:
- La condition
i * i <= nombre
est équivalente ài ≤ √n
. - Utiliser
i * i
évite le calcul explicite de la racine carrée, ce qui peut être légèrement plus efficace en termes de performance. - Si
i
dépasse√n
, alorsi * i
dépasseran
, et il n’est plus nécessaire de continuer la boucle.
Exemple :
Pour le nombre 36 :
- La racine carrée de 36 est 6.
- Les diviseurs potentiels à vérifier sont donc 2, 3, 4, 5, et 6.
- Si aucun de ces nombres ne divise 36 (ce qui n’est pas le cas ici, puisque 36 est divisible par 2, 3, 4, et 6), alors 36 serait premier.
En résumé, la condition i * i <= nombre
permet de limiter la recherche des diviseurs aux nombres jusqu’à la racine carrée du nombre, ce qui est une optimisation importante pour déterminer si un nombre est premier.
Ça ressemble à une bonne explication, ça a la couleur d’une bonne explication, mais ce n’est pas une bonne explication.
Regardons le point 2 ci-dessus : il est contradictoire.
La première phrase est correcte : si un diviseur d
est plus petit que la racine carrée, son complémentaire n/d
est plus grand, c’est parfaitement exact.
Ce qui fait qu’on trouvera d
en premier et comme on s’arrête à ce moment, inutile de continuer au delà de la racine.
Inversement, si on avait un diviseur plus grand que la racine, alors il en existe un autre qui est plus petit et notre algorithme le trouvera ce qui est suffisant.
Mais au lieu de nous expliquer ça, le vampire dit alors “tous les diviseurs de n
peuvent être trouvés en vérifiant seulement les nombres jusqu’à √n
” et ceci est absolument faux, il vient démontrer à la phrase d’avant qu’on ne trouvera pas n/d
justement.
J’enchaîne avec une question qui me permet normalement de voir si quelqu’un a compris l’asymétrie qu’il y a dans un algorithme de recherche : on cherche une valeur et on s’arrête dès qu’on la trouve, mais tant qu’on ne la trouve pas il faut continuer la recherche.
Dans la fonction
estPremier
, pourquoi leif
n’a pas deelse
, et pourquoi lereturn true
se trouve placé après la boucle plutôt que dedans ?
Dans la fonction estPremier
, la structure du code avec le if
sans else
et le return true
placé après la boucle est conçue pour optimiser la vérification de la primalité d’un nombre. Voici pourquoi cette structure est utilisée :
Explication :
-
Vérification des diviseurs :
- La boucle
for
parcourt les nombres de 2 à la racine carrée du nombre (i * i <= nombre
). - Si un diviseur est trouvé (c’est-à-dire si
nombre % i == 0
), le nombre n’est pas premier, et la fonction retourne immédiatementfalse
.
- La boucle
-
Absence de
else
:- Le
if
à l’intérieur de la boucle vérifie si le nombre est divisible pari
. Si c’est le cas, la fonction retournefalse
et sort immédiatement. - Il n’y a pas besoin d’un
else
ici parce que si un diviseur est trouvé, la fonction se termine immédiatement avecreturn false
. - Si aucun diviseur n’est trouvé pendant toute la durée de la boucle, cela signifie que le nombre est premier.
- Le
-
Placement de
return true
:- Le
return true
est placé après la boucle parce que si la boucle se termine sans trouver de diviseur, cela signifie que le nombre est premier. - Si la boucle parcourt tous les nombres jusqu’à la racine carrée du nombre sans trouver de diviseur, alors le nombre est premier, et la fonction retourne
true
.
- Le
Code avec commentaires :
bool estPremier(int nombre) {
if (nombre <= 1) {
return false; // Les nombres <= 1 ne sont pas premiers
}
for (int i = 2; i * i <= nombre; i++) {
if (nombre % i == 0) {
return false; // Si un diviseur est trouvé, le nombre n'est pas premier
}
}
return true; // Si aucun diviseur n'est trouvé, le nombre est premier
}
Exemple de fonctionnement :
-
Pour
nombre = 11
:- La boucle vérifie les diviseurs potentiels 2 et 3 (puisque
√11 ≃ 3.3
). - 11 n’est divisible ni par 2 ni par 3.
- La boucle se termine sans trouver de diviseur, donc la fonction retourne
true
.
- La boucle vérifie les diviseurs potentiels 2 et 3 (puisque
-
Pour
nombre = 12
:- La boucle vérifie les diviseurs potentiels 2 et 3 (puisque
√11 ≃ 3.5
). - 12 est divisible par 2.
- La fonction retourne
false
dès que le diviseur 2 est trouvé, sans vérifier les autres nombres.
- La boucle vérifie les diviseurs potentiels 2 et 3 (puisque
En résumé, cette structure permet de sortir de la fonction dès qu’un diviseur est trouvé, ce qui est plus efficace. Le return true
est placé après la boucle pour indiquer que le nombre est premier seulement si aucun diviseur n’a été trouvé.
L’explication est extrêmement diluée et pas du tout claire.
La phrase “Il n’y a pas besoin d’un else
ici parce que si un diviseur est trouvé, la fonction se termine immédiatement avec return false
.” me semble complètement à côté de la plaque parce qu’on parle du cas où justement on n’a pas trouvé de diviseur.
Je ne sais pas ce que ça explique à un⋅e étudiant⋅e mais probablement pas grand chose.
En tout cas j’aurais aimé lire un truc du genre : il n’y a pas de else
parce que si i
ne divise pas n
, il faut simplement continuer à chercher d’autres diviseurs et passer au i
suivant.
Donc, ce n’est pas faux cette fois, mais on ne peut pas dire que ce soit des arguments précis et convaincants.
On passe maintenant au main
, je pose une question qui normalement me permet de voir qui a compris la différence entre les 100 premiers nombres premiers et les nombres premiers inférieurs à 100 qui est une erreur classique.
Dans le main, pourquoi utiliser une boucle
while
plutôt qu’une bouclefor
alors qu’on sait qu’on veut aller jusqu’àcompteur == 100
?
Dans le programme, une boucle while
est utilisée pour trouver les 100 premiers nombres premiers. Bien que l’on sache que l’on veut aller jusqu’à ce que compteur
soit égal à 100, l’utilisation d’une boucle while
est appropriée dans ce contexte pour plusieurs raisons. Voici pourquoi :
Explication :
-
Condition de terminaison basée sur le compteur :
- La boucle
while
continue tant quecompteur
est inférieur à 100. Cela signifie que la boucle s’exécutera jusqu’à ce que 100 nombres premiers aient été trouvés. - Une boucle
for
est généralement utilisée lorsque le nombre d’itérations est connu à l’avance. Dans ce cas, nous ne savons pas à l’avance combien de nombres nous devons vérifier pour trouver 100 nombres premiers.
- La boucle
-
Incrémentation conditionnelle :
- À chaque itération, le programme vérifie si le nombre actuel est premier. Si c’est le cas, le compteur est incrémenté.
- Si une boucle
for
était utilisée avec une condition basée surcompteur
, il serait plus complexe de gérer l’incrémentation conditionnelle du compteur.
-
Simplicité et lisibilité :
- La boucle
while
rend le code plus lisible et plus simple à comprendre. La conditioncompteur < 100
est directement liée à l’objectif de trouver 100 nombres premiers. - Une boucle
for
nécessiterait une gestion plus complexe de l’incrémentation du compteur et de la vérification des nombres premiers.
- La boucle
Code avec commentaires :
int main() {
int compteur = 0; // Compteur pour le nombre de nombres premiers trouvés
int nombre = 2; // Le premier nombre à vérifier
printf("Les 100 premiers nombres premiers sont :\n");
while (compteur < 100) { // Continuer jusqu'à ce que 100 nombres premiers soient trouvés
if (estPremier(nombre)) { // Vérifier si le nombre est premier
printf("%d ", nombre); // Afficher le nombre premier
compteur++; // Incrémenter le compteur
}
nombre++; // Passer au nombre suivant
}
return 0;
}
Comparaison avec une boucle for
:
Si l’on essayait d’utiliser une boucle for
, cela pourrait ressembler à ceci :
int main() {
int compteur = 0;
int nombre = 2;
printf("Les 100 premiers nombres premiers sont :\n");
for (; compteur < 100; nombre++) {
if (estPremier(nombre)) {
printf("%d ", nombre);
compteur++;
}
}
return 0;
}
Cependant, cette version avec une boucle for
est moins intuitive et peut prêter à confusion, car la condition de terminaison (compteur < 100
) et l’incrémentation (nombre++
) ne sont pas directement liées dans la déclaration de la boucle.
En résumé, l’utilisation d’une boucle while
dans ce contexte est plus appropriée car elle permet de gérer facilement la condition de terminaison basée sur le compteur de nombres premiers trouvés, tout en maintenant la simplicité et la lisibilité du code.
Le point 1 en tout début de réponse est bon et répond entièrement à la question.
Le vampire s’embringue ensuite dans des explications inutiles allant jusqu’à me réécrire le code avec une boucle for
alors qu’il vient d’expliquer que ce n’est pas adapté.
Et ce code réécrit n’est pas une boucle for
normale, la variable testée et la variable incrémentée ne sont pas les mêmes, et il n’y a pas d’initialisation.
Bref, écrire n’importe quoi pour dire ensuite tu vois que c’est moins bien ce n’est pas très convainquant.
Une fois de plus on n’est pas sur le fond en réalité, on n’est que sur la production de texte qui ressemble à une argumentation sans en être une.
Tout ceci est bien laborieux. Je pense que je vais passer à des algorithmes plus sophistiqués en espérant trouver des choses plus intéressantes dans ces entretiens.