Partage
  • Partager sur Facebook
  • Partager sur Twitter

Exercices pour débutants en C

Au menu : zSommeChiffres (nombres, algo)

6 décembre 2008 à 18:08:46

j'ai beau relire, rerelire etc ... le cours sur les fonctions et les pointeurs mais j'ai du mal à piger si vous pouviez faire quelques exos sur ça et une petite aide je vous serais tres reconnaissant
  • Partager sur Facebook
  • Partager sur Twitter
6 décembre 2008 à 18:50:03

Bonjour,

Citation : Crys

Je posterais la correction de zAddition samedi normalement.


Plus beaucoup de temps avant l'élection de Miss France. :D
Mais bon, il te reste un peu de temps, enfin... normalement. :lol:


  • Partager sur Facebook
  • Partager sur Twitter
6 décembre 2008 à 20:07:00

C'est vrai, cela ne se fait pas de dire qu'on va faire ça à tel moment alors qu'on est pas sûr et puis finalement on ne le fait pas. Vraiment désolé mais manque de temps, je posterais cette correction demain ou mercredi au plus tard !
  • Partager sur Facebook
  • Partager sur Twitter
7 décembre 2008 à 14:48:39

Correction pour zAddition



Envois des résultats à réponse : 5

Vu le nombre d'envois de code, je suppose que cet exercice était un peu trop difficile, ou du moins, vous pensiez qu'il l'était ! En réalité, il n'en est rien et c'était surtout un exercice d'algorithme, ce qui implique qu'il fallait commencer par une partie 'réflexion'. Pour la correction, je vais faire de même. Étudions ensemble le problème !
Si vous ne commencez pas par concevoir avant de coder, vous serez très vite perdus !

L'algorithme



La concaténation de matrices est une chose relativement simple, courante mais qui mérite réflexion, surtout quand on débute en programmation. Supposons que je dispose de deux matrices <math>\(M_1\)</math> et <math>\(M_2\)</math> que l'on peut schématiser sous cette forme pour plus de compréhension :

<math>\(M_1 = \begin{pmatrix}1 & 2 & 3 & 4 \\8 & 9 & 0 & 1 \\5 & 6 & 7 & 8\end{pmatrix}\)</math> et <math>\(M_2 = \begin{pmatrix}5 & 6 & 7 \\2 & 3 & 4 \\9 & 0 & 1\end{pmatrix}\)</math>

Nous aimerions les concaténer afin d'obtenir une nouvelle matrice <math>\(M_3\)</math> :

<math>\(M_3 = \begin{pmatrix}1 & 2 & 3 & 4 & 5 & 6 & 7 \\8 & 9 & 0 & 1 & 2 & 3 & 4 \\5 & 6 & 7 & 8 & 9 & 0 & 1\end{pmatrix}\)</math>

Le nombre de colonnes de <math>\(M_3\)</math> est égal à la somme du nombre de colonnes de <math>\(M_1\)</math> et <math>\(M_2\)</math> et le nombre de lignes de <math>\(M_3\)</math> est égal au nombre de lignes de <math>\(M_1\)</math> qui est aussi égal au nombre de lignes de <math>\(M_2\)</math>.

Notez le contenu de <math>\(M_3\)</math>, c'est comme si nous avions 'collé' <math>\(M_2\)</math> à droite de <math>\(M_1\)</math>. Maintenant réfléchissons ! Pour parvenir à un tel résultat, il faut commencer par parcourir la première ligne de <math>\(M_1\)</math> et une fois arrivé au bout, on enchaîne sur <math>\(M_2\)</math> tout en copiant les valeurs récupérées à la suite dans la première ligne de <math>\(M_3\)</math>. Dés qu'on arrive à la fin de la première ligne de <math>\(M_2\)</math>, on refait exactement de même en recommençant sur la deuxième ligne de <math>\(M_1\)</math>, en enchaînant sur la deuxième ligne de <math>\(M_2\)</math> tout en copiant les valeurs récupérées dans la deuxième ligne de <math>\(M_3\)</math>, et ainsi de suite, jusqu'à ce qu'on arrive au bout de la dernière ligne de <math>\(M_2\)</math>.

Simple, non ? ^^

Le code



Parmi les codes et explications envoyés à réponse, tous suivant à peu de choses près le même principe, j'ai retenu le code d'Eusebus, avec les explications qui vont avec. Il n'y rien à dire, le code est structuré, clair, compréhensible, compile, et laisse à l'utilisateur de choix des dimensions des matrices <math>\(M_1\)</math> et <math>\(M_2\)</math>. Et enfin, cerise sur le gâteau, on doit lancer l'exécutable à l'aide de l'invite de commande en précisant les dimensions souhaitées en paramètre du main (il y a un tutoriel non-officiel qui traite ce sujet -> à lire !). La majorité des programmes 'de pros' en mode console suivent ce principe. L'explication fournie dans le MP d'Eusebus est très complète et précise, c'est pourquoi je me contenterais de la copier ici (j'ai juste ajouté quelques commentaires au code).

Citation : Eusebus

Salut :)

