Bonjour,
Beaucoup de langages modernes ont une fonctionnalité que je trouve intéressante. Il s’agit des coroutines.
Pour simplifier, une coroutine est une boucle dans laquelle on peut sortir temporairement, sans en perturber la reprise.
Dans la plupart des langages, on remplace ‘return’ par ‘yield’.
Le langage C n’aime pas l’évolution. Je peux le comprendre, car il faut qu’il garde son statut du « plus bas niveau langage de haut niveau ».
L’avantage des coroutines, c’est qu’on peut séparer la génération de la donnée de son traitement.
De plus, on ne limite pas la génération à quelques valeurs, mais on peut faire une boucle infinie sans se soucier de combien il en faut.
C’est un bon moyen de rendre le code réutilisable.
Dans cet article, nous allons voir comment afficher des nombres séquentiellement. Sans coroutine, puis nous allons voir une méthode de transformation.
Voici un exemple d’un affichage des nombres premiers :
#include <malloc.h> #include <stdio.h> typedef struct _Doublet{ int valeur; int compteur; }Doublet; int main(int argc, char *argv[]){ int tabNb=0; Doublet *tab=0; for(int i=2;i<100;i++){ int j; for(j=0;j<tabNb;j++){ while(tab[j].compteur<i){ tab[j].compteur+=tab[j].valeur; } if(tab[j].compteur==i){ //pas premier break; } } if(j==tabNb){ //premier tabNb++; tab=realloc(tab, sizeof(*tab)*tabNb); tab[tabNb-1].valeur=i; tab[tabNb-1].compteur=i; printf("%d\n",i); } } free(tab); tab=0; }
J’ai fais un algorithme qui garde trace des nombres premiers trouvés pour tester les suivants. Rien de bien évolué, mais il y a une structure et un tableau. Ce qui montrera que la transformation ne marchera pas seulement pour les algorithmes triviaux, mais aussi dans les cas plus complexes.
L’étape suivante est de séparer la déclaration des variables, leur initialisation et la destruction dans des blocs distincts.
#include <malloc.h> #include <stdio.h> typedef struct _Doublet{ int valeur; int compteur; }Doublet; int main(int argc, char *argv[]){ //declaration int tabNb; Doublet *tab; int i; int j; //initialisation tabNb=0; tab=0; //traitement for(i=2;i<100;i++){ for(j=0;j<tabNb;j++){ while(tab[j].compteur<i){ tab[j].compteur+=tab[j].valeur; } if(tab[j].compteur==i){ //pas premier break; } } if(j==tabNb){ //premier tabNb++; tab=realloc(tab, sizeof(*tab)*tabNb); tab[tabNb-1].valeur=i; tab[tabNb-1].compteur=i; printf("%d\n",i); } } //destruction free(tab); tab=0; }
Rien de bien sorcier, il faut juste être rigoureux pour ne rien oublier.
Maintenant, il faut mettre toutes les déclarations dans une structure, et utiliser cette structure bien entendu. J’ai appelé cette structure ‘Contexte’
#include #include typedef struct _Doublet{ int valeur; int compteur; }Doublet; typedef struct _Contexte{ int tabNb; Doublet *tab; int i; int j; }Contexte; int main(int argc, char *argv[]){ //init contexte Contexte *c=0; c=malloc(sizeof(*c)); //initialisation c->tabNb=0; c->tab=0; //traitement for(c->i=2;c->i<100;c->i++){ for(c->j=0;c->jtabNb;c->j++){ while(c->tab[c->j].compteuri){ c->tab[c->j].compteur+=c->tab[c->j].valeur; } if(c->tab[c->j].compteur==c->i){ //pas premier break; } } if(c->j==c->tabNb){ //premier c->tabNb++; c->tab=realloc(c->tab, sizeof(*c->tab)*c->tabNb); c->tab[c->tabNb-1].valeur=c->i; c->tab[c->tabNb-1].compteur=c->i; printf("%d\n",c->i); } } //destruction free(c->tab); c->tab=0; //fin contexte free(c); c=0; }
On va créér des fonctions pour chacune des parties du code : initialisation, traitement, destruction. On pourra laisser le compilateur gérer l’init et la destruction de la structure ‘c’.
#include #include typedef struct _Doublet{ int valeur; int compteur; }Doublet; typedef struct _Contexte{ int tabNb; Doublet *tab; int i; int j; }Contexte; void init(Contexte *c){ c->tabNb=0; c->tab=0; } void premier(Contexte *c){ for(c->i=2;c->i<100;c->i++){ for(c->j=0;c->jtabNb;c->j++){ while(c->tab[c->j].compteuri){ c->tab[c->j].compteur+=c->tab[c->j].valeur; } if(c->tab[c->j].compteur==c->i){ //pas premier break; } } if(c->j==c->tabNb){ //premier c->tabNb++; c->tab=realloc(c->tab, sizeof(*c->tab)*c->tabNb); c->tab[c->tabNb-1].valeur=c->i; c->tab[c->tabNb-1].compteur=c->i; printf("%d\n",c->i); } } } void clean(Contexte *c){ free(c->tab); c->tab=0; } int main(int argc, char *argv[]){ Contexte c; //initialisation init(&c); //traitement premier(&c); //destruction clean(&c); }
Enfin, la dernière étape, ajouter un attribut yield, utiliser goto (oui, c’est mauvais, mais c’est ça ou le duff device, beuurk !) et utiliser notre coroutine.
#include #include typedef struct _Doublet{ int valeur; int compteur; }Doublet; typedef struct _Contexte{ int tabNb; Doublet *tab; int i; int j; int yield; }Contexte; void init(Contexte *c){ c->tabNb=0; c->tab=0; c->yield=0; } int premier(Contexte *c){ switch(c->yield){ case 1: goto yield1; } for(c->i=2;;c->i++){ for(c->j=0;c->jtabNb;c->j++){ while(c->tab[c->j].compteuri){ c->tab[c->j].compteur+=c->tab[c->j].valeur; } if(c->tab[c->j].compteur==c->i){ //pas premier break; } } if(c->j==c->tabNb){ //premier c->tabNb++; c->tab=realloc(c->tab, sizeof(*c->tab)*c->tabNb); c->tab[c->tabNb-1].valeur=c->i; c->tab[c->tabNb-1].compteur=c->i; c->yield=1; return c->i; yield1:; } } } void clean(Contexte *c){ free(c->tab); c->tab=0; } int main(int argc, char *argv[]){ Contexte c; //initialisation init(&c); //traitement for(int i=0;i<25;i++){ printf("%d\n",premier(&c)); } //destruction clean(&c); }
Je ne veux pas intégrer ‘init’ et ‘clean’ dans le traitement à cause de ‘clean’ qui ne serait jamais appelé. Et une destruction explicite sans init me semble bizarre.
Voila, on a une coroutine pas trop dégueux à utiliser et qui reste fidèle à l’algorithme de départ.