mardi 20 juillet 2021
Nous utilisons fréquemment des boucles for
imbriquées. Il arrive que la profondeur de ces imbrications soit trop grande (ou qu'elle soit variable) pour pouvoir les écrire en dur. Ici je donne ma solution en C et en python.
Il s'agit ici d'afficher (et accessoirement dénombrer) les tirages de p numéros parmi n sans remise et en tenant compte de l'ordre. Pour p raisonnable on écrira un code qui ressemble à :
for(int i=0; i<n; i++)
for(int j=0; j<n; i++)
for(int k=0; k<n; k++)
...
Mais si p dépasse 3 ou 4, ça devient lassant. Pire, si p n'est défini qu'à l'exécution ça devient impossible, à moins d'utiliser des tableaux de variables d'indice dans les boucles for
. C'est ce que je propose ici.
/* ex1.c.c
:w | !gcc % -o .%<
./.%<
*/
#include <stdio.h>
#include <stdlib.h>
/*
Tirage de p nombres parmi {0, 1, ... , n - 1} avec remise
Affiche tous les tirages
Exemple pour p = 3 et n = 5 affiche
(0, 0, 0) (0, 0, 1) (0, 0, 2) (0, 0, 3) ... (4, 4, 3) (4, 4, 4)
Retourne le nombre de tirages : n^p
Remplace :
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
for(int k=0; k<n; k++)
...
...
avec p boucles for
*/
int ex1(int p, int n);
void print(int p, int *v);
int main(int argc, char *argv[])
{
printf("Tirages de p nombres parmi n AVEC remise\n");
int p = 3, n = 5, c; // exemple
printf("p = %d, n = %d\n", p, n);
c = ex1(p, n); // nombre de tireages et affichage
printf("\nNombre de tirages (%d^%d): %d\n", n, p, c);
}
void
print(int p, int *v)
{
// affiche un t-uple v sous la forme (v0, v1, ... , vp)
printf("(");
for(int i = 0; i < p - 1; i++) printf("%d, ", v[i]);
printf("%d) ", v[p - 1]);
}
int
ex1(int p, int n)
{
int c = 0, i; // c: compteur, i varable de boucle
int v[p]; // v est une liste de p nombres 0, 1, ... , p
for(i = 0; i < p; i++) v[i] = 0; // initialisation de v à 0
// on quitte dès que le premier terme de la liste dépasse n - 1
while(v[0] < n) {
c++; // compteur
print(p, v);
v[p - 1]++; // on incrémente le dernier terme
for(i = p - 1; i > 0; i--) // propagation du dépassemnt éventuel
if(v[i] == n) { // dépassement
v[i] = 0; // remise à zéro
v[i - 1]++; // propagation
}
}
return c; // nombre de tirages
}
Le code est abondamment commenté. Le code source est là ex1.c.
On peut remplacer p boucles for
imbriquées, où p est un entier supérieur à 2,
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
for(int k=0; k<n; k++)
...
// action
par le code en C :
int v[p], i;
for(i = 0; i < p; i++) v[i] = 0;
while(v[0] < n) {
// action
v[p - 1]++;
for(i = p - 1; i > 0; i--)
if(v[i] == n) {
v[i] = 0;
v[i - 1]++;
}
}
On veut afficher les combinaisons de p entiers parmi n, c'est à dire des tirages sans remise, où l'ordre n'est pas significatif. À la différence de l'exemple précédent on doit éviter les répétitions. Le code source ex2.c que je propose est le suivant :
/* ex2.c
:w | !gcc % -o .%<
./.%<
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
Tirage de p nombres parmi {0, 1, ... , n - 1}
SANS remise et sans tenir compte de l'ordre (combinaisons)
Affiche tous les tirages
Exemple pour p = 3 et n = 5 affiche 012 013 014 023 024 034 123 124 134 234
Retourne le nombre de tirages : nombre de combinaisons de n termes parmi p
Remplace :
for(int i=0; i<n; i++)
for(int j=i+1; j<n; j++)
for(int k=j+1; k<n; k++)
...
...
avec p boucles for
*/
int cnp(int n, int p);
void print(int p, int v[]);
int main(int argc, char *argv[])
{
printf("Affichage des combinaisons de p termes parmi n\n");
int p = 3, n = 5, c;
printf("p = %d, n = %d\n", p, n);
c = cnp(n, p);
printf("Nombre de combinaisons de %d termes parmi %d: %d\n", p, n, c);
}
int
cnp(int n, int p)
{
int i, c = 0; // indice de boucle for, compteur
int v[p]; // résultat d'un tirage sous forme de liste de n nombres
for(i = 0; i < p; i++) // initialisation de v à 0, 1, ... , n - 1
v[i] = i;
// On quitte dès que le premier terme atteint n - 1
while(v[0] <= p - 1) {
c++;
print(p, v); // affichage de la liste
// marche arrière jusqu'au premier terme inférieur à n - 1
for(i = p - 1; i >= 0 && v[i] == n + i - p; i--);
v[i++]++; // incrémentation du terme et de l'indice de parcours
for( ; i < p; i++) // propagation
v[i] = v[i - 1] + 1; // par des termes successifs
}
puts("");
return c; //compteur
}
void
print(int p, int v[])
{
// affichage de v sous forme de liste d'ensembles : ... {0, 2, 4} ...
printf("{");
for(int i = 0; i < p - 1; i++)
printf("%d, ", v[i]);
printf("%d} ", v[p - 1]);
}
On peut remplacer les p boucles for
(où p est un entier supérieur à 2) :
for(int i=0; i<n; i++)
for(int j=i+1; j<n; j++)
for(int k=j+1; k<n; k++)
...
// action
par le code C :
int v[p];
for(i = 0; i < p; i++)
v[i] = i;
while(v[0] <= p - 1) {
// action
for(i = p - 1; i >= 0 && v[i] == n + i - p; i--);
v[i++]++;
for( ; i < p; i++)
v[i] = v[i - 1] + 1;
}
Voici les transcriptions des programmes précédents en téléchargement ;