Voici mon code, les explications suivent derrière :

main.c



#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include "main.h"

int main(int argc, char **argv)
{
        matrice_t mat1, mat2, matc;

        srand(time(NULL));

        if(argc < 4)
        {       // L'utilisateur a lancé la console sans l'invite de commande ou a voulu faire le malin
                fprintf(stderr, "Usage : %s <nb_row> <nb_col1> <nb_col2>\n", argv[0]);
                exit(EXIT_FAILURE);
        }
// On récupère les arguments du main :
        mat1.nb_row = mat2.nb_row = atoi(argv[1]);
        mat1.nb_col = atoi(argv[2]);
        mat2.nb_col = atoi(argv[3]);


// On envoie mat1 et mat2 à init pour les remplir de valeurs pseudo-aléatoires :
        init(&mat1);
        init(&mat2);
// L'algorithme est appliqué :
        zaddition(&mat1, &mat2, &matc);
// On affiche le tout :
        printf("Matrice 1 :\n\n");
        affiche(&mat1);
        printf("\nMatrice 2 :\n\n");
        affiche(&mat2);
        printf("\nMatrice résultant de la concaténation :\n\n");
        affiche(&matc);

        liberer(&mat1);
        liberer(&mat2);
        liberer(&matc);

        return 0;
}

void init(matrice_t *mat)
{
        size_t i = 0, j = 0;

        mat->val = malloc(mat->nb_row * sizeof(int *)); Allocation pour les lignes

        for(i = 0; i < mat->nb_row; i++)
        {
                mat->val[i] = malloc(mat->nb_col * sizeof(int)); // Allocation pour les colonnes
                for(j = 0; j < mat->nb_col; j++)
                        mat->val[i][j] = rand() % 10; // nombre pseudo-aléatoire entre 0 et 10
        }
}

void affiche(matrice_t *mat)
{
        size_t i, j;

        for(i = 0; i < mat->nb_row; i++)
        {
                for(j = 0; j < mat->nb_col; j++)
                        printf("%d ", mat->val[i][j]);
                putchar('\n');
        }
}

void zaddition(matrice_t *mat1, matrice_t *mat2, matrice_t *mat)
{
        size_t i, j;

        mat->nb_row = mat1->nb_row;
        mat->nb_col = mat1->nb_col + mat2->nb_col;

        mat->val = malloc(mat->nb_row * sizeof(int *));

        for(i = 0; i < mat->nb_row; i++)
                mat->val[i] = malloc(mat->nb_col * sizeof(int));

        for(i = 0; i < mat->nb_row; i++)
                for(j = 0; j < mat->nb_col; j++)
                        if(j < mat1->nb_col)
                                mat->val[i][j] = mat1->val[i][j];
                        else
                                mat->val[i][j] = mat2->val[i][j - mat1->nb_col];
}

void liberer(matrice_t* mat)
{
    size_t i;
    for(i = 0; i < mat->nb_row; i++)
        free(mat->val[i]);
    free(mat->val);
}



main.h



#ifndef H_MAIN
#define H_MAIN

typedef struct
{
        int **val;
        size_t nb_row;
        size_t nb_col;
} matrice_t;

void init(matrice_t *mat);

void affiche(matrice_t *mat);

void zaddition(matrice_t *mat1, matrice_t *mat2, matrice_t *mat);

void liberer(matrice_t* mat);

#endif



Explications



En fait j'ai fait un peu plus que ce que l'exercice proposait, j'avais envie de m'amuser un peu. Je programme sous linux, ce qui explique que j'utilise assez souvent (et donc ici) les arguments du main.

Mon programme va générer des matrices (contenant des chiffres entre 0 et 9) de manière aléatoire, en fonction du nombre de lignes et de colonnes que l'on demande à l'appel du programme. Pour me simplifier la vie, j'ai défini une structure assez simple contenant le nombre de lignes, de colonnes et la matrice en elle-même. Cela me permet d'éviter d'avoir des fonctions avec un nombre trop important d'arguments.
Donc, trois fonctions, une pour initialiser les deux matrices à concaténer, une pour les afficher (le copier/coller de code, c'est moche => fonction), et la fonction zaddition à proprement parler.

L'utilisation de atoi ne fait pas planter le programme si l'utilisateur de fournit pas un nombre en argument au main, puisque dans ce cas atoi renvoie 0. J'ai décidé de ne pas me prendre la tête avec la gestion des erreurs (du style "l'argument rentré n'est pas correct", etc...), mais il faudrait le faire. Ah la flemme... ^^

Sinon mon code compile sans warnings avec -W -Wall -ansi -pedantic et fonctionne parfaitement chez moi, j'ai testé avec différents arguments.

Un exemple :

eusebus@laptop:~/Progs/exos/zadd$ ./bla 5 6 8

Matrice 1 :

9 1 4 9 6 6
9 4 2 3 6 0
5 0 9 6 9 7
1 7 9 2 1 3
4 2 2 7 3 2

Matrice 2 :

3 5 3 9 4 1 6 5
8 0 8 4 0 5 7 1
2 6 0 5 6 9 7 7
4 4 1 7 1 6 1 6
1 4 6 7 8 4 2 6

Matrice résultant de la concaténation :

9 1 4 9 6 6 3 5 3 9 4 1 6 5
9 4 2 3 6 0 8 0 8 4 0 5 7 1
5 0 9 6 9 7 2 6 0 5 6 9 7 7
1 7 9 2 1 3 4 4 1 7 1 6 1 6
4 2 2 7 3 2 1 4 6 7 8 4 2 6

eusebus@laptop:~/Progs/exos/zadd$



Bonne fin de journée ! :)



