Partage
  • Partager sur Facebook
  • Partager sur Twitter

Exercice débutant sur les listes chaînées

Sujet résolu
    27 mars 2023 à 4:07:17

    Bonjour,

    Je bloque sur un exercice simple sur les listes chaînées. Je dois écrire un programme d'inscription à un cours, où je dois pouvoir mettre à jour une liste chaînée d'étudiants (avec leurs id et leurs noms) qui se sont inscrit à ce cours. La liste doit toujours être triée dans l'ordre croissant de l'id étudiant.


    Le fichier d'entrée contient les fonctions ci-dessous :

    • A [id] [nom] -> si [id] existe déjà dans une liste, écrire un message d'échec dans le fichier de sortie ; sinon, ajoutez l'élève et écrire la liste mise à jour dans le fichier de sortie
    • D [id] -> si [id] n'existe pas dans la liste, écrire un message d'échec dans le fichier de sortie ; sinon, supprimez l'étudiant et écrire la liste mise à jour dans le fichier de sortie
    • F [id] -> si [id] n'existe pas dans la liste, écrire un message d'échec dans le fichier de sortie ; sinon, écrire l'id et le nom de cet étudiant dans le fichier de sortie

     On me fournit ce code de base :

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    
    int main(int argc, char* argv[]) {
      if (argc != 3) {
        printf("Correct usage: [program] [input] [output]\n");
        exit(1);
      }
      FILE* inFile = fopen(argv[1], "r");
      if (inFile == NULL) {
        printf("no such file %s\n", argv[1]);
        exit(1);
      }
      FILE* outFile = fopen(argv[2], "w");
      if (outFile == NULL) {
        printf("no such file %s\n", argv[2]);
        exit(1);
      }
      Course course;
      char line[512];
      while (fgets(line, sizeof(line), inFile))
      {
        char* token = strtok(line, " \n");
        const char op = line[0];
        int id;
        Student* found = NULL;
        token = strtok(NULL, " \n");
        if (!token) {
          printf("invalid input\n");
          exit(1);
        }
        id = atoi(token);
        switch(op)
        {
          case 'A':
            token = strtok(NULL, " \n");
            if (!token) {
              printf("ADD: invalid input\n");
              exit(1);
            }
            if (addStudent(&course, id, token))
              write(&course, outFile);
            else
              fprintf(outFile, "Addition failed\n");
            break;
          case 'D':
            if (deleteStudent(&course, id))
              write(&course, outFile);
            else
              fprintf(outFile, "Deletion failed\n");
            break;
          case 'F':
            found = find(&course, id);
            if (found == NULL)
              fprintf(outFile, "Search failed\n");
            else
              fprintf(outFile, "%d %s\n", found->id, found->name);
            break;
          default:
            printf("Undefined operator\n");
            exit(1);
        }
      }
      fclose(outFile);
      fclose(inFile);
    }

    Et j'ai fait ça pour le moment (quasiment tout est du copier-coller et modifié de codes que j'ai trouvé sur Internet) :

    typedef struct student {
      int new_id;
      char new_name[50];
      struct student* next;
    } Student;
    
    typedef struct course {
      Student* head;
    } Course; 
    
    bool isEmpty(Course* c) {
      return (c->head == NULL);
    }
    
    bool addStudent(Course* c, int id, char* name) {
      struct Course* new_student = (struct Course*)malloc(sizeof(struct Course));
      new_student->new_id = id;
      new_student->new_name = name;
      new_student->next = NULL;
      struct Course* current;
      if ((*c)->new_id = new_student->new_id) {
        return 0;
      }
      if (*c == NULL || (*c)->new_id > new_student->new_id) {
        new_student->next = *c;
        *c = new_student;
      }
      else {
        current = *c;
        while (current->next != NULL && current->next->new_id < new_student->new_id) {
          current = current->next;
        }
        new_student->next = current->next;
        current->next = new_student;
      }
      return 1;
    }
    
    bool deleteStudent(Course* c, int id) {
      if (c == NULL) {
        return 0;
      }
      if (c->new_id == id) {
        Course* t = c;
        c = c->next;
        free(t);
        return 1;
      }
      deleteStudent(c->next, id);
    }
    
    Student* find(Course* c, int id) {
      if (c == NULL)
          return c;
      if (c->new_id == id)
          return NULL;
      return find(c->next, id);
    }
    
    void write(Course* c, FILE* outFile) {
      //TODO
    }

    A savoir que je suis obligée d'avoir le nom et les arguments des fonctions comme ça, c'est en partie à cause de ça que je suis bloquée car je ne sais pas comment utiliser Student et Course en même temp, et je n'ai rien trouvé sur Internet. De plus, je ne sais comment utiliser la fonction main ci-dessus, je n'en ai jamais utilisé de si complexes et je ne sais pas trop comment gérer les erreurs avec. 

    Est-ce que quelqu'un peut me guider dans cet exercice s'ils-vous-plaît ? Merci beaucoup d'avance :)

    -
    Edité par emmakber 27 mars 2023 à 4:47:52

    • Partager sur Facebook
    • Partager sur Twitter
      27 mars 2023 à 16:44:06

      Bonjour emmakber,

      As-tu essayé de mettre ton code (que tu dis avoir copié-coller d'Internet et modifié) dans ton code de base, avant la fonction main() et de tenter de compiler ?

      Moi j'ai essayé et cela donne ceci :

      $ gcc -Wall -Wextra main.c
      main.c: In function ‘addStudent’:
      main.c:22:62: error: invalid application of ‘sizeof’ to incomplete type ‘struct Course’
         struct Course* new_student = (struct Course*)malloc(sizeof(struct Course));
                                                                    ^~~~~~
      main.c:23:14: error: dereferencing pointer to incomplete type ‘struct Course’
         new_student->new_id = id;
                    ^~
      main.c:27:11: error: invalid type argument of ‘->’ (have ‘Course’ {aka ‘struct course’})
         if ((*c)->new_id = new_student->new_id) {
                 ^~
      main.c:30:10: error: invalid operands to binary == (have ‘Course’ {aka ‘struct course’} and ‘void *’)
         if (*c == NULL || (*c)->new_id > new_student->new_id) {
             ~~ ^~
      main.c:30:25: error: invalid type argument of ‘->’ (have ‘Course’ {aka ‘struct course’})
         if (*c == NULL || (*c)->new_id > new_student->new_id) {
                               ^~
      main.c:32:8: error: incompatible types when assigning to type ‘Course’ {aka ‘struct course’} from type ‘struct Course *’
           *c = new_student;
              ^
      main.c:35:13: error: incompatible types when assigning to type ‘struct Course *’ from type ‘Course’ {aka ‘struct course’}
           current = *c;
                   ^
      main.c: In function ‘deleteStudent’:
      main.c:49:8: error: ‘Course’ {aka ‘struct course’} has no member named ‘new_id’
         if (c->new_id == id) {
              ^~
      main.c:51:10: error: ‘Course’ {aka ‘struct course’} has no member named ‘next’
           c = c->next;
                ^~
      main.c:55:18: error: ‘Course’ {aka ‘struct course’} has no member named ‘next’
         deleteStudent(c->next, id);
                        ^~
      main.c: In function ‘find’:
      main.c:60:14: warning: returning ‘Course *’ {aka ‘struct course *’} from a function with incompatible return type ‘Student *’ {aka ‘struct student *’} [-Wincompatible-pointer-types]
             return c;
                    ^
      main.c:61:8: error: ‘Course’ {aka ‘struct course’} has no member named ‘new_id’
         if (c->new_id == id)
              ^~
      main.c:63:16: error: ‘Course’ {aka ‘struct course’} has no member named ‘next’
         return find(c->next, id);
                      ^~
      main.c: In function ‘write’:
      main.c:66:20: warning: unused parameter ‘c’ [-Wunused-parameter]
       void write(Course* c, FILE* outFile) {
                  ~~~~~~~~^
      main.c:66:29: warning: unused parameter ‘outFile’ [-Wunused-parameter]
       void write(Course* c, FILE* outFile) {
                             ~~~~~~^~~~~~~
      main.c: In function ‘main’:
      main.c:125:44: error: ‘Student’ {aka ‘struct student’} has no member named ‘id’
                 fprintf(outFile, "%d %s\n", found->id, found->name);
                                                  ^~
      main.c:125:55: error: ‘Student’ {aka ‘struct student’} has no member named ‘name’
                 fprintf(outFile, "%d %s\n", found->id, found->name);
                                                             ^~
      main.c: In function ‘deleteStudent’:
      main.c:56:1: warning: control reaches end of non-void function [-Wreturn-type]
       }
       ^
      main.c: In function ‘find’:
      main.c:64:1: warning: control reaches end of non-void function [-Wreturn-type]
       }
       ^
      

      Les avertissements renvoyés par gcc devraient te renseigner sur plusieurs choses qui ne vont pas :

      • struct Course n'est pas un type dans ton code, c'est soit struct course soit son alias Course
      • si c est de type Course*, c'est un pointeur sur struct, et pour accéder à un membre new_id de cette struct à partir de son pointeur tu fais c->new_id et pas (*c)->new_id
      • si c est de type Course*, c'est un pointeur sur struct, et écrire if (*c == NULL ... n'a pas de sens
      • tu confonds le pointeur sur struct avec son contenu à de nombreux endroits

      Le compilateur est ton ami. Commence déjà par comprendre ces avertissemenst et les corriger.

      Cela se fait plus facilement lorsqu'on écrit le code soit même et que l'on compile souvent (avec les warnings). On se rend compte tôt des problèmes et on corrige le tir pour partir sur de bonnes bases, on comprend mieux ce qu'on fait progressivement, plutôt que de copier du code qu'on ne comprend pas et se retrouver avec une montagne d'avertissements.

      Si tu utilises le type bool, autant renvoyer true au lieu de 1.

      Tu pourrais créer une fonction permettant d'initialiser une liste vide, et à partir de là faire ta fonction d'ajout, avec des cas de tests te permettant de vérifier que lors de l'insertion, ta contrainte que les enregistrements que tu insères dans ta liste soient ordonnés sur les numéros d'étudiants soit respectée.

      En C, une chaîne se copie dans un tableau de char correctement dimensionné avec strcpy

      https://cplusplus.com/reference/cstring/strcpy/

      Si tu copies le pointeur sur char issu de strtok(), à la ligne suivante lue, il ne pointera plus sur la chaîne précédente. Sur le dimensionnement du membre char new_name[50]; comment sais-tu que 50 chars suffisent alors que le code que tu as peut lire une ligne dans un tableau dimensionné à 512 ?

      Ce n'est pas clair pour moi ce que doit faire la fonction write(), compte tenu de ce que tu postes.

      Edit: coquilles et ajout pb copie de chaîne

      -
      Edité par Dlks 27 mars 2023 à 17:25:25

      • Partager sur Facebook
      • Partager sur Twitter
        28 mars 2023 à 2:18:55

        Faire un copier-coller sans comprendre, et nodifier sans comprendre ...
        C'est peut-être un exercice trop difficile étant donné ta compréhension des pointeurs et des structures.
        J'ai repris ta fonction addStudent en ajoutant quelques commentaires (mais sans vraiment modifier):
        ça ne marchera pas avec une liste vide.
         
        bool addStudent(Course* c, int id, char* name) {
            struct Course* new_student = (struct Course*)malloc(sizeof(struct Course));    // plutôt Student, on définit un étudiant.
            new_student->new_id = id;
            new_student->new_name = name;   // strcpy(...)
            new_student->next = NULL;
            struct Course* current;   // Student, on parcours une liste d'étudiants.
            if ((*c)->new_id = new_student->new_id) {   // Ici, on compare, c'est == au lieu de =, et si la liste est vide?
                return false;   // au lieu de 0, les éléments identiques ne sont pas nécessairement au début ...
            }
            if (*c == NULL || (*c)->new_id > new_student->new_id) {
                new_student->next = *c;
                *c = new_student;
            }
            else {
                current = *c;
                while (current->next != NULL && current->next->new_id < new_student->new_id) {
                    current = current->next;
                }
                new_student->next = current->next;
                current->next = new_student;
            }
            return true;    // au lieu de 1
        }
        • Partager sur Facebook
        • Partager sur Twitter

        Le Tout est souvent plus grand que la somme de ses parties.

          28 mars 2023 à 2:54:50

          Merci beaucoup pour vos réponses. Avant de les voir, j'avais continué de travailler sur mon script et j'ai réussit à le faire fonctionner en recommençant complètement mes fonctions, de sorte à mieux les comprendre.

          Merci beaucoup quand même ! :D

          • Partager sur Facebook
          • Partager sur Twitter
            28 mars 2023 à 3:20:37

            Je te donne tout de même ma version de la fonction addStudent. Tu noteras qu'elle est plus simple que ta version d'origine.
            J'ai déplacé juste avant le chaînage la création du nouvel élément pour éviter les fuites (ou gaspillage) de mémoire si l'élément est déjà dans la liste:
             
            bool addStudent(Course* course, int id, char* name) {
                Student* current = course->head;
                Student* previous = NULL;
                while(current != NULL && current->new_id < id) {   // < pour ordre ascendant, > pour ordre descendant.
                    previous = current;
                    current = current->next;
                }
                if(current != NULL && current->new_id == id) {
                    return false;
                }
                Student* new_student = malloc(sizeof(Student));
                new_student->new_id = id;
                strcpy(new_student->new_name, name);
                new_student->next = current;
                if(previous == NULL)
                    course->head = new_student;
                else
                    previous->next = new_student;
                return true;
            }
            • Partager sur Facebook
            • Partager sur Twitter

            Le Tout est souvent plus grand que la somme de ses parties.

              28 mars 2023 à 12:59:28

              On a tout intérêt à séparer

              • la création (allocation + initialisation) d'une structure représentant un étudiant
              • son insertion dans la liste
              Student *new_student(int id, char *name)
              {
                  Student* s = malloc(sizeof(Student));
                  s->new_id = id;
                  strcpy(s->name, name);
                  return s;
              }
              
              
              Je ne sais pas pourquoi (*) mais on trouve une certaine obstination à confondre Etudiant et Maillon d'une Liste d'étudiants.
              Sachant qu'un étudiant pourrait faire partie de plusieurs listes, par groupe, par année, par participation à tels cours etc. le maillon devrait contenir un pointeur (pour que plusieurs pointeurs puissent pointer sur le même) et non pas une structure :
              struct Etudiant {
                  int id;
                  char *nom;
              };
              
              struct Maillon {
                  struct Etudiant *etudiant;
                  struct Maillon  *suivant;
              };
              
              struct Liste {
                  struct Maillon *premier, *dernier;
                  int taille;
              };
              
              Bonus, ça permet de faire gratuitement des listes génériques en C. Suffit de dire que les maillons sont génériques
              struct Maillon {
                  void *etudiant;
                  struct Maillon  *suivant;
              };
              et comme ça on n'écrit les codes de manipulation de liste qu'une seule fois (*) dans sa vie.
              (*) si, parce qu'autrefois on faisait comme ça (des chaînages internes) dans les exemples de base (genre liste d'entiers), et les cours tous aussi nuls ont copié les uns sur les autres y compris pour des trucs qui ont une "sémantique d'entité" plutôt que de valeur.
              (**) sauf pour ceux qui comme moi "rangent" leurs affaires dans un bordel infame.


              -
              Edité par michelbillaud 28 mars 2023 à 13:28:55

              • Partager sur Facebook
              • Partager sur Twitter

              Exercice débutant sur les listes chaînées

              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
              • Editeur
              • Markdown