Les tests sont écrits avec assert (il faut #include <assert.h>) et sont à
placer dans la fonction main. On aura aussi besoin de stdio.h et de
string.h.
Conversions binaires¶
On considère l'écriture binaire \(n = \sum_{k=0}^{+\infty} b_k 2^k\) d'un entier naturel \(n\), où les \(b_k \in \{0, 1\}\) sont presque tous nuls. En pratique, on ne considère que des nombres dont la taille est majorée, donc de la forme \(\sum_{k=0}^{N-1} b_k 2^k\). \(N\) est le nombre de bits sur lequel on a écrit l'entier \(n\). Le bit \(b_{N-1}\) peut être nul, c'est-à-dire qu'on n'impose pas que \(b_{N-1} \neq 0\) ; par contre, on a forcément \(n < 2^N\).
Les différents types de valeurs entières en C sont de la forme t ou
unsigned t où :
unsignedsignifie qu'on ne peut stocker que des valeurs positives ; si on l'omet on peut avoir des valeurs négatives, sachant qu'il faut un bit pour coder l'information du signe : un même type peut prendre des valeurs deux fois plus grandes enunsigned;-
tpeut valoir (types principaux) ATTENTION : c'est dépendant du compilateur, on donne ici les valeurs usuelles pour un compilateur moderne et un processeur 64 bits ! :char: pour des valeurs sur \(N=8\) bits, donc 1 octet, donc de \(0\) à \(255\) enunsigned(de \(-128\) à \(127\) sinon) ;short: pour des valeurs sur \(N=16\) bits, 2 octets, donc de \(0\) à \(2^{16}-1\) enunsigned;int: pour des valeurs sur \(N=32\) bits, donc 4 octets, donc de \(0\) à \(2^{32}-1\) enunsigned;long: pour des valeurs sur \(N=64\) bits, donc 8 octets, donc de \(0\) à \(2^{64}-1\) enunsigned.
En version signée, un type sur \(N\) bits va de \(-2^{N-1}\) à \(2^{N-1}-1\).
Dans le doute, on peut connaître la taille en nombre d'octets en
utilisant sizeof : sizeof(long) vaut 8.
Il est aussi possible d'utiliser des types explicites comme int32_t
ou int64_t (dans stdint.h).
Question 1
Écrire une fonction void vers_binaire(unsigned int x, unsigned char b[]).
On considère ici que b est un tableau de 32 unsigned char ; cette
fonction renvoie dans b la représentation en binaire de x de sorte
que b[i] contienne \(b_i\) de l'expression au-dessus (comme toujours, voir
les tests en cas de doute).
Tests :
unsigned char b[32];
unsigned char out[] = { 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
vers_binaire(123457, b);
for(int i = 0; i < 32; i++)
{
assert(b[i] == out[i]);
}
vers_binaire(4294967295, b);
for(int i = 0; i < 32; i++)
{
assert(b[i] == 1);
}
Corrigé
void vers_binaire(unsigned int x, unsigned char b[])
{
for (int i = 0; i < 32; i++)
{
b[i] = x % 2;
x = x / 2;
}
}
Question 2
Écrire une fonction unsigned int depuis_binaire(unsigned char b[]) qui
renvoie l'entier représenté par le tableau de 32 bits b. On fera en
sorte que depuis_binaire et vers_binaire soient réciproques l'une de
l'autre.
Tests :
unsigned char b[] = { 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
assert(depuis_binaire(b) == 123457);
unsigned char c[32];
for (int i = 0; i < 32; i++) c[i] = 1;
assert(depuis_binaire(c) == 4294967295);
Corrigé
unsigned int depuis_binaire(unsigned char b[])
{
unsigned int x = 0;
unsigned int p = 1;
for (int i = 0; i < 32; i++)
{
if (b[i] == 1)
{
x = x + p;
}
p = 2*p;
}
return x;
}
Retour sur les chaînes¶
On rappelle qu'une chaîne de caractères en C est juste un tableau de
char. Les char sont des valeurs entières sur un octet (8 bits). En
pratique, on ne s'intéressera pas à la valeur entière mais uniquement à
leur représentation sous forme de caractère, donc on écrira 'a' et pas
97 bien que ce soit la même valeur : 'a' == 97 est vrai.
On ne considère ici que les caractères ASCII dont la valeur est comprise
entre 0 et 127. ATTENTION : comme les char sont limités, on ne peut
pas écrire for(char i = 0; i < 128; i++) car, sur la plupart des machines où
char va de \(-128\) à \(127\), la comparaison avec 128 est toujours vraie : la
boucle ne termine pas.
Les chaînes finissent toutes par '\0' et donc on peut passer des
chaînes en argument sans se soucier de donner leur taille grâce à ce
caractère spécial. On appelle ça une sentinelle.
Pour faire une boucle dans un tableau de taille n, on écrit
for(i = 0; i < n; i++) mais ici, comme on ne connaît pas n, il faut
utiliser la sentinelle, par exemple en écrivant
for(i = 0; s[i] != '\0'; i++).
Question 3
Écrire une fonction int longueur_chaine(char s[]) qui renvoie la longueur
de la chaîne s.
Tests :
assert(longueur_chaine("test") == 4);
assert(longueur_chaine("") == 0);
Corrigé
int longueur_chaine(char s[])
{
int i;
for (i = 0; s[i] != '\0'; i++)
{
}
return i;
}
Cette fonction, comme d'autres sur les chaînes, est définie directement
dans la bibliothèque standard du C (en-tête string.h) : c'est strlen. On
pourra donc l'utiliser dans la suite pour pouvoir faire des boucles avec la
taille comme dans un tableau usuel.
Il est probable que pour écrire la fonction suivante vous souhaitiez
pouvoir sortir prématurément d'une boucle comme en Python. C'est
également possible en C, on peut utiliser :
break;pour sortir de la boucle ;continue;pour finir l'itération et passer à l'itération suivante.
Question 4
Écrire une fonction int recherche_motif(char s[], char m[]) qui renvoie
l'indice de début de la première occurrence du motif m dans s s'il est
présent, ou -1 sinon.
Tests :
char s[] = "tetututo";
assert(recherche_motif(s, "tu") == 2);
assert(recherche_motif(s, "to") == 6);
assert(recherche_motif(s, "ti") == -1);
Corrigé
int recherche_motif(char s[], char m[])
{
int n = strlen(s);
int p = strlen(m);
for (int i = 0; i <= n-p; i++)
{
int j;
for (j = 0; j < p; j++)
{
if (s[i+j] != m[j])
{
break;
}
}
if (j == p)
{
return i;
}
}
return -1;
}
En sortie de la boucle sur j, on a j == p exactement quand on n'est
pas sorti par le break, c'est-à-dire quand le motif est présent à
l'indice i. Si le motif est plus long que la chaîne, n-p < 0 et on
renvoie directement -1. Cet algorithme naïf est en \(O(np)\) dans le pire
cas.
Question 5
Écrire une fonction void occurrences(char s[], int occ[]) qui renvoie dans
le tableau occ, contenant 128 valeurs NON INITIALISÉES, les nombres
d'occurrences de chaque caractère dans la chaîne s.
Tests :
int occ[128];
occurrences("test", occ);
for(int i = 0; i < 128; i++)
{
if (i == 't') assert(occ[i] == 2);
else if (i == 'e' || i == 's') assert(occ[i] == 1);
else assert(occ[i] == 0);
}
Corrigé
void occurrences(char s[], int occ[])
{
for (int i = 0; i < 128; i++)
{
occ[i] = 0;
}
for (int i = 0; s[i] != '\0'; i++)
{
occ[(unsigned char) s[i]]++;
}
}
La conversion en unsigned char évite un indice négatif si char est
signé ; on suppose de plus que la chaîne ne contient que des caractères
ASCII, sinon l'indice peut dépasser 127.
Automates de Wolfram¶
Présentation et premier automate¶
On va s'intéresser ici à des automates cellulaires unidimensionnels. On
considère ainsi une ligne infinie de cellules. Chaque cellule a
exactement deux cellules voisines, une à sa droite et une à sa gauche.
Les cellules contiennent un état. Pour simplifier ici, on ne va
considérer que deux états 1 et 0, noir ou blanc, ou encore '#' et ' '
dans la suite.
Afin de représenter informatiquement de telles lignes infinies, on va considérer un nombre fini de cellules en supposant que toutes les autres sont dans l'état 0.
Un automate de Wolfram est la donnée d'un ensemble de règles de la forme
ce qui peut se lire sous la forme \((g,c,d) \rightarrow c'\) : connaissant l'état \(g\) de la cellule de gauche, \(c\) de la cellule courante et \(d\) de la cellule de droite, le nouvel état de la cellule courante est \(c'\).
Question 6
Écrire une fonction int regle_vois(int g, int c, int d) qui prend un
triplet \((g,c,d)\) sous la forme de valeurs 0 ou 1 et renvoie \(c'\) selon la
règle au-dessus en image, selon la correspondance case pleine (bleue) = 1, case vide = 0.
Tests :
assert(regle_vois(0,0,0) == 0);
assert(regle_vois(0,0,1) == 1);
assert(regle_vois(0,1,0) == 1);
assert(regle_vois(0,1,1) == 1);
assert(regle_vois(1,0,0) == 1);
assert(regle_vois(1,0,1) == 0);
assert(regle_vois(1,1,0) == 0);
assert(regle_vois(1,1,1) == 0);
Corrigé
int regle_vois(int g, int c, int d)
{
if (g == 1 && c == 1 && d == 1)
return 0;
if (g == 1 && c == 1 && d == 0)
return 0;
if (g == 1 && c == 0 && d == 1)
return 0;
if (g == 1 && c == 0 && d == 0)
return 1;
if (g == 0 && c == 1 && d == 1)
return 1;
if (g == 0 && c == 1 && d == 0)
return 1;
if (g == 0 && c == 0 && d == 1)
return 1;
return 0;
}
On voit dans l'exemple précédent qu'il y a 8 cas de figure ; on peut les numéroter de 0 à 7. En fait, on peut considérer l'entier \(4g + 2c + d\) pour associer à un tel triplet ce numéro. Par exemple, la configuration \((1,1,0)\) correspond à \(4 + 2 = 6\).
Question 7
Écrire une fonction int regle(int config) qui prend le triplet sous la
forme d'un numéro comme décrit au-dessus et renvoie la nouvelle valeur.
On ne fera PAS appel à la fonction regle_vois précédente.
Tests :
int attendu[8] = { 0, 1, 1, 1, 1, 0, 0, 0 };
for (int k = 0; k < 8; k++)
{
assert(regle(k) == attendu[k]);
}
Corrigé
int regle(int config)
{
if (config == 0 || config == 5 || config == 6 || config == 7)
return 0;
return 1;
}
Question 8
Écrire une fonction void suivant(char in[], char out[]) qui prend une
configuration in de cellules SOUS LA FORME D'UNE CHAÎNE (donc terminée
par '\0' et dont on peut obtenir la taille avec strlen), applique la
règle précédente sur chacune et stocke la nouvelle configuration dans la
chaîne out DE MÊME LONGUEUR.
ATTENTION : on utilise à la place de 0 le caractère ' ' et à la place de
1 le caractère '#'.
ATTENTION : en dehors du tableau, on considère que les cellules sont à
' '.
Test :
char in[] = " ## ## ## # # # #";
char out[] = " "; // de même longueur
suivant(in, out);
assert(strcmp(out, " ## # ## # # ##### # #") == 0);
Corrigé
void suivant(char in[], char out[])
{
int n = strlen(in);
for(int i = 0; i < n; i++)
{
int config = 0;
if (i > 0 && in[i-1] == '#')
{
config += 4;
}
if (in[i] == '#')
{
config += 2;
}
if (i < n-1 && in[i+1] == '#')
{
config += 1;
}
if (regle(config) == 1)
{
out[i] = '#';
}
else
{
out[i] = ' ';
}
}
}
Question 9
Écrire une fonction void copie_chaine(char tgt[], char src[]) qui prend
en entrée deux chaînes src et tgt de même longueur et copie la chaîne
src dans la chaîne tgt.
Test :
char src[] = "toto";
char tgt[] = "abcd";
copie_chaine(tgt, src);
assert(strcmp(src, tgt) == 0);
Corrigé
void copie_chaine(char tgt[], char src[])
{
int n = strlen(src);
for (int i = 0; i < n; i++)
{
tgt[i] = src[i];
}
}
Question 10
Écrire une fonction
void itere_regle(char depart[], char temp[], char sortie[], int iterations)
qui applique iterations fois la fonction suivant en partant de la
configuration depart et place la configuration finale dans sortie.
Comme on ne sait pas encore allouer une telle chaîne dynamiquement, on
vous donne aussi une chaîne temp comme espace de travail.
ATTENTION : les chaînes depart, temp et sortie sont de même longueur.
Test :
char init[81];
for(int i = 0; i < 80; i++)
{
init[i] = ' ';
}
init[80] = '\0';
char temp[81], sortie[81];
strcpy(temp, init);
strcpy(sortie, init);
init[40] = '#';
itere_regle(init, temp, sortie, 30);
assert(strcmp(sortie, " ## # # ### # ## # # ##### # ###### # # ## # # # ") == 0);
Corrigé
void itere_regle(char depart[], char temp[], char sortie[], int iterations)
{
copie_chaine(sortie, depart);
copie_chaine(temp, depart);
for (int i = 0; i < iterations; i++)
{
suivant(temp, sortie);
copie_chaine(temp, sortie);
}
}
Vous pouvez maintenant profiter de l'affichage des 30 premières itérations :
char init[81];
for(int i = 0; i < 80; i++)
{
init[i] = ' ';
}
init[80] = '\0';
init[40] = '#';
char temp[81], sortie[81];
strcpy(temp, init);
strcpy(sortie, init);
for(int i = 0; i < 30; i++)
{
itere_regle(init, temp, sortie, i);
printf("%s\n", sortie);
}
#
###
## #
## ####
## # #
## #### ###
## # # #
## #### ######
## # ### #
## #### ## # ###
## # # #### ## #
## #### ## # # ####
## # ### ## ## # #
## #### ## ### ### ## ###
## # # ### # ### # #
## #### ## # # ##### #######
Il se trouve que la règle qu'on vient d'implémenter s'appelle la règle 30. En effet, si on construit l'entier \(\sum_{i=0}^7 \mathrm{regle}(i)\, 2^i\), où \(\mathrm{regle}(i)\) a été définie au-dessus, on obtient l'entier :
On va maintenant définir une fonction permettant d'obtenir la règle associée à un nombre entre 0 et 255. Par exemple, comme \(111\) s'écrit \(01101111\) en binaire, cela signifie qu'on associe la valeur 0 aux configurations de numéro 7 et 4 (i.e. \((g,c,d)\) valant \((1,1,1)\) et \((1,0,0)\)) et la valeur 1 à toutes les autres.
Question 11
Écrire une fonction int regle_gen(int gen, int config) qui renvoie la
valeur obtenue en appliquant la règle gen sur la configuration de numéro
config. On pourra utiliser vers_binaire, même si, sur 32 bits,
beaucoup de valeurs seront à 0.
Tests :
assert(regle_gen(30, 2) == 1);
assert(regle_gen(188, 6) == 0);
assert(regle_gen(188, 7) == 1);
assert(regle_gen(188, 2) == 1);
assert(regle_gen(188, 3) == 1);
Corrigé
int regle_gen(int gen, int config)
{
unsigned char r[32];
vers_binaire(gen, r);
return r[config];
}
Question 12
Écrire une fonction void suivant_gen(int gen, char in[], char out[]) qui
fait comme suivant mais en appliquant la règle gen.
REMARQUE : AVEC UN COPIER-COLLER de ce que vous avez fait pour suivant,
ça prend une seconde.
Tests :
char in[] = " ## ## ## # # # #";
char out[] = " "; // de même longueur
suivant_gen(30, in, out);
assert(strcmp(out, " ## # ## # # ##### # #") == 0);
suivant_gen(188, in, out);
assert(strcmp(out, " # # # ## # ## #####") == 0);
Corrigé
void suivant_gen(int gen, char in[], char out[])
{
int n = strlen(in);
for(int i = 0; i < n; i++)
{
int config = 0;
if (i > 0 && in[i-1] == '#')
{
config += 4;
}
if (in[i] == '#')
{
config += 2;
}
if (i < n-1 && in[i+1] == '#')
{
config += 1;
}
if (regle_gen(gen, config) == 1)
{
out[i] = '#';
}
else
{
out[i] = ' ';
}
}
}
Question 13
Écrire une fonction
void itere_regle_gen(int gen, char depart[], char temp[], char sortie[], int iterations)
qui fait comme itere_regle mais en appliquant suivant_gen.
Tests (avec init, temp et sortie comme dans le test de la
question 10) :
itere_regle_gen(30, init, temp, sortie, 30);
assert(strcmp(sortie, " ## # # ### # ## # # ##### # ###### # # ## # # # ") == 0);
itere_regle_gen(188, init, temp, sortie, 30);
assert(strcmp(sortie, " # ### ### ### ### ### ### ### # ") == 0);
Corrigé
void itere_regle_gen(int gen, char depart[], char temp[], char sortie[], int iterations)
{
copie_chaine(sortie, depart);
copie_chaine(temp, depart);
for (int i = 0; i < iterations; i++)
{
suivant_gen(gen, temp, sortie);
copie_chaine(temp, sortie);
}
}
Dernière étape : savourez en affichant les 256 règles sur 30 itérations.
char init[81];
for(int i = 0; i < 80; i++)
{
init[i] = ' ';
}
init[80] = '\0';
init[40] = '#';
char temp[81], sortie[81];
strcpy(temp, init);
strcpy(sortie, init);
for (int i = 0; i < 256; i++)
{
printf("Règle %d\n", i);
for(int j = 0; j < 30; j++)
{
itere_regle_gen(i, init, temp, sortie, j);
printf("%s\n", sortie);
}
printf("\n");
}