Encore un grand merci à tout ceux qui ont participé à cet exercice ! :)

crys

PS : Le prochain pour bientôt. ;)
  • Partager sur Facebook
  • Partager sur Twitter
7 décembre 2008 à 16:37:08

Mouarf ! :o
J'avais la tête tellement concentré sur l'algorithmique que je n'ai même pas vu ça !
Corrigé !
  • Partager sur Facebook
  • Partager sur Twitter
7 décembre 2008 à 16:46:40

Citation : crys'


Et enfin, cerise sur le gâteau, on doit lancer l'exécutable à l'aide de l'invite de commande en précisant les dimensions souhaitées en paramètre du main (il y a un tutoriel non-officiel qui traite ce sujet -> à lire !). La majorité des programmes 'de pros' en mode console suivent ce principe.



Le problème c'est qu'on ne pose la cerise que lorsque le gâteau est prêt. Or c'est loin d'etre le cas, il y a plusieurs insuffisances du point de vue du langage C :

-- retours d'allocation non vérifiés,
-- pas de libérations.

Sinon, il est curieux que la fonction zaddition() reçoive des paramètres de type différents : pourquoi tantot de type matrice_t, tantot pointeur sur matrice_t.

  • Partager sur Facebook
  • Partager sur Twitter
7 décembre 2008 à 16:52:13

Pour la libération de la mémoire, j'ai corrigé, cela ne se reproduira plus.

Pour ce qui est de la fonction zaddition, demande à Eusebus pourquoi il a préféré passer par des instances de matrice_t au lieu de pointeurs pour mat1 et mat2. ;)
  • Partager sur Facebook
  • Partager sur Twitter
7 décembre 2008 à 16:55:44

En effet j'ai fait ça un peu rapidement, je voulais y apporter quelques correctifs.

Il serait en effet plus propre de vérifier les allocations, j'avoue avoir eu un peu la flemme, mais j'ai estimé que l'exercice étant la fonction zaddition, je pouvais être un peu incomplet ailleurs (c'est mal je sais mais comme précisé c'était fait assez rapidement).

Sinon, pour zaddition, en effet c'est une erreur de ma part, étant donné que par principe j'envoie toujours des pointeurs de structures à mes fonctions, mea culpa. Je ne sais pas ce qui m'a pris :(

Je vais essayer de reposter un code plus propre d'ici 15 minutes.

Edit : bon en fait il a corrigé, resterait plus qu'à check les retour des malloc...
  • Partager sur Facebook
  • Partager sur Twitter
7 décembre 2008 à 17:00:11

Le plus grave, c'est que moi je n'ai rien remarqué.
EDIT : c'est bon, tout est corrigé.

Mercredi : exercice suivant ! Au menu : Pile, algorithmique, chaîne ! ;)
  • Partager sur Facebook
  • Partager sur Twitter
8 décembre 2008 à 7:48:22

merci pour la correction et les petits plus :)
@+
Post scriptum : j'ai essaye ton code et petit probleme :
mat->val = malloc(mat->nb_row * sizeof(int *)); Allocation pour les lignes

sur cette ligne j'ai une erreur :'allocation undeclared' (first use in this function)

De plus, j'ai vu sur differents post que le meilleur moyen de faire une allocation serait de faire comme j'ai fait ici:
#include <stdio.h>
#include <stdlib.h>

// declaration de mes fonctions
void affiche (int **tab,int ligne,int colonne);
void fonction (int **tab,int ligne,int colonne);
void concateneTab(int **tabA,int **tabB,int **tabResultat,int ligne,int colonneA,int colonneB);

//corps principal du programme
int main(int argc, char *argv[])
{
   int **tab1 = NULL;
   int **tab2 = NULL;
   int **tab3 = NULL;
   size_t i;

   int L =10;
   int C1 = 5;
   int C2 = 5;

    // allocation dynamique des tableaux
   tab1=malloc(L*sizeof(*tab1));
   tab2=malloc(L*sizeof(*tab2));
   tab3=malloc(L*sizeof(*tab3));
   for (i=0;i<L;i++)
   {
       tab1[i]=malloc(C1 * sizeof (**tab1));
       tab2[i]=malloc(C2 * sizeof (**tab2));
       tab3[i]=malloc((C1+C2) * sizeof (**tab3));
   }

   if ( (tab1 == NULL) || (tab2 == NULL) || (tab3 == NULL) )
   {
       printf("\n Erreur d'allocation de mémoire pour les tableaux \n");
       exit(0);
   }

   // on remplie les tableaux et on les affiche
   fonction(tab1,L,C1);
   affiche(tab1,L,C1);

   fonction(tab2,L,C2);
   affiche(tab2,L,C2);

   // on concatene les deux tableaux et on l'affiche
   concateneTab(tab1,tab2,tab3,L,C1,C2);
   affiche(tab3,L,C1+C2);





   // fin du programme on detruit les donnees en liberant la mémoire
   for (i=0;i<L;i++)
   {
    free(tab1[i]);
    free(tab2[i]);
    free(tab3[i]);
   }
   free(tab1);
   free(tab2);
   free(tab3);



    return 0;
}

// definition de mes fonctions
void affiche (int **tab,int ligne,int colonne)
{
    int i,j;
    for (i=0;i<ligne;i++)
    {
        for (j=0;j<colonne;j++)
        {
            printf("%3d",tab[i][j]);
        }
        printf("\n");
    }
    printf("\n\n");
}


void fonction (int **tab,int ligne,int colonne)
{
    int i,j,compteur;
    compteur = 0;
    for (i=0;i<ligne;i++)
    {
        for (j=0;j<colonne;j++)
        {
            tab[i][j]=compteur;
            compteur ++ ;
        }

    }
}

void concateneTab(int **tabA,int **tabB,int **tabResultat,int ligne,int colonneA,int colonneB)
{
    int i,j;

    for(i=0;i<ligne;i++)
    {
        for(j=0;j<colonneA;j++)
        {
            tabResultat[i][j]=tabA[i][j];
        }

        for(j=0;j<colonneB;j++)
        {
            tabResultat[i][j+colonneB]=tabB[i][j];
        }
    }
}


Par contre je sais que mon code est loin d'être optimisé, mais me semble plus facile à lire pour les débutants (je n'utilise aucune contraction permise du langage, j'ai un peu de mal avec pour ma part, et il faut que je relise le cours de mateo) !
Bon si quelqu'un peut me dire si j'ai tout bien fait , notamment dans mes controles 'd erreurs d'allocation' et dans ma liberation de la memoire pour les "free" !
Merci
  • Partager sur Facebook
  • Partager sur Twitter
9 décembre 2008 à 22:27:16

Il a oublié de mettre "Allocation pour les lignes" en commentaire :-°

Mets-le entre /* */ et ça devrait aller. ;)

En ce qui concerne l'allocation, c'est vrai que j'aurais pu écrire (pour la fonction init par exemple)

void init(matrice_t *mat)
{
        size_t i = 0, j = 0;

        mat->val = malloc(mat->nb_row * sizeof *mat->val); /* Allocation pour les lignes */

        for(i = 0; i < mat->nb_row; i++)
        {
                mat->val[i] = malloc(mat->nb_col * sizeof **mat->val); // Allocation pour les colonnes
                for(j = 0; j < mat->nb_col; j++)
                        mat->val[i][j] = rand() % 10; // nombre pseudo-aléatoire entre 0 et 10
        }
}


La première manière comme la seconde sont correctes, après la 2e est en effet à préférer pour des questions de maintenance et de facilité de modification (si j'veux par exemple que val soit de type (float **) :p ) ça évite d'avoir à farfouiller partout dans le code pour modifier les alloc.
  • Partager sur Facebook
  • Partager sur Twitter
9 décembre 2008 à 22:33:40

j'ai cherche midi à 14 h alors que la solution etait plus simple :-°
l'oubli des // pour les commentaires
merci, je vais pouvoir etudier ton code
@+
  • Partager sur Facebook
  • Partager sur Twitter
9 décembre 2008 à 23:04:52

Citation : zx-spectrum


mat->val = malloc(mat->nb_row * sizeof(int *)); Allocation pour les lignes


sur cette ligne j'ai une erreur :'allocation undeclared' (first use in this function)


Oui, il y a oublié les slash de commentaires.


Citation : zx-spectrum


De plus, j'ai vu sur differents post que le meilleur moyen de faire une allocation serait de faire comme j'ai fait ici:



OK mais on n'est pas toujours obligé de ne pas faire simple. Et pour être encore plus direct je dirais qu'il ne faut pas confondre la cerise et le gâteau et donc savoir distinguer l'accessoire de l'essentiel et hiérarchiser les nécessités. Ce que je veux dire c'est qu'il assez grotesque de veiller à des détails et négliger (ou ne pas vérifier) des éléments importants.


Citation : zx-spectrum


Par contre je sais que mon code est loin d'être optimisé,


Je ne sais pas pourquoi sur ce forum beaucoup d'intervenants emploient le terme "optimiser" ou ses dérivés à tort et à travers.

Citation : zx-spectrum



Bon si quelqu'un peut me dire si j'ai tout bien fait , notamment dans mes controles 'd erreurs d'allocation'


et bien justement :


Citation : zx-spectrum


int main(int argc, char *argv[])
{
   int **tab1 = NULL;
   int **tab2 = NULL;
   int **tab3 = NULL;
   size_t i;

   int L =10;
   int C1 = 5;
   int C2 = 5;

    // allocation dynamique des tableaux
   tab1=malloc(L*sizeof(*tab1));
   tab2=malloc(L*sizeof(*tab2));
   tab3=malloc(L*sizeof(*tab3));
   for (i=0;i<L;i++)
   {
       tab1[i]=malloc(C1 * sizeof (**tab1));
       tab2[i]=malloc(C2 * sizeof (**tab2));
       tab3[i]=malloc((C1+C2) * sizeof (**tab3));
   }

   if ( (tab1 == NULL) || (tab2 == NULL) || (tab3 == NULL) )
   {
       printf("\n Erreur d'allocation de mémoire pour les tableaux \n");
       exit(0);
   }

}




Tu utilises tab1 (ligne 18) et tu vérifies APRÈS (ligne 23) si l'allocation a réussi. Dans la meme ligne (ligne 18), tu as privilégié un détail (sizeof*tab au lieu d'un tout bête sizeof(int *) ) et tu a as négligé un point essentiel (la vérification opportune du retour).
Au passage, il n'est pas usuel de capitaliser l'initiale d'une variable.



Sinon, une question qui n'a pas été évoqué dans ce post et qui a sa place me semble-t-il : qui doit se charger d'allouer l'espace nécessaire pour le tableau concaténé. Si on regarde comment la bibliothèque standard (fonction strcat) règle ce type de problème, on voit que ce n'est pas la fonction qui concatène (ici : zaddition) qui alloue et perso, je trouve cette solution plus claire : une fonction = un boulot bien précis. En plus ça rend le code plus autonome, plus accessible au débutant (l'allocation dynamique suppose connues pas mal de choses pour etre entamée). Au passage je note que le code proposé ne donne pas d'exemple de concaténation de tableaux statiques (ce type d'exemple peut être souhaité par un débutant).

  • Partager sur Facebook
  • Partager sur Twitter
9 décembre 2008 à 23:38:52

Merci Candide pour tes remarques constructives, je vais revoir ma copie pour mes "malloc" et je reposterai ma nouvelle solution obtenu

Je suis d'accord avec toi sur le fait, qu'une solution avec des tableaux statiques aurait été plus simple pour les débutants, car j'ai moi même des difficultées à déchiffrer le code d'Eusebus

Il serait bien que d'autres débutants comme moi se manifestent pour savoir à quel niveau ils se situent, et ne pas avoir peur de dire ce qu'ils en pensent !
Je constate que peu de personnes postent leur code ce qui m'amène a dire :
- soit ils comprennent pas grand chose, a ce qu'on fait ici
- soit cela ne les intéressent pas.

Le but de se propos : est bien sûr d'aider Chys à choisir ses sujets, et de preciser peut être une méthode à employer.

En tout cas je remercie tous les intervenants, et j'encourage à continuer.
@+
  • Partager sur Facebook
  • Partager sur Twitter
10 décembre 2008 à 18:06:20

Titre : zMath
Mois : décembre
Sujet : algorithmique, pile

Objectif



L'objectif de cet exercice est de réaliser un calculateur d'expressions sous la forme NPI (notation polonaise inverse) en vous servant des piles. Vous devez donc vous imaginer un algorithme qui permettrait d'évaluer un calcul en NPI en retournant le résultat. Il existe un tutoriel non-officiel sur ce site traitant des piles (structure de données), vous allez devoir le lire pour mener à bien cet exercice. ;)
Mais voyons cela de plus près !

La notation polonaise inverse



Qu'est-ce que cette notation ? En réalité, c'est tout simple, la NPI est une notation de calculs mathématiques sous une autre forme que celle que nous avons l'habitude d'utiliser. Dans cette notation, il n'existe pas de parenthèses. Pour vous expliquer concrètement de quoi il s'agit, voici une expression mathématique simple écrite ordinairement que je vais ensuite convertir en utilisant la NPI :

<math>\(4 * (3 + 5) - 2\)</math>

Exprimé en NPI, on obient : <math>\(3 5 + 4 * 2 -\)</math>

Comment ai-je fait ? En réalité, le but de la NPI est de pouvoir lire le calcul à partir du début puis, dés que l'on rencontre un opérateur (<math>\(+,-,*,/,\^\)</math>), on applique ce dernier au deux derniers nombres lus ! Vous comprenez un peu mieux le précédent calcul j'espère. On commence par lire 3 puis 5, ensuite, on tombe sur l'opérateur '+'. On va donc additionner 3 et 5 et considérer le résultat, pour la suite, comme une valeur précédemment lue. Voici donc en détail les opérations effectuées :

Calcul NPI : <math>\(3 5 + 4 * 2 -\)</math>
<math>\(3 + 5 = 8\)</math>
<math>\(8 * 4 = 32\)</math>
<math>\(32 - 2 = 30\)</math> !

Pourquoi utiliser les piles ?



La pile est une structure de données que la majorité des programmeurs considère comme adaptée à l'évaluation d'expressions mathématiques en NPI. La principe de la pile et de pouvoir facilement ajouter des valeurs au sommet de la pile et de les récupérer en temps voulu à partir du sommet (LIFO : last in, first out). Imaginons que nous avons un calcul NPI à lire, nous allons lire l'expression et pour chaque nombre rencontré, nous allons l'ajouter au sommet de la pile (opération couramment nommée 'push'). Dés que l'on rencontre un opérateur, on récupère les deux dernières valeurs lues en les retirant de la pile ('pop') et en ajoutant à la pile la valeur qui résulte du calcul de ses deux valeurs par l'opérateur.

En gros, pour ceux qui se sentent à l'aise, on aurait aussi pu écrire <math>\(4 * (3 + 5) - 2\)</math> de cette manière :

<math>\(4 3 5 + * 2 -\)</math>
ou : <math>\(2 4 3 5 + * -\)</math>

Un exemple concret



À présent, je vais vous montrer ce que doit pouvoir réaliser votre algorithme en utilisant les piles. Supposons que je veuille résoudre le calcul <math>\(4 3 5 + * 2 -\)</math>, l'algorithme sera :
La pile est vide
On reçoit la chaîne
   Lecture de 4 : la pile contient [4]
   Lecture de 3 : la pile contient [4,3]
   Lecture de 5 : la pile contient [4,3,5]
   Lecture de l'opérateur + :
      On récupère 3 et 5 : la pile contient [4]
      On applique l'opérateur + à 3 et 5, on obtient 8
      On ajoute 8 à la pile : la pile contient [4,8]
   Lecture de l'opérateur * :
      On récupère 4 et 8 : la pile est vide
      On applique l'opérateur * à 4 et 8, on obtient 32
      On ajoute 32 à la pile : la pile contient [32]
   Lecture de 2 : la pile contient [32,2]
   Lecture de l'opérateur - :
      On récupère 32 et 2 : la pile est vide
      On applique l'opérateur - à 32 et 2, on obtient 30
      On ajoute 30 à la pile : la pile contient [30]
Fin d'itération sur la chaîne
Renvoie de la valeur stocké dans la pile : Renvoie de 30
Résultat du calcul : 30


Où récupérer le calcul ? Quelles valeurs autorisées ?



Pour la première question, je vous laisse le choix : soit vous récupérez l'expression mathématique en NPI à partir d'un fichier et vous afficher le résultat en console, soit on demande à l'utilisateur d'entrer cette expression directement par le clavier lors de l'exécution du programme. Pour la dernière question, utiliser seulement les chiffres dans vos calculs et non les nombres à plus d'un chiffre. Pour lire un nombre dans une chaîne, c'est déjà un algorithme tout à part (pas très dur mais faites juste ce qui est demandé), tandis que récupérer un chiffre est très simple (on n'a même pas à réfléchir) et vous aurez tout le temps pour vous concentrer sur l'algorithme de lecture d'expressions en NPI, ce qui est le but de l'exercice.

Un exemple de programme pourrait être :
Saisissez le calcul NPI : 3 2 + 4 * 6 / 2 -
Résultat : 1
(Notez que les espaces entre les caractères ne sont pas nécessaires si vous manipulez uniquement les chiffres).

Pour les plus férus d'algorithmique, vous êtes libres d'implémenter la lecture et la récupération de nombre à plus d'un chiffre ! ^^

Je corrigerais cet exercice dans plus ou moins trois semaines (on m'a demandé de laisser un peu plus de temps cette fois).

Merci pour votre (future) participation !
crys
  • Partager sur Facebook
  • Partager sur Twitter
10 décembre 2008 à 18:56:31

Ça va t'es allé le chercher loin l'exercice :D
  • Partager sur Facebook
  • Partager sur Twitter
10 décembre 2008 à 19:22:39

Pas vraiment, la NPI est une notation souvent utilisée en programmation ou qui a souvent été utilisée, un exercice de ce genre est typique. :)
  • Partager sur Facebook
  • Partager sur Twitter
10 décembre 2008 à 19:35:36

bonjour chrys,

peut on avoir un joker supplémentaire : une semaine de plus pour pouvoir faire l'exo. Pour ma part je vais avoir besoin d'un peu plus de temps, déja pour comprendre le cours que j'ai pas encore vu :-°
@+
  • Partager sur Facebook
  • Partager sur Twitter
10 décembre 2008 à 20:00:48

Mais pas de soucis ! Même si il faut y passer un mois, l'important, c'est que vous compreniez et que vous réussissiez tout en apprenant. Vous avez donc à peu près 3 semaines.
  • Partager sur Facebook
  • Partager sur Twitter
11 décembre 2008 à 2:10:05

Bonjour,

Je me permet de poster le pseudo-code de crys' de façon plus lisible selon moi, car on "visualise" mieux la pile:

La pile est vide
On reçoit la chaîne
   Lecture de 4 : la pile contient [...4]
   Lecture de 3 : la pile contient [...3,4]
   Lecture de 5 : la pile contient [...5,3,4]
   Lecture de l'opérateur + :
      On récupère 5 et 3 : la pile contient [...4]
      On applique l'opérateur + à 5 et 3, on obtient 8
      On ajoute 8 à la pile : la pile contient [...8,4]
   Lecture de l'opérateur * :
      On récupère 8 et 4 : la pile est vide [...]
      On applique l'opérateur * à 8 et 4, on obtient 32
      On ajoute 32 à la pile : la pile contient [...32]
   Lecture de 2 : la pile contient [...2,32]
   Lecture de l'opérateur - :
      On récupère 2 et 32 : la pile est vide
      On applique l'opérateur - à 2 et 32, on obtient 30
      On ajoute 30 à la pile : la pile contient [...30]
Fin d'itération sur la chaîne
Renvoie de la valeur stocké dans la pile : Renvoie de 30
Résultat du calcul : 30


Et puis : lien vers le tuto sur les piles et les files.
  • Partager sur Facebook
  • Partager sur Twitter
11 décembre 2008 à 6:18:32

Merci Yoch pour ce complément d'info ;)
@+
Ps: tu m'as fait gagné 24 H :lol:
  • Partager sur Facebook
  • Partager sur Twitter
11 décembre 2008 à 20:40:46

Citation : crys'

Pour lire un nombre dans une chaîne, c'est déjà un algorithme tout à part (pas très dur mais faites juste ce qui est demandé), tandis que récupérer un chiffre est très simple (on n'a même pas à réfléchir) et vous aurez tout le temps pour vous concentrer sur l'algorithme de lecture d'expressions en NPI, ce qui est le but de l'exercice.


Je propose a ceux qui voudraient améliorer leur calculette NPI sans pour autant se préoccuper de cette deuxième question algorithmique d'utiliser la fonction strtol(), particulièrement adaptée.

Autre idée d'amélioration, pour ceux qui veulent : que le programme signale si erreur dans l'expression mathématique d'entrée.
  • Partager sur Facebook
  • Partager sur Twitter
12 décembre 2008 à 7:24:14

@ Flooder : T'as réfléchi une seconde avant d'écrire ça ?

Citation : Flooder

int zIsspace(char c)
{
   return c && ( c==' ' || c=='\t' || c=='\n' );
}


Par ailleurs, ton code pour zStrcap est inutilement compliqué (a part le fait qu'il ne compilait pas pour cause de fautes de frappe) :

char * zStrcap(char * chaine)
{
    char* c = chaine;
    /* pour chaque phrase de la chaine */
    while (*c)
    {
        /* on avance jusqu'a premier caractère different de l'espace */
        while ( zIsspace(*c) )
        {
            c++;
        }
        /* On met le caractère en majuscule si possible */
        *c = zToupper(*c);
        /* On met les autres caractères en minuscule */
        while ( !zIsendsent(*c) )
        {
            c++;
            *c = zTolower(*c);
        }
        c++;
    }
    return chaine;
}
  • Partager sur Facebook
  • Partager sur Twitter
12 décembre 2008 à 14:16:51

Citation

@ Flooder : T'as réfléchi une seconde avant d'écrire ça ?


Effectivement, j'ai bug ^^
  • Partager sur Facebook
  • Partager sur Twitter
19 décembre 2008 à 21:37:24

Hello, voici mon premier post.
Et je souhaiterais pouvoir participer à ce topic, Or, je ne peux pas, a cause de mon niveau
( en effet très bas :euh: .).
Donc, si il y aurais possibilité de faire de genres de TP sur les cours aux alentours du cours sur les pointeurs, s'il vous plaît, je vous en serait reconnaissant.

Donc voilà, désolé si je chamboule un peu tout, mais bon, là je patauge dans la semoule. :p
  • Partager sur Facebook
  • Partager sur Twitter
19 décembre 2008 à 22:28:21

Salut,

Je tenterais de faire un peu plus simple pour le prochain exercice mais voici ce que je te conseille d'ici là : continue à lire les chapitres du cours de M@teo21 en faisant sérieusement tous les TPs et en allant te documenter si nécessaire sur d'autres sites. ;)
  • Partager sur Facebook
  • Partager sur Twitter
20 décembre 2008 à 16:37:18

Citation : crys'


L'objectif de cet exercice est de réaliser un calculateur d'expressions sous la forme NPI (notation polonaise inverse) en vous servant des piles. Vous devez donc vous imaginer un algorithme qui permettrait d'évaluer un calcul en NPI en retournant le résultat.



Comme d'habitude le spécification sont assez vagues. Le programmeur est-il censé donner une expression valide ? Sous quelle forme apparaissent les entrées, ce n'est pas anodin du tout à cause des nombres négatifs (cf. ci-dessous) ? L'obligation d'utiliser une pile est une contrainte dictée par la tradition mais on peut s'en abstenir me semble-t-il.


Sinon, je trouve l'exercice mal choisi, cf. le titre du post : "Exercices pour débutants en C". C'est beaucoup plus un exercice sur les structures de données et d'algorithmique qu'un exercice de codage en C, ce qui est au bout du compte dans ce zExo la partie la plus facile.

Par ailleurs, l'exercice en soi est difficile pour des débutants. La preuve ? toute simple : le code dans K&R qui implémente cette question (§5.10 Command-line Arguments) est très long : environ 125 lignes mais une ligne de C de K&R vaut 3 à 5 lignes chez le débutant, donc on va compter 500 lignes et Kernighan est un maitre en algorithmique donc le codage du débutant sera encore plus long. Noter que les formules doivent tenir dans des tableaux statiques ce qui un grosse limitation et dont la résolution nécessiterait encore plus de lignes de code.

Et le pire, ils se sont plantés. D'abord, une formule qui marche :


candide@candide-desktop:~$ ./x
3 5 + 4 * 2 -
	30


Maintenant deux hics :

1°) le programme ne gère pas les formules invalides et pire il répond quelque chose :

candide@candide-desktop:~$ ./x
100000 1 1 * + 1 1 1 + +
	3


2°) Absolument incroyable, le programme ne sait pas gérer les nombre négatifs et le passage à l'opposé, exemple :

candide@candide-desktop:~$ ./x
2 3 + -
error: stack empty
	-5
2 3 + - 2 3 + - *
error: stack empty
error: stack empty
	-0
2 -3 -
error: stack empty
	-5


Le code de K&R :


#include <stdio.h>
#include <stdlib.h> /* for atof() - in K&R, math.h is referenced - this is an anachronism */

#define MAXOP 100 /* max size of operand or operator */
#define NUMBER '0' /* signal that a number was found */

int getop(char []);
void push(double);
double pop(void);

/* reverse Polish calculator */

int main(void)
{
  int type;
  double op2;
  char s[MAXOP];

  while((type = getop(s)) != EOF)
  {
    switch(type)
    {
      case NUMBER:
        push(atof(s));
        break;
      case '+':
        push(pop() + pop());
        break;
      case '*':
        push(pop() * pop());
        break;
      case '-':
        op2 = pop();
        push(pop() - op2);
        break;
      case '/':
        op2 = pop();
        if(op2 != 0.0)
          push(pop() / op2);
        else
          printf("error: zero divisor\n");
        break;
      case '\n':
        printf("\t%.8g\n", pop());
        break;
      default:
        printf("error: unknown command %s\n", s);
        break;
    }
  }

  return 0;
}

#define MAXVAL  100 /* maximum depth of val stack */

int sp = 0; /* next free stack position */
double val[MAXVAL]; /* value stack */

/* push: push f onto value stack */
void push(double f)
{
  if(sp < MAXVAL)
    val[sp++] = f;
  else
    printf("error: stack full, can't push %g\n", f);
}

/* pop: pop and return top value from stack */
double pop(void)
{
  if(sp > 0)
    return val[--sp];
  else
  {
    printf("error: stack empty\n");
    return 0.0;
  }
}

#include <ctype.h>

int getch(void);
void ungetch(int);

/* getop: get next operator or numeric operand */
int getop(char s[])
{
  int i, c;

  while((s[0] = c = getch()) == ' ' || c == '\t')
    ;

  s[1] = '\0';
  if(!isdigit(c) && c != '.')
    return c; /* not a number */
  i = 0;
  if(isdigit(c)) /* collect integer part */
    while(isdigit(s[++i] = c = getch()))
      ;
  if(c == '.')
    while(isdigit(s[++i] = c = getch()))
      ;
  s[i] = '\0';
  if(c != EOF)
    ungetch(c);
  return NUMBER;
}

#define BUFSIZE 100

char buf[BUFSIZE]; /* buffer for ungetch */
int bufp = 0; /* next free position in buf */

int getch(void) /* get a (possibly pushed back) character */
{
  return (bufp > 0) ? buf[--bufp] : getchar();
}

void ungetch(int c) /* push character back on input */
{
  if(bufp >= BUFSIZE)
    printf("ungetch: too many characters\n");
  else
    buf[bufp++] = c;
}




Au passage, tout ceci me confirme que, malgré tout ce qu'on dit (et surtout qu'on répète sans avoir vérifié par soi-même), le K&R est un livre bâclé (et j'aurais de nombreux autres exemples à donner).

Bref, ton exercice me semble totalement inadapté et c'est d'autant plus clair qu'il faisait suite à un exercice quasi-trivial qui avait donné peu de réponses et juste avant un exo super facile sur les tris élémentaires et qui avaient été considérés par toi meme comme difficile (je ne me souviens plus du terme exact).

Choisir des exercices ainsi que exemples adaptés à ceux à qui on veut enseigner quelque chose est tâche hautement difficile et c'est en partie à cause de la mauvaise réalisation de cette tâche que l'apprentissage du C est difficile.

Une fois de plus, ce ne seront pas les débutants qui répondront substantiellement à cet exo, c'est à craindre en tous cas.

  • Partager sur Facebook
  • Partager sur Twitter